聊天室这么简单你还学不会?

145 阅读4分钟

1. Socket类是干嘛的

Socket类是用于建立客户端与服务端之间双向通信的类。

2. 为什么需要Socket类

在没有使用socket模式(双向通信模式)下,通信方式为http模式(请求-响应模式),即客户端发送请求,服务端响应数据。服务端并不能主动向客户端发送信息,客户端要想与服务端建立双向通信,只能每隔一段时间向服务端发起请求。但是这种方法会带来延时高、服务端资源消耗大等问题,因此socket模式更适合聊天室场景。

3. Socket()和ServerSocket()

你是李白,我是杜甫,有一天你饮酒诗意起,想要和我交流一下你刚写的诗。于是你拿起电话给我发了一句“劝君更尽一杯酒, 西出阳关无故人。”,可是怎么也发不出去,微信提示着网络无法连接让你为了难,于是你拼命的爬上了有基站的山顶才将信息发了出去。因此看出要想实现客户端(李白、杜甫)之间通信,服务端(基站)必须作为中转站转发消息。

屏幕截图 2024-07-21 162411.png 现在,聪明的你一定想到了Socket()和ServerSocket()是用来干嘛的了。
Socket()为客户端构造方法。

Socket client = new Socket("127.0.0.1",1024)//参数:IP地址,服务端端口号

ServerSocket()为服务端构造方法。

ServerSocket server = new ServerSocket(1024)//参数:服务端端口号

屏幕截图 2024-07-21 163758.png

4. 编写Socket服务端类

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

public class SocketServer {
    public static void main(String[] args) {
        ServerSocket server = null;
        try {
            //实例化服务端,端口号为1024
            server = new ServerSocket(1024);
            System.out.println("服务已启动,等待客户端连接");
            //循环用于接收多个客户端连接
            while (true) {
                //接收客户端的连接,并将预制连接的客户端信息存入该Socket实例
                Socket socket = server.accept();
                String ip = socket.getInetAddress().getHostAddress();
                System.out.println("有客户端连接ip:" + ip + "端口:" + socket.getPort());
                //开启新线程接收客户端发送的信息
                 new Thread(()-> {
                        while (true) {
                            InputStream inputStream = null;
                            try {
                                //获取关于客户端的输入流
                                inputStream = socket.getInputStream();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }

                            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
                            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
                            String read = null;

                            try {
                                read = bufferedReader.readLine();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }

                            System.out.println("收到客户端消息->" + read);

                            //获取关于客户端的输出流,并向客户端输出消息
                            OutputStream outputStream = null;
                            try {
                                outputStream = socket.getOutputStream();
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                            PrintWriter printWriter = new PrintWriter(outputStream);
                            printWriter.println("我是服务端,我已收到消息:" + read);
                            printWriter.flush();
                        }
                  }).start();
             }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

5. 编写Socket客户端类

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

public class SocketClient {
    public static void main(String[] args) {
        Socket client = null;
        try {
            //实例化客户端,与ip地址为127.0.0.1即本机地址(localhost),端口号为1024的服务端连接
            client = new Socket("127.0.0.1", 1024);
            System.out.println("客户端初始化,并连接上服务端");
            //获取与服务端之间的输出流
            OutputStream outputStream = client.getOutputStream();
            PrintWriter printStream = new PrintWriter(outputStream);
            //开启客户端输出信息线程,并输出信息
            new Thread(()->{
                while (true) {
                    System.out.println("请输入内容:");
                    Scanner scanner = new Scanner(System.in);
                    String input = scanner.nextLine();

                    printStream.println(input);
                    printStream.flush();
                }
            }).start();
            //获取与服务端之间的输入流
            InputStream inputStream = client.getInputStream();
            //开启客户端输入信息线程,并接收信息
            new Thread(()-> {
                while (true) {
                    InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
                    BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
                    String read = null;
                    try {
                        read = bufferedReader.readLine();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println("收到服务端消息->" + read);
                }
            }).start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

输入内容测试完毕

屏幕截图 2024-07-22 135950.png 屏幕截图 2024-07-22 135940.png

6. 客户端之间的信息发送

在上面的步骤中,我们完成了客户端与服务端之间的消息发送,但是多个客户端之间群聊应该怎么实现呢?聪明的你一定想的了之前讲的李白和杜甫的故事。我们可以利用服务端作为中转站,将客户端A发送给服务端的消息转发给客户端B、客户端C......
具体实现: 我们在服务端维护一个HashMap存放客户端信息,每次做消息转发时就遍历HashMap向每个客户端发出消息。

屏幕截图 2024-07-22 141924.png

7. 服务端代码

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

public class SocketServer {
    public static void main(String[] args) {
        ServerSocket server = null;
        
        //新增->维护一个HashMap
        Map<String,Socket> CLENT_MAP = new HashMap<>();
        
        try {
            //实例化服务端,端口号为1024
            server = new ServerSocket(1024);
            System.out.println("服务已启动,等待客户端连接");
            //循环用于接收多个客户端连接
            while (true) {
                //接收客户端的连接,并将与之连接的客户端信息存入该Socket实例
                Socket socket = server.accept();
                String ip = socket.getInetAddress().getHostAddress();
                System.out.println("有客户端连接ip:" + ip + "端口:" + socket.getPort());
                
                //新增->将连接的客户端信息存入HashMap
                String clintKey = ip + socket.getPort();
                CLENT_MAP.put(clintKey,socket);
                
                //开启新线程接收客户端发送的信息
                 new Thread(()-> {
                        while (true) {
                            InputStream inputStream = null;
                            try {
                                //获取关于客户端的输入流
                                inputStream = socket.getInputStream();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }

                            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
                            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
                            String read = null;

                            try {
                                read = bufferedReader.readLine();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }

                            System.out.println("收到客户端消息->" + read);
                            
                            //新增->进行消息转发
                            String finalRead = read;
                            CLENT_MAP.forEach( (k, v)->{
                                try {
                                    OutputStream outputStream = v.getOutputStream();
                                    PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(outputStream));
                                    printWriter.println(socket.getPort() + "->" + finalRead);
                                    printWriter.flush();
                                }catch (IOException e) {
                                    e.printStackTrace();
                                }
                            });

                            //获取关于客户端的输出流,并向客户端输出消息
                            OutputStream outputStream = null;
                            try {
                                outputStream = socket.getOutputStream();
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                            PrintWriter printWriter = new PrintWriter(outputStream);
                            printWriter.println("我是服务端,我已收到消息:" + read);
                            printWriter.flush();
                        }
                  }).start();
             }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

8. 测试

  1. 复制粘贴SocketClient类并重命名
    屏幕截图 2024-07-22 143704.png
  2. 先启动SocketServer类,后启动另外3个SocketClient类。此时60288端口为SocketClientA的端口号,依此类推 屏幕截图 2024-07-22 145030.png
  3. 在SocketClientA下输入:“你们好呀!!!我是A”。同时另外2个SocketClient就收到消息了 屏幕截图 2024-07-22 145203.png 屏幕截图 2024-07-22 145214.png 屏幕截图 2024-07-22 145224.png