Java multithreading to realize TCP network socket programming (C/S communication)

table of Contents

Must know at the beginning

1. Multithreading technology

Second, realize multi-threaded reception

1. Single-threaded version

 2. Multi-threaded version

Three, the relationship between multithreading and process

Fourth, the complete code of the client interface

Five, multi-threaded communication comparison

At last


Must know at the beginning

In the previous article " Network Socket Programming Based on TCP Protocol (Java Implementation of C/S Communication) ", there is actually a problem. If the server sends multiple messages to the client after establishing a connection, the client cannot receive all of them. The reason This is because the client is single-threaded and only accepts the first message, and the remaining messages are blocked waiting for the next transmission. Therefore, this causes the client to be unable to process the message queue, and only receives and outputs one piece of server information at a time, resulting in a problem of information out of synchronization.

This article will solve this problem and record in detail the realization of java multi-threaded communication. The goal is to enable the client to receive multiple messages sent by the server at one time and avoid blocking. The method is to separate the client's receiving information function into a thread to complete, further improve the TCP Socket network communication, the programming of the C/S software architecture!

Java implementation of socket network programming series articles:

  1. Network Socket programming based on UDP protocol (C/S communication case in java) [ 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 ]

1. Multithreading technology

First, understand multi-threading technology. The following figure shows the sequential execution of program calls (left: single thread) and parallel execution of thread calls (right: multithreaded)

Multithreaded program is the method of parallel execution on the right.

In the TCPClientFX.java program in the previous article, sending information can be actively controlled through the "send" button, but the receiving information is passive, and only one piece of information can be received. It is uncontrollable how much information is in the input stream. To this end, we use multi-threading technology and add a thread specifically responsible for reading the information in the input stream.

In Java, there are mainly two ways to achieve multithreading, one is to use the Thread class, and the other is to use the Runnable class and implement the run() method . Below I use the Thread class to achieve, the anonymous inner class uses lambda writing, which is more concise.

readThread = new Thread(()->{
    //匿名内部类函数体

});

Second, realize multi-threaded reception

1. Single-threaded version

First of all, compared with the previous single-threaded communication, the following code can only allow the client to receive a single message from the server. If the server sends multiple messages continuously, it cannot receive all of them correctly. After the connection is successful, only one message from the server can be received.

        //连接按钮
        btConn.setOnAction(event -> {
            String ip=ipAddress.getText().trim();
            String port=tfport.getText().trim();

            try {
                //tcpClient是本程序定义的一个TCPClient类型的成员变量
                tcpClient = new TCPClient(ip, port);
                //成功连接服务器,接受服务器发来的第一条欢迎信息
                String firstMsg=tcpClient.receive();
                taDisplay.appendText(firstMsg+"\n");
                //连接服务器之后未结束服务前禁用再次连接
                btConn.setDisable(true);
                //重新连接服务器时启用输入发送功能
                tfSend.setDisable(false);
                btnSend.setDisable(false);
            }catch (Exception e){
                taDisplay.appendText("服务器连接失败!"+e.getMessage()+"\n");
            }
        });

 Similarly, in the send button, you can only "send one, receive one" each time, as shown in the following code:

        //发送按钮事件
        btnSend.setOnAction(event -> {
            String msg=tfSend.getText();
            tcpClient.send(msg);//向服务器发送一串字符
            taDisplay.appendText("客户端发送:"+msg+"\n");
            String receiveMsg=tcpClient.receive();//从服务器接收一串字符
            taDisplay.appendText(receiveMsg+"\n");
            if (msg.equals("bye")){
                btnSend.setDisable(true);//发送bye后禁用发送按钮
                tfSend.setDisable(true);//禁用Enter发送信息输入区域
                //结束服务后再次启用连接按钮
                btConn.setDisable(false);
            }
            tfSend.clear();
        });

 2. Multi-threaded version

According to the previous analysis, the function received by the client only needs to be handed over to a thread for processing, and the purpose can be achieved without blocking the main process due to the receiving statement.

