The reason why CLOSE_WAIT occurs is because of a design flaw in Linux's implementation of TCP. After all, none of us are the kind of people who have traveled through the years from the invention of TCP to its widespread use, and this kind of mistake is indeed possible to make.
Close and Shutdown
First Linux closes a socket with two system callsclose
cap (a poem)shutdown
. This is so because TCP is a two-way communication protocol consisting of two one-way channels.close
will close both channels, and theshutdown
There is an option to turn off either one, or both. Use a table to tell the difference.
functionality | shutdown | close |
---|---|---|
Closing method | Option to turn off read side, write side or both | Simultaneous shutdown of the read and write side |
Send FIN message | be | be |
Release of resources | No, until four waves are complete | Yes, release immediately |
transmitter buffer | If the write end is turned off, the data in the transmit buffer continues to be sent | If the shutdown function does not close the write side, the close function forces the write side to close, discarding the remaining data in the transmit buffer |
And TCP is shutting down the read end whenThere is no method to notify the other end of the socket., which means that the client closes the read side and calls theshutdown(sock, SHUT_RD)
When the kernel is used, it just marks a state in the kernel and doesn't tell the server "you can't send me data anymore".
Reproducing CLOSE_WAIT
Use the following code as the server side, and then use thenc localhost 12345
, and then to nc Ctrl+C to close it. At this pointnetstat -anp | grep 12345
, you will find at least one channel in the CLOSE_WAIT state.
#include <>
#include <>
#include <>
#include <arpa/>
#include <sys/>
#define PORT 12345
#define BUFFER_SIZE 1024
#include <>
void sigpipe_handler(int sig) {
printf("SIGPIPE received!\n");
}
int main() {
// Setting the signal processing function at the beginning of the program
signal(SIGPIPE, sigpipe_handler);
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
char buffer[BUFFER_SIZE];
// establish socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
// Binding address
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind");
close(server_fd);
exit(EXIT_FAILURE);
}
// monitor a connection
if (listen(server_fd, 1) < 0) {
perror("listen");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Server is listening on port %d\n", PORT);
// accept a connection
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_addr_len);
if (client_fd < 0) {
perror("accept");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Client connected, %d\n", client_fd);
// Reading client data(Stay connected)
while (1) {
ssize_t bytes_received = recv(client_fd, buffer, BUFFER_SIZE - 1, 0);
if (bytes_received < 0) {
perror("recv");
break;
}
sleep(1);
buffer[bytes_received] = '\0';
printf("Received: %ld, %s\n", bytes_received, buffer);
// send(client_fd, "pong", 4, 0);
}
// Close connection(analog (device, as opposed digital) CLOSE_WAIT state of affairs)
// take note of:non-closure socket,will result in the entry of CLOSE_WAIT state of affairs
// close(client_fd);
// cloture server socket
close(server_fd);
return 0;
}
Avoiding the CLOSE_WAIT problem
- Use of automatic shutdown mechanism for long periods of no communication
This scenario is also available in Redis. cluster communicate bus has this scenario.
- send + sigpipe + close, it works but I don't think anyone does it. It's fine as a toy for yourself.
A send is performed, which triggers a sigpipe, leaving the socket in an unavailable state, but you still have to manually close. this diagram is an example. You can see that you do remove two channels with this scheme, but the fd is still not closed.
- read + close
If read returns 0, that means you should close. (I guess no one will try to write 0 into the size parameter of read ......)
- Multiplexing + read + close
After the client closes, the server will trigger the readable event. At this point read this socket, will return 0, which means the server side should close.
- Non-blocking read + close
A non-blocking read returns no data when theEAGAIN/EWOULDBLOCK
, but if it returns 0, it's still time to close.
But with all the multiplexing, no one should be thinking of using non-blocking READ ...... Non-blocking WRITE is really necessary because blocking WRITE waits for at least one RTT, but READ doesn't.