网络编程中的SO_REUSEADDR和SO_REUSEPORT参数详解
SO_REUSEADDR:
目前为止我见到的设置SO_REUSEADDR的使用场景:server端在调用bind函数时
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,(const void *)&reuse , sizeof(int));
目的:当服务端出现timewait状态的链接时,确保server能够重启成功。
注意:SO_REUSEADDR只有针对time-wait链接(linux系统time-wait连接持续时间为1min),确保server重启成功的这一个作用,至于网上有文章说:如果有socket绑定了0.0.0.0:port;设置该参数后,其他socket可以绑定本机ip:port。本人经过试验后均提示“Address already in use”错误,绑定失败。
举个例子:
server监听9980端口,由于主动关闭链接,产生了一个time-wait状态的链接:
如果此时server重启,并且server没有设置SO_REUSEADDR参数,server重启失败,报错:“Address already in use”
如果设置SO_REUSEADDR,重启ok;
SO_REUSEPORT:
SO_REUSEPORT使用场景:linux kernel 3.9 引入了最新的SO_REUSEPORT选项,使得多进程或者多线程创建多个绑定同一个ip:port的监听socket,提高服务器的接收链接的并发能力,程序的扩展性更好;此时需要设置SO_REUSEPORT(注意所有进程都要设置才生效)。
setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT,(const void *)&reuse , sizeof(int));
目的:每一个进程有一个独立的监听socket,并且bind相同的ip:port,独立的listen()和accept();提高接收连接的能力。(例如nginx多进程同时监听同一个ip:port)
解决的问题:
(1)避免了应用层多线程或者进程监听同一ip:port的“惊群效应”。
(2)内核层面实现负载均衡,保证每个进程或者线程接收均衡的连接数。
(3)只有effective-user-id相同的服务器进程才能监听同一ip:port (安全性考虑)
代码示例:server_128.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <sys/wait.h>
void work () {
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd < 0) {
perror("listen socket");
_exit(-1);
}
int ret = 0;
int reuse = 1;
ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,(const void *)&reuse , sizeof(int));
if (ret < 0) {
perror("setsockopt");
_exit(-1);
}
ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT,(const void *)&reuse , sizeof(int));
if (ret < 0) {
perror("setsockopt");
_exit(-1);
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
//addr.sin_addr.s_addr = inet_addr("10.95.118.221");
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
addr.sin_port = htons(9980);
ret = bind(listenfd, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0) {
perror("bind addr");
_exit(-1);
}
printf("bind success\n");
ret = listen(listenfd,10);
if (ret < 0) {
perror("listen");
_exit(-1);
}
printf("listen success\n");
struct sockaddr clientaddr;
int len = 0;
while(1) {
printf("process:%d accept...\n", getpid());
int clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, &len);
if (clientfd < 0) {
printf("accept:%d %s", getpid(),strerror(errno));
_exit(-1);
}
close(clientfd);
printf("process:%d close socket\n", getpid());
}
}
int main(){
printf("uid:%d euid:%d\n", getuid(),geteuid());
int i = 0;
for (i = 0; i< 6; i++) {
pid_t pid = fork();
if (pid == 0) {
work();
}
if(pid < 0) {
perror("fork");
continue;
}
}
int status,id;
while((id=waitpid(-1, &status, 0)) > 0) {
printf("%d exit\n", id);
}
if(errno == ECHILD) {
printf("all child exit\n");
}
return 0;
}
上述示例程序,启动了6个子进程,每个子进程创建自己的监听socket,bind相同的ip:port;独立的listen(),accept();如果每个子进程不设置SO_REUSEADDR选项,会提示“Address already in use”错误。
安全性:是不是只要设置了SO_REUSEPORT选项的服务器程序,就能够监听相同的ip:port;并获取报文呢?当然不是,当前linux内核要求后来启动的服务器程序与前面启动的服务器程序的effective-user-id要相同。
举个例子:
上图中的server_127和server128两个服务器程序都设置了SO_REUSEPORT,监听同一ip:port,用root用户启动两个服务器程序,因为effective-user-id相等,都为0,所以启动没问题。
修改server127的权限:chmod u+s server_127
启动完server_128后,在启动server_127,提示错误:
http://weixin.qq.com/r/mi7v9x7EDUNyrVqU93uI (二维码自动识别)
想了解更多相关知识,请搜索关注微信公众号 bloomingTony