SO_REUSEPORT与SO_REUSEADDR
SO_REUSEPORT端口重用
int opt_val = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &opt_val, sizeof(opt_val))
-
SO_REUSEPORT是允许多个socket绑定到同一个ip+port上
-
每一个线程拥有自己的服务器套接字, 在服务器套接字上没有了锁的竞争
-
内核层面实现负载均衡
SO_REUSEPORT在以下场景下有用:
-
当服务器需要使用多个进程或线程来监听相同的端口,而不想遇到惊群现象或资源竞争时,可以设置SO_REUSEPORT选项,以便实现网络连接的负载均衡。
-
当服务器需要使用UDP协议来接收多播数据报时,可以设置SO_REUSEPORT选项,以便让每个进程或线程接收到不同的数据报,提高效率和性能。
-
EPOLL惊群问题
SO_REUSEADDR是地址重用
int opt_val = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt_val, sizeof(opt_val))
-
SO_REUSEADDR可以解决服务器主动关闭连接后,需要等待2MSL时间才能重新绑定相同的端口的问题。如果不设置这个选项,服务器在关闭连接后,会进入TIME_WAIT状态,此时再次启动服务器,就会绑定失败,报:Address already in use。如果设置了这个选项,服务器就可以立即重启并绑定相同的端口
-
SO_REUSEADDR可以解决服务器使用通配符IP地址(0.0.0.0)监听端口后,无法再绑定具体的IP地址到相同的端口的问题。如果不设置这个选项,当一个socket绑定到0.0.0.0:port后,其他socket就无法绑定到192.168.0.1:port等具体的IP地址上。如果设置了这个选项,就可以允许多个socket绑定到相同的端口,只要它们使用不同的本地IP地址
SO_REUSEADDR在以下场景下有用:
-
当服务器需要频繁地启动和关闭,而不想等待2MSL时间才能重新绑定相同的端口时,可以设置SO_REUSEADDR选项,以便快速重启。
-
当服务器需要使用通配符IP地址(0.0.0.0)监听端口,同时又想绑定其他具体的IP地址到相同的端口时,可以设置SO_REUSEADDR选项,以便支持多个IP地址的监听。
Epoll惊群
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#define PROCESS_NUM 10
static int create_and_bind (char *port)
{
int fd = socket(PF_INET, SOCK_STREAM, 0);
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(atoi(port));
bind(fd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
return fd;
}
static int make_socket_non_blocking (int sfd)
{
int flags, s;
flags = fcntl (sfd, F_GETFL, 0);
if (flags == -1)
{
perror ("fcntl");
return -1;
}
flags |= O_NONBLOCK;
s = fcntl (sfd, F_SETFL, flags);
if (s == -1)
{
perror ("fcntl");
return -1;
}
return 0;
}
#define MAXEVENTS 64
int main (int argc, char *argv[])
{
int sfd, s;
int efd;
struct epoll_event event;
struct epoll_event *events;
sfd = create_and_bind("8001");
if (sfd == -1)
abort ();
s = make_socket_non_blocking (sfd);
if (s == -1)
abort ();
s = listen(sfd, SOMAXCONN);
if (s == -1)
{
perror ("listen");
abort ();
}
efd = epoll_create(MAXEVENTS);
if (efd == -1)
{
perror("epoll_create");
abort();
}
event.data.fd = sfd;
//event.events = EPOLLIN | EPOLLET;
event.events = EPOLLIN;
s = epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &event);
if (s == -1)
{
perror("epoll_ctl");
abort();
}
/* Buffer where events are returned */
events = calloc(MAXEVENTS, sizeof event);
int k;
for(k = 0; k < PROCESS_NUM; k++)
{
int pid = fork();
if(pid == 0)
{
/* The event loop */
while (1)
{
int n, i;
n = epoll_wait(efd, events, MAXEVENTS, -1);
printf("process %d return from epoll_wait!\n", getpid());
for (i = 0; i < n; i++)
{
if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) || (!(events[i].events & EPOLLIN)))
{
/* An error has occured on this fd, or the socket is not ready for reading (why were we notified then?) */
fprintf (stderr, "epoll error\n");
close (events[i].data.fd);
continue;
}
else if (sfd == events[i].data.fd)
{
/* We have a notification on the listening socket, which means one or more incoming connections. */
struct sockaddr in_addr;
socklen_t in_len;
int infd;
char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
in_len = sizeof in_addr;
infd = accept(sfd, &in_addr, &in_len);
if (infd == -1)
{
printf("process %d accept failed!\n", getpid());
break;
}
printf("process %d accept successed!\n", getpid());
/* Make the incoming socket non-blocking and add it to the list of fds to monitor. */
close(infd);
}
}
}
}
}
int status;
wait(&status);
free (events);
close (sfd);
return EXIT_SUCCESS;
}
Re:
https://blog.csdn.net/c359719435/article/details/51721902
https://www.cnblogs.com/schips/p/12553321.html
https://zhuanlan.zhihu.com/p/378892166