阿男的小窝

View the Project on GitHub

TCP Server与Client,应该由谁负责关闭连接?

TCP Server与Client,应该由谁负责关闭连接?其实两边都可以发起主动的关闭请求,但是涉及到具体的实现代码,会有不同。

下面是一个基础的TCPServer的代码1

import java.io.DataInputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class EchoServer {
    public static void main(String[] args) {
        // declaration section:
// declare a server socket and a client socket for the server
// declare an input and an output stream
        ServerSocket echoServer = null;
        String line;
        DataInputStream is;
        PrintStream os;
        Socket clientSocket = null;
// Try to open a server socket on port 9999
// Note that we can't choose a port less than 1023 if we are not
// privileged users (root)
        try {
            echoServer = new ServerSocket(9999);
        } catch (IOException e) {
            System.out.println(e);
        }
// Create a socket object from the ServerSocket to listen and accept
// connections.
// Open input and output streams
        try {
            clientSocket = echoServer.accept();
            is = new DataInputStream(clientSocket.getInputStream());
            os = new PrintStream(clientSocket.getOutputStream());
// As long as we receive data, echo that data back to the client.
            line = is.readLine();
            os.println(line);
            os.close();
            is.close();
        } catch (IOException e) {
            System.out.println(e);
        }
    }
}

上面是一个基础的EchoServer。注意这里:

            clientSocket = echoServer.accept();
            is = new DataInputStream(clientSocket.getInputStream());
            os = new PrintStream(clientSocket.getOutputStream());
// As long as we receive data, echo that data back to the client.
            line = is.readLine();
            os.println(line);
            os.close();
            is.close();

可以看到,这个EchoServer只accept一次客户连接,然后就主动关闭和客户端的连接,并退出了。我们可以执行起来这个server:

然后使用telnet进行请求:

此时可以看到EchoServer也已经关闭:

使用Wireshark查看协议包的流程:

注意到第53号packet,从端口号可以看出,是服务端主动向客户端发起的关闭连接请求。

如果我们从EchoServer里面去掉这两行:

os.close();
is.close();

然后把服务端的读取客户端逻辑代码调整如下:

while (true) {
    line = is.readLine();
    os.println(line);
}

可以看到上面的代码会让服务端进入一个无限循环,不断读入客户端的请求。当然这个Server不是多线程或异步式的,因此它只能接受一个连接请求。

调整好代码后重新启动服务器:

然后使用telnet进行请求:

可以看到服务端会持续echo客户端发来的数据,并且服务端不再主动退出连接。

此时我们可以让客户端主动关闭连接:

此时查看服务端:

可以看到服务端还在循环里没有退出,但此时已经不再能接受客户端新的请求了:

如果我们查看协议包,可以看到这次是客户端主动发起的关闭连接请求:

注意上面蓝色高亮现实的1386号packet。从端口号可以看出,是客户端主动发起的关闭请求。

实际上上面的这种场景就是”长连接”,但是为了让服务端能够妥善地退出循环,客户端需要与服务端约定一个协议,比如客户端发送一个命令数据给服务端,告知自己要关闭连接了,这样服务端好知道关掉连接。

这个话题展开讲会很大,包括服务端的多线程设计等等,在这篇文章就不展开了。