服务器端

package com.atguigu.example;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;

public class Server4 {
    private static ArrayList<Socket> online = new ArrayList<Socket>();

    public static void main(String[] args) throws IOException {
        //1开启服务器
        ServerSocket server = new ServerSocket(9999);

        while (true){
            //2、接收客户端连接
            Socket socket = server.accept();

            //客户端加到online
            online.add(socket);

            //每个客户端单独一个线程
            MessageHandler mh = new MessageHandler(socket);
            mh.start();
        }
    }

    //信息处理类
    private static class MessageHandler extends Thread{
        private Socket socket;
        private String ip;

        //构造器
        public MessageHandler(Socket socket){
            super();
            this.socket = socket;
            this.ip = socket.getInetAddress().getHostAddress();
        }

        public void run(){
            sendToOthers(ip+"上线了");

            //(1)接收当前的客户端发送的消息
            try {
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in);
                BufferedReader br = new BufferedReader(isr);

                String content;
                //br可以看做生成的缓存文件,而readLine用于从中提取出内容
                while ((content = br.readLine()) != null){
                    //收到一句转发一句
                    sendToOthers(ip+"说:"+content);

                    if ("bye".equals(content)){
                        //给自己发一句bye
                        OutputStream out = socket.getOutputStream();
                        PrintStream ps = new PrintStream(out);
                        ps.println("bye");

                        break;
                    }
                }
                sendToOthers(ip+"下线了");
            } catch (IOException e) {
                sendToOthers(ip+"掉线了");
            }
        }

        public void sendToOthers(String str){
            //遍历所有的online客户端
            Iterator<Socket> iterator = online.iterator();
            //hasnext()方法阻塞等待用户输入,无参情况下永远返回true
            while(iterator.hasNext()){
                Socket on = iterator.next();
                try {
                    if(!on.equals(socket)){//!只给其它客户端发送
                        OutputStream out = on.getOutputStream();
                        PrintStream ps = new PrintStream(out);

                        ps.println(str);
                    }
                } catch (IOException e) {
                    //说明on这个客户可能下线或者掉线了
                    iterator.remove();
                }
            }
        }
    }
}
/*
Server的理解:
1、首先就得开服务器接口ServerSocket
2、之后创建Socket为ServerSocket的accept
3、socket的run()是我们的核心需求,可以对此创建一个专门的handler子类,继承Thread线程
4、在run里面需要的方法可以在类内构建,这里用的是输入流InputStream
5、InputStream(代表socket的输入流)→InputStreamReader(解码器,从字节流到字符流)→BufferedReader(看作缓存为文件)→br.readLine()(相当于读取文件中的内容)
6、如何发送内容给客户端?OutputStream→PrintStream(out)  (打印流,将out的内容存入ps) → ps.println(str) 发送内容
7、注意可能的错误!用try catch来避免输入流和输出流可能的报错
 */

客户端

package com.atguigu.example;

import java.io.*;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

public class Client4 {
    public static void main(String[] args) throws UnknownHostException, IOException {
        Socket socket = new Socket("127.0.0.1",9999);

        //开启双线程,一个接收,一个发送
        SendThread st = new SendThread(socket);
        ReceiveThread rt = new ReceiveThread(socket);

        st.start();
        rt.start();
        //join()方法是Thread类中的一个方法,该方法的定义是等待该线程终止。
        //其实就是join()方法将挂起调用线程的执行,直到被调用的对象完成它的执行。这段话难理解,后面我会用实例做讲解。

        try {
            st.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            rt.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        socket.close();

    }
    //发送线程
    static class SendThread extends Thread{
        private Socket socket;

        public SendThread(Socket socket){
            super();
            this.socket = socket;
        }
        public void run(){
            try {
                Scanner input = new Scanner(System.in);
                OutputStream out = socket.getOutputStream();//输出流,发送
                PrintStream ps = new PrintStream(out);//打印流
                while (true){
                    System.out.println("输入你想要输入的消息:");
                    String content = input.nextLine();
                    ps.println(content);
                    //输入bye结束发送
                    if("bye".equals(content)){
                        break;
                    }
                }
                input.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    //接收线程
    static class ReceiveThread extends Thread{
        private Socket socket;

        public ReceiveThread(Socket socket){
            super();
            this.socket = socket;
        }
        public void run(){
            try {
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in);
                BufferedReader br = new BufferedReader(isr);

                while (true){
                    String line = br.readLine();
                    if("bye".equals(line)){
                        break;
                    }
                    System.out.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
/*
Client理解:客户端的发送和接收更为重要
1、开始我们就创建接口Socket,host代表客户,而端口对应我们要交互的服务器
2、发送和接收创建两个单独的类,通过设置构造器连接Socket,功能核心在于类内的run()
 */