Java advanced: TCP-based network real-time chat room (socket communication case)

table of Contents

Straight to the point

1. Data structure Map

Second, ensure thread safety

Three, the core method of group chat

Fourth, the specific design of the chat room

0, the user logs in to the server

1. View current online users

2. Group chat

3, personal communication

4. Exit the current chat state

5. Offline

6. View help

Five, the complete code of the chat room service

6. Effect demonstration: TCP-based network real-time chat room

Conclusion


Straight to the point

In the past month, I have recorded the knowledge and practical cases of learning Socket network programming. Relatively speaking, I have learned more systematically to realize network communication based on the TCP protocol, which is also the top priority in computer networks. TCP/IP belongs to the network layer, in java. , The work of this layer is encapsulated, and it is easier to realize communication in programming, regardless of the realization of the bottom layer. Of course, we need to be familiar with the five-layer protocol, experience the principles in practice, and have a deeper understanding.

Therefore, the series of articles started from the beginning, and continuously improved the Socket communication of the C/S architecture. Recall that firstly, the server and the client communicate with each other. In this process, problems are found, and then the multithreading technology is used to solve the real-time client receiving information. Later, when I came to the server side, I found the "first come, first served" problem of multi-user connecting to the server, and the "last comer" could not communicate normally, so the thread pool technology was used to solve the problem of multi-user server.

So far, a simple client-server application has been basically realized. Therefore, this article will use the client-server (C/S architecture) based on all the previous content, combined with multi-threading technology, to simulate chat functions similar to QQ and WeChat. Realize a network real-time chat room, the current functions include:

(1)L(list):查看当前上线用户;
(2)G(group):群聊;
(3)O(one-one):私信;
(4)E(exit):退出当前聊天状态;
(5)bye:离线;
(6)H(help):查看帮助

This article will record in detail the implementation steps of the network real-time chat room, and the following series of articles are the prerequisite.

Java implementation of socket communication network programming series of articles:

  1. Network Socket programming based on UDP protocol (java implementation of C/S communication case) [ https://blog.csdn.net/Charzous/article/details/109016215 ]
  2. Network socket programming based on TCP protocol (java realizes C/S communication) [ https://blog.csdn.net/Charzous/article/details/109016215 ]
  3. Java multithreading to realize TCP network Socket programming (C/S communication) [ https://blog.csdn.net/Charzous/article/details/109283697 ]
  4. Java multithreading realizes multi-user and server-side Socket communication [ https://blog.csdn.net/Charzous/article/details/109440277 ]

1. Data structure Map

The TCPClientThreadFX and TCPThreadServer in the first two articles implement multi-threaded communication, but they are only chats between the client and the server. How to achieve group chats? The idea is that the chat information of customer A is forwarded to all customers online at the same time through the server.

The specific method is to add the function of recording login customer information on the server side , and each user has its own identity. This article will use a simple "online method" to record customer sockets, that is, use collections to save user login socket information to track user connections.

Therefore, we need to choose a suitable data structure to save the user's Socket and user name information. What data structures are provided in Java?

Commonly used collection types in Java are: Map, List, and Set. Map is to store Key-Value pairs. List is similar to an array and can store repeatable values, while Set only stores non-repeated values, which is equivalent to a Map that only stores keys and does not store values.

If it is a login operation with a user name and student ID, it can be stored in a Map type collection. For example, you can use the key to record the user name + student ID, and the value to save the socket. For the needs of the network chat room in this article, Map is required to store the sockets and login names of different users. The user socket socket is a more convenient choice as a key to identify an online user, because the IP address + port combination of each client is different.

Second, ensure thread safety

Obviously, we need to use multi-threading technology. In a multi-threaded environment, there is a problem of thread concurrency safety in reading and writing shared resources. For example, HashMap, HaspSet, etc. are not thread-safe, and can be locked through the synchronized keyword. , But there is a more convenient solution: you can directly use the thread-safe collection provided by the java.util.concurrent package of the Java standard library. For example , the thread safety of HashMap is ConcurrentHashMap, and the thread safety of HashSet is CopyOnWriteArraySet .

As shown in the figure, the Map inheritance system:

 In JDK1.8, the HashMap has been improved. When the number of nodes exceeds, TREEIFY_THRESHOLD it will be converted to a red-black tree. This greatly optimizes the efficiency of the query, but it is still not thread-safe.

Here is a brief look, you can check relevant information for specific learning. With the above basic knowledge, let's start the concrete realization of the network real-time chat room.

Three, the core method of group chat

Based on the previous idea: the realization of group chat is that the chat information of customer A is forwarded to all customers online at the same time through the server, and the server side records the socket of the logged-in user according to the HashMap and forwards the information to all users.

The core group sending method sendToAllMembers is used to send information to all online customer service.

private void sendToMembers(String msg,String hostAddress,Socket mySocket) throws IOException{

    PrintWriter pw;
    OutputStream out;
    Iterator iterator=users.entrySet().iterator();
    while (iterator.hasNext()){
        Map.Entry entry=(Map.Entry) iterator.next();
        Socket tempSocket = (Socket) entry.getKey();
        String name = (String) entry.getValue();
        if (!tempSocket.equals(mySocket)){
            out=tempSocket.getOutputStream();
            pw=new PrintWriter(new OutputStreamWriter(out,"utf-8"),true);
            pw.println(hostAddress+":"+msg);
        }
    }

}

 Use Map traversal to send information to all other users.

The same principle, we realized the function of private chat, and transformed it into the realization of the idea, that is, the communication between the current user and the specified user Socket, so I wrote a sendToOne method.

private void sendToOne(String msg,String hostAddress,Socket another) throws IOException{

    PrintWriter pw;
    OutputStream out;

    Iterator iterator=users.entrySet().iterator();
    while (iterator.hasNext()){

        Map.Entry entry=(Map.Entry) iterator.next();
        Socket tempSocket = (Socket) entry.getKey();
        String name = (String) entry.getValue();

        if (tempSocket.equals(another)){
            out=tempSocket.getOutputStream();
            pw=new PrintWriter(new OutputStreamWriter(out,"utf-8"),true);
            pw.println(hostAddress+"私信了你:"+msg);
        }
    }
}

 The above two methods are the key to this network chat room, and the functions implemented later will be the flexible use of these two methods.

Fourth, the specific design of the chat room

The functional positioning of the current chat room includes: 1) view current online users; 2): group chat; 3) private messages; 4) exit the current chat status; 5) offline; 6) view help.

