socket中的reuse addr和reuse port

Time Wait

SO_REUSEADDR

(1)首先对于一个server服务进程来说,它的创建流程是

socket->bind ->listen->accept

创建监听套接字,bind一个指定端口,listen监听端口,为每个连接提供服务(一般会创建子进程)。当服务进程重启时,那么会重新执行一遍上面的流程,如果在创建socket后没有使用SO_REUSEADDR选项进行设置,再次进行bind就会失败。那么原因是什么?
首先需要了解一下TCP协议的4次挥手动作,如文章开头的图片所示,当一个连接在关闭时,会有4次挥手的过程,TIME_WAIT状态存在于关闭发起方发送完最后一个ACK,之所以需要有一个等待时间,是为了以防最后一个ACK丢包。如果最后一个ACK丢包了会导致接收方再次重发一个FIN包,如果发起方此时还在等待,那么将还有正常处理的机会。

正常关闭
主动close socket的一方再发送最后一个ACK后会进入TIME_WAIT,默认情况下,处于TIME_WAIT状态的端口是不能用来listen的。对于服务端listen socket来说,如果在执行close时,等待队列中没有对应的连接,那么状态会直接进入closed,此时再次使用bind操作时理论上是不会有异常的,但是当我们关闭listen socket时,如果不够幸运,等待队列中还有等待处理的连接,那么后续将执行TCP的正常关闭流程,并且服务端作为发起方,那么无疑最后是会进入到TIME_WAIT状态,并且等待一定时间确保接收方已经接收了最后的一个ACK。实际上对于应用来说,执行完close它就已经退出了,而TCP的关闭流程是由内核协议栈管理的,在这个TIME_WAIT的过程中,内核是不允许再次对该地址和端口进行bind的。
异常关闭
如果是异常kill掉进程,对于server端连接状态会直接变为TIME_WAIT,如果是client端异常关闭,那么会导致server端口状态在多个状态上都需要进行等待。因此除非我们显式的指定了SO_REUSEADDR,否则我们都无法在立即重启服务后执行bind操作。

以上这就是SO_REUSEADDR对于服务端来说存在的意义。

(2)对于client进程来说,创建套接字去连接某个监听套接口,一般操作为:

socket->connect

在连接到某个服务器端口上时,我们不用执行bind操作,内核会随机为客户端生成对应的本地地址和端口号。因此这个选项使用到的机会并不会很大,加入我们没有对socket执行该选项,那么主动关闭后,虽然处于TIME_WAIT状态,但是下次执行客户端区连接时,内核会重新为客户端随机产生其他的端口号去连接,所以大多数情况下是用不到的,除非我们想要在客户端也bind端口号。

杀死进程,保证client端执行主动关闭。那么重启进程后,除非上一个连接退出了TIME_WAIT状态,否则重启的进程在调用connect时候错误返回。
主动close socket的一方再发送最后一个ACK后会进入TIME_WAIT,默认情况下,处于TIME_WAIT状态的端口是不能用来LISTEN的。会遇到的问题处于TIME_WAIT的数量过多导致后续无法连接,可以通过tcp_max_syn_backlog修改等待队列长度(这个值包括了ESTABLISHED连接和半连接),还可以用tcp_max_tw_buckets配置项来清除处于TIME_WAIT的连接。
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,(const void *)&reuse , sizeof(int));

SO_REUSEPORT

对于服务器来说默认情况下一个PORT只能被一个服务进程所占用,那么为了增加服务的能力,可以使能该特性,让多个服务进程同时复用同一个端口,提升服务吞吐量。使能该选项可以让多进程或者多线程创建多个socket绑定到同一个PORT上,提高服务器的并发能力。该特性在kernel 3.9 中引入的,因此在旧版本的内核版本上不能会用。


https://hea-www.harvard.edu/~fine/Tech/addrinuse.html

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Socket编程,TCP和UDP是两种不同的协议。它们之间的最主要的区别在于TCP是面向连接的协议,而UDP是无连接的协议。 在C语言,我们可以使用socket API来进行TCP和UDP的编程。对于TCP,我们需要先建立连接,然后才能进行数据的传输。而对于UDP,则是直接通过sendto()函数发送数据,接收方通过recvfrom()函数接收数据。 以下是一个简单的例子,展示了如何使用socket API实现TCP和UDP通信: TCP通信: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> int main() { int server_fd, new_socket; struct sockaddr_in address; int opt = 1; int addrlen = sizeof(address); char buffer[1024] = {0}; char *hello = "Hello from server"; // 创建套接字 if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } // 设置套接字选项 if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) { perror("setsockopt"); exit(EXIT_FAILURE); } // 绑定地址 address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons( 8080 ); if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) { perror("bind failed"); exit(EXIT_FAILURE); } // 监听连接 if (listen(server_fd, 3) < 0) { perror("listen"); exit(EXIT_FAILURE); } // 接受连接 if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) { perror("accept"); exit(EXIT_FAILURE); } // 发送数据 send(new_socket, hello, strlen(hello), 0); printf("Hello message sent\n"); return 0; } ``` UDP通信: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> int main() { int sockfd; char buffer[1024] = {0}; char *hello = "Hello from server"; struct sockaddr_in servaddr, cliaddr; // 创建套接字 if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) { perror("socket creation failed"); exit(EXIT_FAILURE); } memset(&servaddr, 0, sizeof(servaddr)); memset(&cliaddr, 0, sizeof(cliaddr)); // 设置服务器地址 servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = INADDR_ANY; servaddr.sin_port = htons(8080); // 绑定地址 if ( bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0 ) { perror("bind failed"); exit(EXIT_FAILURE); } int len, n; len = sizeof(cliaddr); // 接收客户端地址信息 n = recvfrom(sockfd, (char *)buffer, 1024, MSG_WAITALL, ( struct sockaddr *) &cliaddr, &len); buffer[n] = '\0'; printf("Client : %s\n", buffer); // 发送数据 sendto(sockfd, (const char *)hello, strlen(hello), MSG_CONFIRM, (const struct sockaddr *) &cliaddr, len); printf("Hello message sent.\n"); return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值