In most cases, TCP connections are closed in one direction while the other direction is still available. When the client initiates a connection interruption, the client stops writing data to the server, and the server may be processing the last message from the client. When the processing is complete, the server sends the result to the client through the socket, which is said to be “half-closed”.

Closing the connection mode

Close function (close in both directions)

Function prototype:

 int close(int sockfd)
Copy the code

This function performs close on the connected socket and returns 0 on success or -1 on failure.

The close function counts the socket’s reference count to -1. Once the socket’s reference count is reduced to 0, it releases the socket completely and closes TCP traffic in both directions.

Socket reference count: Since a socket can be shared by multiple processes, such as forking, the socket reference count is +1, and the socket reference count is -1 if close is called once.

In order to close the data flow in both directions, the system kernel makes the socket unreadable in the data receiving direction, and any read operation will return an exception. In the data sending direction, the system kernel tries to send the data in the send buffer to the peer end, and finally sends a FIN packet to the peer end. If the socket is written again, an exception is returned.

If the peer end continues to send packets without detecting that the socket is closed, the peer end receives an RST packet. If you write to the socket that has received an RST, the kernel sends a SIGPIPE signal to the process. The default behavior of this signal is to terminate the process.

Shutdown function

Function prototype:

 int shutdown(int sockfd, int howto)
Copy the code

After the shutdown operation is executed on the connected socket, 0 is displayed if the operation succeeds, and -1 is displayed if the operation fails.

Howto is the setting option, and there are three main options:

  1. SHUT_RD(0) : Closes the “read” direction of the connection. Read operations on the socket directly return EOF. From the perspective of data, the existing data in the receive buffer on the socket will be discarded. That is, the peer will still receive an ACK, but in this case will never know that the data has been discarded.
  2. SHUT_WR(1) : Closes the “write” direction of the connection. In this case, the connection is “half closed” and the write direction of the connection is closed regardless of the socket reference count. The existing data in the send buffer on the socket is immediately sent out and a FIN packet is sent to the peer end. The application will report an error if it writes to the socket. If the socket is shared by another process, it will also be affected. If the socket is shared by another process, it will also be affected.
  3. SHUT_RDWR(2) : closes the read and write directions of the socket, which is equivalent to SHUT_RD and SHUT_WR operations respectively.

Difference between Close and shutdown

  1. Close closes the connection and releases all resources associated with the connection, whereas shutdown does not release the socket and all resources. Specifically, close is used to close a socket, purging the socket descriptor (or handle) from memory, and the socket can no longer be used. After an application closes a socket, the connections and caches associated with that socket become meaningless, and the TCP protocol automatically triggers the closing of the connection.

    Shutdown () is used to close the connection, not the socket, and no matter how many times shutdown() is called, the socket remains until close is called to clean the socket from memory. (Close is still needed to close the socket after shutdown is called)

    When close is called to close the socket, or shutdown is called to close the output stream, a FIN packet is sent to the other party. The FIN packet indicates that the data transfer is complete. The computer receives the FIN packet and knows that no more data will be transmitted.

  2. Close does not necessarily make the socket unavailable because of reference counts. Shutdown does not make the socket unavailable because of reference counts. If another process attempts to use the socket, it is affected.

  3. The close reference count does not necessarily send a FIN end message, whereas shutdown always sends a FIN end message, which is important when we want to close the connection to notify the peer

Note: If the client sends the EOF identifier, the server needs to read all the previous valid data before the EOF identifier is read.

Why can I directly call exit(0) to send FIN packets? Why not call close or shutdown?

After calling exit(0), the process exits, and all resources, files, memory, signals, and other kernel-allocated resources associated with the process are released. In Linux, everything is a file, and the socket itself is a file type. The kernel creates a file structure for each open file and maintains a reference count to that structure. Each process structure maintains an array of files opened by the process. Close removes the specified fd entry from the array of files opened by the process, and subtracts the reference count in the file structure to zero. When the reference count reaches zero, the contained file operation close is called.

What happens after you call close?

Scenario: The server actively closes a TCP connection by close(), and the client obtains 0 by read() (the return value of read is 0, indicating that the peer sent a FIN packet), and calls close() to close the connection.

At the TCP level: After the server calls close(), it sends the FIN to the client, and the client responds with fin-ack. The server enters the FIN-WaIT-2 state and the client enters the close-wait state. After calling close(), the client sends a FIN to the server, which uses fin-ack. The server enters the time-wait state, and the client enters the CLOSE state.

If the client continues to write to the server after receiving read()==0read()==0read()==0, the kernel will send a SIGPIPE signal to the process if it continues to write to the socket. The default behavior of this signal is to terminate the process.

If the client does not call close() when it gets read()==0read()==0read()==0, for example, when read()==0read()==0read()== 0read()==0, the client blocks and waits for some time, If you send SIGINT to exit the process (equivalent to calling close(), which is equivalent to not calling close in time), the following happens:

  • When the server SOCKET is in fin-WaIT-2 state, the client sends a SIGINT signal to exit. The client sends a FIN message, and the server replies with a FIN-ACK message. At this point, end the link as normal.
  • After the server SOCKET waits for fin-WaIT-2 timeout, the client sends a FIN message and the server replies with RST to terminate the connection.

Analysis: After the server SOCKET is closed, there is no reference to the SOCKET. The SOCKET entered the orphan SOCKET state. If an orphan Socket exists, the system protocol stack completes subsequent FIN processes. If the orphan Socket times out, the system protocol stack does not contain information about the Socket. The client sends the FIN and receives an RST reply.

Orphan SOCKET: From the application’s point of view, this SOCKET connection has finished sending and receiving data, and the connection is closed, but the Linux kernel maintains these SOCKET states in the TCP layer for normal TCP (such as buffer data) conversion, until the system recycle. Sockets in this state are orphan sockets.