First, initialize the most critical data structure, as a class member variable, HashMap is used to store Socket and username:

private ConcurrentHashMap<Socket,String> users=new ConcurrentHashMap();

The specific implementation of each function is as follows:

0, the user logs in to the server

Here is the first server-side information processing. It is necessary to record the login information of each user, including the connected socket and custom nickname, to facilitate subsequent use. The method I use is to remind the user to enter the user name for further operations when the user connects to the server, and also realize the judgment logic of no duplicate name. code show as below:

pw.println("From 服务器:欢迎使用服务!");
pw.println("请输入用户名:");
String localName = null;
while ((hostName=br.readLine())!=null){
    users.forEach((k,v)->{
        if (v.equals(hostName))
            flag=true;//线程修改了全局变量
    });

    if (!flag){
        localName=hostName;
        users.put(socket,hostName);
        flag=false;
        break;
    }
    else{
        flag=false;
        pw.println("该用户名已存在,请修改!");
    }
}

 After successful login, all online users will be notified of the online information.

1. View current online users

In fact, the information recorded in the HashMap on the server side is returned to the requesting user, which can be viewed through the agreed command L:

if (msg.trim().equalsIgnoreCase("L")){
       users.forEach((k,v)->{
       pw.println("用户:"+v);
       });
       continue;
}

2. Group chat

else if (msg.trim().equals("G")){
    pw.println("您已进入群聊。");
    while ((msg=br.readLine())!=null){
        if (!msg.equals("E")&&users.size()!=1)
            sendToMembers(msg,localName,socket);
        else if (users.size()==1){
            pw.println("当前群聊无其他用户在线,已自动退出!");
            break;
        }
        else {
            pw.println("您已退出群组聊天室!");
            break;
        }
    }

}

3, personal communication

In the same way, the processing logic becomes one-to-one communication, which is similar to the previous server-client one-to-one communication, but more processing is needed here to ensure that the logic is correct, including the online status of the person being chatted privately, and the user being chatted privately. Is the name correct?