Therefore, it is better to put the thread in the connection button. After successfully connecting to the server, the program starts the thread at the right time. The code is modified as follows:

        //连接按钮
        btConn.setOnAction(event -> {
            String ip=ipAddress.getText().trim();
            String port=tfport.getText().trim();

            try {
                //tcpClient是本程序定义的一个TCPClient类型的成员变量
                tcpClient = new TCPClient(ip, port);
                //用于接收服务器信息的单独线程
                readThread = new Thread(()->{
                    String receiveMsg=null;//从服务器接收一串字符
                    while ((receiveMsg=tcpClient.receive())!=null){
                        //lambda表达式不能直接访问外部非final类型局部变量,需要定义一个临时变量
                        //若将receiveMsg定义为类成员变量,则无需临时变量
                        String msgTemp = receiveMsg;
                        Platform.runLater(()->{
                            now = LocalDateTime.now();
                            taDisplay.setStyle("-fx-text-fill:red");
                            taDisplay.appendText(dtf.format(now) +"\n");
//                            taDisplay.setStyle("-fx-text-fill:black");
                            taDisplay.appendText(msgTemp+"\n");
                        });
                    }
                    Platform.runLater(()->{
                        taDisplay.appendText("对话已关闭!\n");
                    });
                });

                readThread.start();
                //连接服务器之后未结束服务前禁用再次连接
                btConn.setDisable(true);
                //重新连接服务器时启用输入发送功能
                tfSend.setDisable(false);
                btnSend.setDisable(false);
            }catch (Exception e){
                taDisplay.appendText("服务器连接失败!"+e.getMessage()+"\n");
            }
        });

Three, the relationship between multithreading and process

To do this, the function of multi-thread communication has been completed. However, when you are constantly debugging, you will find that when you close the form, the terminal will have problems such as the Socket is closed, the input and output streams are not closed normally, and the thread is abnormal. The following error:

java.net.SocketException: Socket closed

Why is this? The reason is the relationship between multithreading and the program.

Since the program is multi-threaded, there are several thread processing problems. Then, when we manually close the form, some threads are in a waiting state, waiting for IO operations, not all threads are closed immediately when they are closed , so the solution is to set a short interval to ensure that all threads are closed .

Set the thread sleep time in the exit method to ensure that the exit button and close the window do not throw such exceptions.

    private void exit() throws InterruptedException {
        if (tcpClient!=null){
            tcpClient.send("bye");
            readThread.sleep(1000);//多线程等待,关闭窗口时还有线程等待IO,设置1s间隔保证所有线程已关闭
            tcpClient.close();
        }
        System.exit(0);
    }

Fourth, the complete code of the client interface

/*
 *  TCPClientThreadFX.java
 * Copyright (c) 2020-10-25
 * author : Charzous
 * All right reserved.
 */

import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;



public class TCPClientThreadFX extends Application {

    private Button btnExit=new Button("退出");
    private Button btnSend = new Button("发送");

    private TextField tfSend=new TextField();//输入信息区域

    private TextArea taDisplay=new TextArea();//显示区域
    private TextField ipAddress=new TextField();//填写ip地址
    private TextField tfport=new TextField();//填写端口
    private Button btConn=new Button("连接");
    private TCPClient tcpClient;
    private Thread readThread;

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        BorderPane mainPane=new BorderPane();

        //连接服务器区域
        HBox hBox1=new HBox();
        hBox1.setSpacing(10);
        hBox1.setPadding(new Insets(10,20,10,20));
        hBox1.setAlignment(Pos.CENTER);
        hBox1.getChildren().addAll(new Label("ip地址:"),ipAddress,new Label("端口:"),tfport,btConn);
        mainPane.setTop(hBox1);

        VBox vBox=new VBox();
        vBox.setSpacing(10);

        vBox.setPadding(new Insets(10,20,10,20));
        vBox.getChildren().addAll(new javafx.scene.control.Label("信息显示区"),taDisplay,new Label("信息输入区"),tfSend);

        VBox.setVgrow(taDisplay, Priority.ALWAYS);
        mainPane.setCenter(vBox);



        HBox hBox=new HBox();
        hBox.setSpacing(10);
        hBox.setPadding(new Insets(10,20,10,20));
        hBox.setAlignment(Pos.CENTER_RIGHT);
        hBox.getChildren().addAll(btnSend,btnExit);
        mainPane.setBottom(hBox);

