聊天室

实现简单聊天室
能够实现简单功能
1 查看在线人名单 2 私聊 3 群发 4 退出
客户端代码

public class Client {

    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("localhost", 5000);
        System.out.println("已连接服务器");
        //建立连接
        System.out.println("请输入一个昵称");
        Scanner input = new Scanner(System.in);
        String s = input.next();
        OutputStream out = socket.getOutputStream();
        InputStream in = socket.getInputStream();
        out.write(s.getBytes());
        //发送请求
        new Thread(()->{
            while (true){
                try {
                    byte[] bt = new byte[1024*8];
                    int l= 0;
                    while(true){
                        l =in.read(bt);
                        if(l==-1) break;
                        System.out.println(new String(bt, 0, l));
                    }

                } catch (IOException e) {
                    e.printStackTrace();
                }


            }
        }).start();

        //输出接收的消息
        new Thread(()->{
            String s1;
            System.out.println("请选择功能:");
            System.out.println("1.查看在线人名单");
            System.out.println("2.私聊(2 人名 信息)");
            System.out.println("3.群发(3 群发信息)");
            System.out.println("4.退出");
            while(true){

                try {
                    s1= input.next();
                    out.write(s1.getBytes());
                switch (s1) {
                    case "2":
                        s1=input.next();
                        out.write(s1.getBytes());
                        s1=input.next();
                        out.write(s1.getBytes());
                       break;
                    case "3":
                        s1=input.next();
                        out.write(s1.getBytes());break;
                    case "4":System.exit(0);
               }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

客户端需要两个线程同时工作,一个线程从控制台读取数据,一个线程将从服务器返回的数据显示在控制台上。

服务器代码

public class task implements Runnable {

    private Socket socket;
    final static ConcurrentHashMap<Socket, String> map = new ConcurrentHashMap<>();
    public task(Socket socket) {
        this.socket=socket;
    }
    @Override
    public void run() {
        try {
            Login(socket);
            handle(socket);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private static void handle(Socket socket) throws IOException {
        InputStream in = socket.getInputStream();
        OutputStream out = socket.getOutputStream();
        byte[] bt = new byte[1024*8];
        int l= 0;
        Set<Socket> sockets=  map.keySet();
        Socket sockett = null;
        String s1,s2;
        //读取请求

        while (true){
            l = in.read(bt);
            String s =new String(bt,0,l);
            switch (s){
                case "1":
                    //响应名单
                    Collection<String> list = map.values();
                    out.write(list.toString().getBytes());break;
                case "2"://读取目的用户
                    l = in.read(bt);
                    s1 =new String(bt,0,l);
                    //寻找目的用户的socket
                    for (Socket socket1 : sockets) {
                        if(map.get(socket1).equals(s1)){
                            sockett=socket1;
                            break;
                        }
                    }
                    //读取内容
                    l = in.read(bt);
                    s2 =new String(bt,0,l);
                    //响应输出
                    OutputStream out1 = sockett.getOutputStream();
                    out1.write((map.get(socket)+":"+s2).getBytes());break;

                case "3": l = in.read(bt);
                    s2 =new String(bt,0,l);
                    for (Socket socket1 : sockets) {
                        OutputStream out2 = socket1.getOutputStream();
                        out2.write((map.get(socket)+":"+s2).getBytes());
                    }break;
                case "4":
                    String s3=map.get(socket);
                            map.remove(socket);
                    System.out.println(s3+"离开了");
                    Thread thread =Thread.currentThread();
                    thread.stop();
            }

        }

    }

    //建立连接
    private static void Login(Socket socket) throws IOException {

        try {
            InputStream in = socket.getInputStream();
            OutputStream out = socket.getOutputStream();
            byte[] bt = new byte[1024*8];
            int l=in.read(bt);
            String s =new String(bt,0,l);
            System.out.println(s+"已连接");
            map.put(socket,s);
            s="欢迎您"+s;
            out.write(s.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class Server {

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(5000);
        System.out.println("服务已启动,等待连接");
        ExecutorService service = new ThreadPoolExecutor(10, 10, 0, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>());
        while(true) {
            // 2. 调用accept 等待客户端连接
            Socket socket = serverSocket.accept();
            service.submit(new task(socket));
        }
    }
}

在实现过程中,每次先给服务器发送请求代号。如果是1则服务器之间返回Map信息,如果是2则服务器再接受两条信息,分别是发送对象和信息内容,如果是3则服务器再接受信息内容。

一般客户端-服务器编程应该使用同一的协议,而且客户端与服务器的接受和发送内容很相似,可以抽象为一个父类,只是接受信息后的处理不同。而子类只需要完成接受后的处理操作就好。
协议:
先发送一个字节的请求代号,然后发送两个字节的消息长度,最后发送消息。

抽象父类

public abstract class AbstractCS {
		//接受消息
    protected void receive(Socket socket, InputStream in, OutputStream out) throws IOException {
        while (true) {
            int cmd = in.read();
            if (cmd == -1) {
                break;
            }
            int hi = in.read();
            int lo = in.read();
            int length = (hi << 8) + lo;
            byte[] content = new byte[length];
            in.read(content);
            String str = new String(content, "utf-8");
            handle(socket, out, cmd, str);
        }
    }
		//发送消息
    protected void send(OutputStream out, int cmd, String content) throws IOException {
        out.write(cmd);
        byte[] bytes = content.getBytes("utf-8");
        int length = bytes.length;
        out.write(0xFF & length >> 8);
        out.write(0xFF & length);
        out.write(bytes);
    }
		//自定义接受消息后的处理
    protected abstract void handle(Socket socket, OutputStream out, int cmd, String str) throws IOException;
}

客户端

public class Client extends AbstractCS {

    public static void main(String[] args) throws IOException {
        Client client = new Client();
        client.start();

    }

    public void start() throws IOException {
        Socket socket = new Socket("localhost", 5000);

        // 第一个线程负责从控制台获取输入
        new Thread(() -> {
            try (
                    Scanner scanner = new Scanner(System.in);
                    OutputStream out = socket.getOutputStream();
            ) {
                System.out.println("请输入昵称:");
                Scanner s = new Scanner(System.in);
                String name = s.nextLine();
                send(socket.getOutputStream(),1,name);
                input(scanner, out);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();

        // 第二个线程负责接收服务器端的输入
        new Thread(() -> {
            try (
                    InputStream in = socket.getInputStream();
                    OutputStream out = socket.getOutputStream();
            ) {
                receive(socket, in, out);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }

    @Override
    protected void handle(Socket socket, OutputStream out, int cmd, String content) throws IOException {
        switch (cmd){
            case 5:
                System.out.println(content);break;
            case 6:
                System.out.println(content);break;
            case 7:
                System.out.println(content);break;
            case 8:
                System.out.println(content);break;

        }
    }


    protected void input(Scanner scanner, OutputStream out) {
        try {
            while (scanner.hasNextLine()) {
                String str = scanner.nextLine();
                char cmd = str.charAt(0);
                // 发送消息
                // 2 获取用户
                // 3 内容 群聊
                // 4 内容 对方名字 私聊
                // 对命令进行分析
                switch (cmd) {
                    case '2':
                        send(out, 2, "");
                        break;
                    case '3':
                        String content = str.substring(2);
                        send(out, 3, content);
                        break;
                    case '4':
                        String content1 = str.substring(2);
                        send(out, 4, content1);
                        break;
                    default:
                        System.out.println("不支持的命令");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

服务器

public class Server extends AbstractCS {
    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

    public void start() {
        System.out.println("==== 【畅聊】聊天室,版本 v2");
        ExecutorService service = Executors.newFixedThreadPool(10);
        try (ServerSocket serverSocket = new ServerSocket(5000)) {
            while (true) {
                // 来一个连接,开一个空闲线程处理请求,但上限是10
                Socket socket = serverSocket.accept();
                service.submit(() -> {
                    try (
                            InputStream in = socket.getInputStream();
                            OutputStream out = socket.getOutputStream();
                    ) {
                        this.receive(socket, in, out);
                    }
                    catch (SocketException ex){
                        map.remove(socket);
                    }
                    catch (IOException e) {
                        e.printStackTrace();
                    }
                });
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private Map<Socket, String> map = new ConcurrentHashMap<>();
    @Override
    protected void handle(Socket socket, OutputStream out, int cmd, String content) throws IOException {
        switch (cmd) {
            case 1:
                map.put(socket, content);
                send(out,5, "欢迎[" + content + "]来到聊天室");
                break;
            case 2:
                send(out,6, map.values().toString());
                break;
            case 3:
                String nick = map.get(socket);
                String c = nick+": "+content;
                map.forEach((s, value)->{
                    try {
                        send(s.getOutputStream(),7,c);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
                break;
            case 4:
                String[] array = content.split(" ");
                String n = array[0]; // 对方的名字
                String c2 = array[1]; // 内容
                boolean found = false;
                for (Map.Entry<Socket, String> entry :map.entrySet()) {
                    // 找到了
                    if(entry.getValue().equals(n)) {
                        send(entry.getKey().getOutputStream(),8, map.get(socket)+":"+c2);
                        found = true;
                        break;
                    }
                }
                if(!found) {
                    send(out,8, "聊天室没有这个人");
                }
                break;

        }
    }
}