//一对一私聊
else if (msg.trim().equalsIgnoreCase("O")){
    pw.println("请输入私信人的用户名:");
    String name=br.readLine();

    //查找map中匹配的socket,与之建立通信
    //有待改进,处理输入的用户名不存在的情况
    users.forEach((k, v)->{
        if (v.equals(name)) {
            isExist=true;//全局变量与线程修改问题
        }

    });
    //已修复用户不存在的处理逻辑
    Socket temp=null;
    for(Map.Entry<Socket,String> mapEntry : users.entrySet()){
        if(mapEntry.getValue().equals(name))
            temp = mapEntry.getKey();
//                            System.out.println(mapEntry.getKey()+":"+mapEntry.getValue()+'\n');
    }
    if (isExist){
        isExist=false;
        //私信后有一方用户离开,另一方未知,仍然发信息而未收到回复,未处理这种情况
        while ((msg=br.readLine())!=null){
            if (!msg.equals("E")&&!isLeaved(temp))
                sendToOne(msg,localName,temp);
            else if (isLeaved(temp)){
                pw.println("对方已经离开,已断开连接!");
                break;
            }
            else{
                pw.println("您已退出私信模式!");
                break;
            }
        }
    }
    else
        pw.println("用户不存在!");
}

4. Exit the current chat state

This function is mainly integrated into group chats and private chats. It can be seen that the internal calls implemented by the above two functions define a method isLeaved to determine whether the user has been offline.

//判断用户是否已经下线
private Boolean isLeaved(Socket temp){
    Boolean leave=true;
    for(Map.Entry<Socket,String> mapEntry : users.entrySet()) {
        if (mapEntry.getKey().equals(temp))
            leave = false;
    }
    return leave;
}

5. Offline

This function is relatively simple and is executed through an agreed command.

if (msg.trim().equalsIgnoreCase("bye")) {
     pw.println("From 服务器:服务器已断开连接,结束服务!");

     users.remove(socket,localName);

     sendToMembers("我下线了",localName,socket);
     System.out.println("客户端离开。");//加当前用户名
     break;
}

6. View help

Requesting help from the server through the command H means that the user checks which commands correspond to the function to make a selection.

else if (msg.trim().equalsIgnoreCase("H")){
    pw.println("输入命令功能:(1)L(list):查看当前上线用户;(2)G(group):进入群聊;(3)O(one-one):私信;(4)E(exit):退出当前聊天状态;(5)bye:离线;(6)H(help):帮助");
    continue;//返回循环
}

Five, the complete code of the chat room service

The main work of the chat room implementation is on the server side, focusing on the internal class Hanler that is processed by the server thread. The above is the specific introduction of each function. The code is given below in full. Only on the basis of the previous article, see Java multithreading to achieve multiple users and services End Socket communication .

Modify the server-side thread processing code:

class Handler implements Runnable {
    private Socket socket;