        Scene scene =new Scene(mainPane,700,500);
        primaryStage.setScene(scene);
        primaryStage.show();
        //连接按钮
        btConn.setOnAction(event -> {
            String ip=ipAddress.getText().trim();
            String port=tfport.getText().trim();

            try {
                //tcpClient是本程序定义的一个TCPClient类型的成员变量
                tcpClient = new TCPClient(ip, port);
                //用于接收服务器信息的单独线程
                readThread = new Thread(()->{
                    String receiveMsg=null;//从服务器接收一串字符
                    while ((receiveMsg=tcpClient.receive())!=null){
                        //lambda表达式不能直接访问外部非final类型局部变量,需要定义一个临时变量
                        //若将receiveMsg定义为类成员变量,则无需临时变量
                        String msgTemp = receiveMsg;
                        Platform.runLater(()->{
                            taDisplay.appendText(msgTemp+"\n");
                        });
                    }
                    Platform.runLater(()->{
                        taDisplay.appendText("对话已关闭!\n");
                    });
                });

                readThread.start();
                //连接服务器之后未结束服务前禁用再次连接
                btConn.setDisable(true);
                //重新连接服务器时启用输入发送功能
                tfSend.setDisable(false);
                btnSend.setDisable(false);
            }catch (Exception e){
                taDisplay.appendText("服务器连接失败!"+e.getMessage()+"\n");
            }
        });

//        btConn.defaultButtonProperty();

        //发送按钮事件
        btnSend.setOnAction(event -> {
            String msg=tfSend.getText();
            tcpClient.send(msg);//向服务器发送一串字符
            taDisplay.appendText("客户端发送:"+msg+"\n");
            if (msg.equalsIgnoreCase("bye")){
                btnSend.setDisable(true);//发送bye后禁用发送按钮
                tfSend.setDisable(true);//禁用Enter发送信息输入区域
                //结束服务后再次启用连接按钮
                btConn.setDisable(false);
            }
            tfSend.clear();
        });
        //对输入区域绑定键盘事件
        tfSend.setOnKeyPressed(new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent event) {
               if(event.getCode()==KeyCode.ENTER){
                   String msg=tfSend.getText();
                   tcpClient.send(msg);//向服务器发送一串字符
                   taDisplay.appendText("客户端发送:"+msg+"\n");

                   if (msg.equalsIgnoreCase("bye")){
                       tfSend.setDisable(true);//禁用Enter发送信息输入区域
                       btnSend.setDisable(true);//发送bye后禁用发送按钮
                       //结束服务后再次启用连接按钮
                       btConn.setDisable(false);
                   }
                   tfSend.clear();
                }
            }
        });

        btnExit.setOnAction(event -> {
            try {
                exit();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        //窗体关闭响应的事件,点击右上角的×关闭,客户端也关闭
        primaryStage.setOnCloseRequest(event -> {
            try {
                exit();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }

    private void exit() throws InterruptedException {
        if (tcpClient!=null){
            tcpClient.send("bye");
            readThread.sleep(1000);//多线程等待,关闭窗口时还有线程等待IO,设置1s间隔保证所有线程已关闭
            tcpClient.close();
        }
        System.exit(0);
    }
}

 The program of the server and the client can follow the content of the previous article, and the code is no longer posted here. For details, see this article: Network socket programming based on TCP protocol (java realizes C/S communication)

Five, multi-threaded communication comparison

So what is the difference? Let's verify it now. Let's modify the code of the server to return the information, and add one to the server to continuously send repeated information.

pw.println("来自服务器消息:"+msg);
pw.println("来自服务器,重复消息:"+msg);

The effect of the previous single-threaded program is like this. When the server is also in the "one-transmit and one-receive" state, and the client is "one-transmit and one-receive", there is no problem :

The server adds one to use the server to continuously send repeated messages. The result becomes this, and there is an obvious error . You can analyze the execution of single-thread and multi-message by yourself:

After the client interface is multi-threaded, solve the problem!

At last

This article details the realization of java multi-threaded communication, the goal is to achieve that the client can receive multiple messages sent by the server at one time, avoiding blocking. Separate the client's information receiving function into a thread to complete, further improve TCP Socket network communication, and solve the problem of single thread, which is also a problem left by the previous blog.

What is the other problem? Stay here and think for 3 seconds!

……

……

……

That is: now only a single user can communicate with the server normally . If there are multiple users, such as opening multiple client interfaces to connect to the server, there is a big problem. The server only realizes the single-user function. Then, immediately think of it and draw inferences. Can the multi-user function be solved using the multi-threading technology of this article to the server side? To solve this problem, wait for the next update!

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 network programming series articles:

  1. Network Socket programming based on UDP protocol (C/S communication case in java) [ 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 ]

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

Guess you like

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