Java Socket模拟实现聊天室

时间:2022-01-08 16:19:22

使用Java Socket模拟实现了一个聊天室,实现了基本的私聊以及群聊。分为服务器端和客户端,下面我来介绍一下实现的步骤。

服务器端

服务器端是聊天室的核心所在,主要用来处理客户端的请求,先来看一下服务器端的主方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
        try {
            ExecutorService executorService = Executors.newFixedThreadPool(100);//最多容纳100个客户端聊天
            ServerSocket serverSocket = new ServerSocket(6655);//监听6655号端口
            for (int i = 0; i < 100; i++) {
                Socket client = serverSocket.accept();
                System.out.println("有新的用户连接 " + client.getInetAddress() +
                        client.getPort());
                executorService.execute(new ExecuteClientThread(client));
            }
            executorService.shutdown();
            serverSocket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

首先我创建了一个固定大小为100的线程池,这个聊天室的实现是一个服务器线程对应一个客户端线程的,就是说线程池的大小就是最大的同时聊天的人数。服务器的执行顺序是这样的:

1.监听端口,等待客户端连接

2.如果有客户端连接到监听的端口,那么通过accept()方法返回该客户端的Socket,并且在线程池中启动一个新的服务器线程用来与刚刚连接的客户端"沟通"。

3.把接收到的客户端的Socket构造注入新启动的服务器线程中,这样服务器线程就可以获取到客户端对应的流。

到这里,服务器已经和客户端连接成功了,我们现在来看一下服务器线程是如何处理客户端的请求的,先上一段服务器代码

?
1
2
3
4
5
6
7
8
private static Map<String, Socket> clientMap = new ConcurrentHashMap<>();//存储所有的用户信息
 
    static class ExecuteClientThread implements Runnable {
        private Socket client;//每一个服务器线程对应一个客户端线程
        ExecuteClientThread(Socket client) {
            this.client = client;
        }
......

代码的第一行,创建了一个ConcurrentHashmap,这个map不是某个线程中的,而是服务器的static属性,用来存储所有客户端的信息。因为客户端是有姓名,有Socket的,所以采用K-value的模式来存储,用户名作为Key。考虑到线程安全的原因,采用了ConcurrentHashmap,保证了线程安全。

接下来就是刚刚构造注入的、连接的客户端的Socket了,我们可以通过这个Socket获取到输入和输出流。

然后就是服务器的线程执行的run方法了,具体的就直接看代码把。都有注释,就不一一解释了,以下是所有服务器端的代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
 
 
public class Main {
 
 
    private static Map<String, Socket> clientMap = new ConcurrentHashMap<>();//存储所有的用户信息
 
    static class ExecuteClientThread implements Runnable {
        private Socket client;//每一个服务器线程对应一个客户端线程
        ExecuteClientThread(Socket client) {
            this.client = client;
        }
 
        @Override
        public void run() {
            boolean Flag = true;//防止一个客户端多次注册所做的标记位置
            try {
                PrintStream PrintToCilent = new PrintStream(client.getOutputStream());//服务器向用户输出一些提示信息
 
 
                Scanner scanner = new Scanner(client.getInputStream());
                String str = null;//用户外部的输入信息
                while (true) {
                    if (scanner.hasNext()) {
                        str = scanner.next();//外部的用户输出
 
                        Pattern pattern = Pattern.compile("\r");//排除特殊符号
                        Matcher matcher = pattern.matcher(str);
                        str = matcher.replaceAll("");
 
                        if (str.startsWith("userName")) {
                            String userName = str.split(":")[1];
                            userRegist(userName, client, Flag);
                            Flag = false;
                        }
                        // 群聊流程
                        else if (str.startsWith("G:")) {
                            PrintToCilent.println("已进入群聊模式!");
                            groupChat(scanner,client);
                        }
                        // 私聊流程
                        else if (str.startsWith("P")) {//模式
                            String userName = str.split("-")[1];
                            PrintToCilent.println("已经进入与"+userName+"的私聊");
 
                            privateChat(scanner,userName);
                        }
                        // 用户退出
                        else if (str.contains("byebye")) {
                            String userName = null;
                            for (String getKey:clientMap.keySet()) {
                                if (clientMap.get(getKey).equals(client)) {
                                    userName = getKey;
                                }
                            }
 
                            System.out.println("用户"+userName+"下线了..");
                            clientMap.remove(userName);//将此实例从map中移除
                        }
                    }
                }
 
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
       
        private void userRegist(String userName, Socket client, boolean Flag) throws IOException {
            PrintStream PrintToCilent = new PrintStream(client.getOutputStream());//服务器向用户输出一些提示信息
            if(Flag) {
                System.out.println("用户" + userName + "上线了!");
 
                clientMap.put(userName, client);//把用户加入储存map
                System.out.println("当前群聊人数为" + (clientMap.size()) + "人");
                PrintToCilent.println("注册成功!");
            }else {
                PrintToCilent.println("警告:一个客户端只能注册一个用户!");
            }
        }
     
        private void groupChat(Scanner scanner,Socket client) throws IOException {
            // 取出clientMap中所有客户端Socket,然后遍历一遍
            // 分别取得每个Socket的输出流向每个客户端输出
            PrintStream PrintToClient = new PrintStream(client.getOutputStream());//在群聊的时候服务器向客户端发送数据
            boolean ExitFlag = false;
 
            Set<Map.Entry<String, Socket>> entrySet =
                    clientMap.entrySet();
 
            String userName = null;
            for (Map.Entry<String, Socket> socketEntry : entrySet) {//获得:是哪个用户说的话
                if (socketEntry.getValue() == client) {
                    userName = socketEntry.getKey();//发出信息的用户
                }
            }
            String msg = null;
 
            while (true) {
                if (scanner.hasNext()) {
                    msg = scanner.next();
                    if("exit".equals(msg)){//如果用户退出了
                        for(Map.Entry<String,Socket> stringSocketEntry : entrySet){
                            new PrintStream(stringSocketEntry.getValue().getOutputStream(),true).println("用户"+userName+"刚刚退出了群聊!!");//给所有人发退出群聊的消息
                        }
                        return;
                    }
 
 
                    for (Map.Entry<String, Socket> stringSocketEntry : entrySet) {//遍历用户的map,获取所有用户的Socket
                        try {
                            Socket socket = stringSocketEntry.getValue();
                            PrintStream ps = new PrintStream(socket.getOutputStream(), true);
 
                            ps.println("群聊:用户" + userName + "说: " + msg);//给每个用户发消息
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
 
                }
            }
 
        }
        private void privateChat(Scanner scanner, String privatepeopleName) throws IOException {
 
            Socket privateUser = clientMap.get(privatepeopleName);
            PrintStream ps = new PrintStream(privateUser.getOutputStream());//拿到私聊对象的输出流
            PrintStream PrintToClient = new PrintStream(client.getOutputStream());//拿到当前客户端的输出流
            String Message = null;
            String MyName = null;
            Set<Map.Entry<String,Socket>> set = clientMap.entrySet();
            for(Map.Entry<String,Socket> value : set){
                if(value.getValue() == client){
                    MyName = value.getKey();
                    break;
                }
            }
 
            while (true) {
                if(scanner.hasNext()) {
                    Message = scanner.next();
                    if ("exit".equals(Message)){//如果用户输入了退出
                        PrintToClient.println("已退出和"+privatepeopleName+"的私聊");
                        ps.println("对方已经退出了私聊");
                        break;
                    }
                    ps.println(MyName+"说"+Message);//如果用户没有退出,向私聊对象发送消息
                }
            }
 
 
        }
 
 
    }
 
    public static void main(String[] args) {
        try {
            ExecutorService executorService = Executors.newFixedThreadPool(100);//最多容纳100个客户端聊天
            ServerSocket serverSocket = new ServerSocket(6655);
            for (int i = 0; i < 100; i++) {
                Socket client = serverSocket.accept();
                System.out.println("有新的用户连接 " + client.getInetAddress() +
                        client.getPort());
                executorService.execute(new ExecuteClientThread(client));
            }
            executorService.shutdown();
            serverSocket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

然后是客户端的代码,客户端的代码比较简单:分为两个线程,一个线程用于接收服务器的数据,一个线程用于向服务器发送数据。我就直接上代码了,里面有注释的。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
 
 
class ExcuteServerInPut implements Runnable{//接收服务器的数据
    private Socket ToServer;
 
    ExcuteServerInPut(Socket ToServer){
        this.ToServer = ToServer;
    }
 
    @Override
    public void run() {
        try {
            Scanner scanner = new Scanner(ToServer.getInputStream());
               while (scanner.hasNext()){
                System.out.println(scanner.nextLine());
            }
            scanner.close();
            ToServer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
 
class ExcuteServerOutPut implements Runnable{//向服务器发送数据
 
    private Socket Socket;
    ExcuteServerOutPut(Socket Socket){
        this.Socket = Socket;
    }
 
    @Override
    public void run() {
        try {
            PrintStream printStream = new PrintStream(Socket.getOutputStream());
            Scanner scanner = new Scanner(System.in);
            scanner.useDelimiter("\n");
            System.out.println("*****************************************");
            System.out.println("***用户注册:useerName:同户名(仅限一次)***");
            System.out.println("***进入群聊:G:           退出群聊:exit***");
            System.out.println("***私聊:P-用户名         退出私聊:exit***");
            System.out.println("***********退出聊天室:byebye*************");
            while (true){
                if(scanner.hasNext()) {
                    String string = scanner.next();
                    printStream.println(string);
                    if ("byebye".equals(string)) {
                        System.out.println("退出!");
                        printStream.close();
                        scanner.close();
                        break;
                    }
                }
 
            }
 
            Socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
 
 
public class Main {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 6655);
        ExcuteServerInPut excuteServerInPut = new ExcuteServerInPut(socket);
        ExcuteServerOutPut excuteServerOutPut = new ExcuteServerOutPut(socket);
        new Thread(excuteServerInPut).start();
        new Thread(excuteServerOutPut).start();
        }
}

后续我会做一些改进,希望可以对大家有所帮助

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/qq_38449518/article/details/80646608