    public Handler(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        //本地服务器控制台显示客户端连接的用户信息
        System.out.println("New connection accept:" + socket.getInetAddress().getHostAddress());
        try {
            BufferedReader br = getReader(socket);
            PrintWriter pw = getWriter(socket);

            pw.println("From 服务器:欢迎使用服务!");
            pw.println("请输入用户名:");
            String localName = null;
            while ((hostName=br.readLine())!=null){
                users.forEach((k,v)->{
                    if (v.equals(hostName))
                        flag=true;//线程修改了全局变量
                });

                if (!flag){
                    localName=hostName;
                    users.put(socket,hostName);
                    flag=false;//可能找出不一致问题
                    break;
                }
                else{
                    flag=false;
                    pw.println("该用户名已存在,请修改!");
                }
            }

//                System.out.println(hostName+": "+socket);
            sendToMembers("我已上线",localName,socket);
            pw.println("输入命令功能:(1)L(list):查看当前上线用户;(2)G(group):进入群聊;(3)O(one-one):私信;(4)E(exit):退出当前聊天状态;(5)bye:离线;(6)H(help):帮助");

            String msg = null;
            //用户连接服务器上线,进入聊天选择状态
            while ((msg = br.readLine()) != null) {
                if (msg.trim().equalsIgnoreCase("bye")) {
                    pw.println("From 服务器:服务器已断开连接,结束服务!");

                    users.remove(socket,localName);

                    sendToMembers("我下线了",localName,socket);
                    System.out.println("客户端离开。");//加当前用户名
                    break;
                }
                else if (msg.trim().equalsIgnoreCase("H")){
                    pw.println("输入命令功能:(1)L(list):查看当前上线用户;(2)G(group):进入群聊;(3)O(one-one):私信;(4)E(exit):退出当前聊天状态;(5)bye:离线;(6)H(help):帮助");
                    continue;//返回循环
                }
                else if (msg.trim().equalsIgnoreCase("L")){
                    users.forEach((k,v)->{
                        pw.println("用户:"+v);
                    });
                    continue;
                }
                //一对一私聊
                else if (msg.trim().equalsIgnoreCase("O")){
                    pw.println("请输入私信人的用户名:");
                    String name=br.readLine();

                    //查找map中匹配的socket,与之建立通信
                    users.forEach((k, v)->{
                        if (v.equals(name)) {
                            isExist=true;//全局变量与线程修改问题
                        }

                    });
                    //已修复用户不存在的处理逻辑
                    Socket temp=null;
                    for(Map.Entry<Socket,String> mapEntry : users.entrySet()){
                        if(mapEntry.getValue().equals(name))
                            temp = mapEntry.getKey();
                    }
                    if (isExist){
                        isExist=false;
                        //私信后有一方用户离开,另一方未知,仍然发信息而未收到回复,未处理这种情况
                        while ((msg=br.readLine())!=null){
                            if (!msg.equals("E")&&!isLeaved(temp))
                                sendToOne(msg,localName,temp);
                            else if (isLeaved(temp)){
                                pw.println("对方已经离开,已断开连接!");
                                break;
                            }
                            else{
                                pw.println("您已退出私信模式!");
                                break;
                            }
                        }
                    }
                    else
                        pw.println("用户不存在!");
                }
                //选择群聊
                else if (msg.trim().equals("G")){
                    pw.println("您已进入群聊。");
                    while ((msg=br.readLine())!=null){
                        if (!msg.equals("E")&&users.size()!=1)
                            sendToMembers(msg,localName,socket);
                        else if (users.size()==1){
                            pw.println("当前群聊无其他用户在线,已自动退出!");
                            break;
                        }
                        else {
                            pw.println("您已退出群组聊天室!");
                            break;
                        }
                    }

                }
                else
                    pw.println("请选择聊天状态!");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (socket != null)
                    socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

6. Effect demonstration: TCP-based network real-time chat room

First, open multiple clients, and the connection server starts to enter the communication state.

The following animation demonstrates several basic functions. You can see that three users have realized real-time communication and chat, including group chat and private chat. The other functions are left for everyone to explore.

Conclusion

The series of articles started from the beginning and continued to improve the Socket communication of the C/S architecture. Recall that firstly, the server and the client communicate with each other. During this process, problems are found, and then the multithreading technology is used to solve the problem of the client receiving information in real time. Later, when I came to the server side, I found the "first come, first served" problem of multi-user connecting to the server, and the "last comer" could not communicate normally, so the thread pool technology was used to solve the problem of multi-user server.

This article basically implements a simple client-server application, using client-server (C/S architecture), combined with multithreading technology, simulating chat functions similar to QQ and WeChat, and realizing a network real-time chat room.

The knowledge learned includes: multithreading, thread pool, Socket communication, TCP protocol, HashMap, JavaFX, etc., the combined application of all knowledge, and through practical exercises, understand the knowledge step by step!

If you think it’s good, welcome to "one-click, three-link", like, bookmark, follow, comment directly if you have any questions, and exchange and learn!

Java implementation of socket communication network programming series of articles:

  1. Network Socket programming based on UDP protocol (java implementation of C/S communication case) [ https://blog.csdn.net/Charzous/article/details/109016215 ]
  2. Network socket programming based on TCP protocol (java realizes C/S communication) [ https://blog.csdn.net/Charzous/article/details/109016215 ]
  3. Java multithreading to realize TCP network Socket programming (C/S communication) [ https://blog.csdn.net/Charzous/article/details/109283697 ]
  4. Java multithreading realizes multi-user and server-side Socket communication [ https://blog.csdn.net/Charzous/article/details/109440277 ]

My CSDN blog: https://blog.csdn.net/Charzous/article/details/109540279

Guess you like

Origin blog.csdn.net/Charzous/article/details/109540279