《TIME_WAIT相关知识》里边有相关理论知识。
《TIME_WAIT状态TCP连接导致套接字无法重用实验》有相关实验。
现代Linux的TCP协议栈已经做了许多升级,所以可以让我们直接重用TIME_WAIT状态套接字而不会引起问题。下边是优化的内容:
1.新连接的SYN告知序列号比原来TIME_WAIT老连接末序列号要大,所以就可以通过序列号分辨出来新老连接。
2.开启tcp_timestamps,这样就让新连接时间戳比旧连接时间戳大,这样可以通过检查时间戳来判断新老连接。
在这样两重优化下,重用TIME_WAIT状态连接就不会产生任何问题了。
int on = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
SO_REUSEADDR用来告诉操作系统内核,如果端口已被占用,但是 TCP 连接状态位于 TIME_WAIT ,可以重用端口。这段代码需要放到socket函数和bind函数之间。
下边是timeWaitSetsockopt.c
完整代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<errno.h>
#include<syslog.h>
#include<signal.h>
void error_handling(char *buf);
#define MAXLINE 1024
static int count;
static void sig_int(int signo) {
printf("\nreceived %d datagrams\n", count);
exit(0);
}
int main(int argc, char **argv) {
if (argc != 2) {
error_handling("usage: select01 <IPaddress> or <Port>");
}
int serv_sock = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
server_addr.sin_port=htons(atoi(argv[1]));
// SO_REUSEADDR用来告诉操作系统内核,如果端口已被占用,但是 TCP 连接状态位于 TIME_WAIT ,可以重用端口
int on = 1;
setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if(bind(serv_sock, (struct sockaddr*) &server_addr, sizeof(server_addr))==-1){
fprintf(stderr, "error in bind: %s (%d)\n", strerror(errno), errno);
exit(errno);
}
if(listen(serv_sock, 5)==-1){
fprintf(stderr, "error in listen: %s (%d)\n", strerror(errno), errno);
exit(errno);
}
signal(SIGPIPE, sig_int);
int connfd;
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
if ((connfd = accept(serv_sock, (struct sockaddr *) &client_addr, &client_len)) < 0) {
printf("accept failed");
exit(0);
}
char message[MAXLINE];
count = 0;
int n;
for (;;) {
int n = read(connfd, message, MAXLINE);
if (n < 0) {
fprintf(stderr, "error in read: %s (%d)\n", strerror(errno), errno);
exit(0);
} else if (n == 0) {
fprintf(stderr, "client closed: %s (%d)\n", strerror(0), 0);
exit(0);
}
message[n] = 0;
printf("received %d bytes: %s\n", n, message);
count++;
}
}
void error_handling(char *buf)
{
fputs(buf, stderr);
fputc('\n', stderr);
exit(1);
}
在服务器端sudo gcc timeWaitSetsockopt.c -o timeWaitSetsockopt
进行编译,sudo ./timeWaitSetsockopt 8080
启动程序。
在客户端使用sudo telnet 127.0.0.1 8080
进行连接,然后输入good
之后按下回车键。
然后在服务器端同时按住ctrl+c关闭程序,之后快速再使用sudo ./timeWaitSetsockopt 8080
启动程序,然后快速在客户端使用sudo telnet 127.0.0.1 8080
进行连接,输入network
之后按下回车键,还是需要在服务器端快速按下sudo netstat -altnp | grep 8080
看一下连接状态,这些动作只要够快就可以出现两条记录。