惊群问题
使用epoll_wait会造成惊群问题,执行下面的代码会出现连接异常。
#include <stdio.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <pthread.h>
#define PROCESS_NUMS 5
// 监听本地ip
/// @brief 初始化监听socket
/// @param server_ip 服务器的ip地址 SERVER_IP
/// @param port 服务器的端口号 78
/// @return 返回监听的socket
int init_listen(const char *server_ip, int port)
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0)
{
perror("Error creating socket");
close(fd);
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
// s_addr就是32位的一个数
if (inet_pton(AF_INET, server_ip, &addr.sin_addr) <= 0)
{
perror("Error converting Ip address");
close(fd);
return -2;
}
if (bind(fd, &addr, sizeof(addr)) < 0)
{
perror("Error binding Ip address");
close(fd);
return -3;
}
// 标记套接字为被动套接字,等待连接请求
if (listen(fd, 5) == -1)
{
perror("Error listening on socket");
close(fd);
return -4;
}
printf("Server is listening for incoming connections\n");
return fd;
}
int main()
{
// 开启4个进程
int epoll_fd = epoll_create(512);
// 监听4个socket
int ports[] = {8888, 8889, 8890, 8881};
int listen_sockets[4];
for (int i = 0; i < 4; ++i)
{
int listen_fd = init_listen("192.168.64.13", ports[i]);
printf("%d ", listen_fd);
listen_sockets[i] = listen_fd;
struct epoll_event ev;
ev.data.fd = listen_fd;
ev.events = EPOLLIN;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev);
}
printf("end listening.");
fflush(stdout);
for (int i = 0; i < PROCESS_NUMS; ++i)
{
pid_t pid = fork();
if (pid == 0)
{
while (1)
{
printf("%d\n", pid);
struct epoll_event evs[10];
// epoll_wait会造成惊群问题
// 需要使用互互斥锁保护这个资源
// 申请锁
// 获取资源
// 释放锁
printf("ready to wait epoll return.\n");
int cnt = epoll_wait(epoll_fd, evs, 10, -1);
// printf("%d",cnt);
printf("return from epoll_wait, res=%d\n", cnt);
if (cnt < 0)
{
perror("Error");
return 1;
}
else if (cnt == 0)
{
printf("No IO event ready.\n");
}
else
{
for (int i = 0; i < cnt; ++i)
{
int fd = evs[i].data.fd;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
socklen_t socklen = sizeof(addr);
int conn_fd = accept(fd, (struct sockaddr *)&addr, &socklen);
printf("return from accept function\n");
if (conn_fd < 0)
{
printf("Error Connection\n");
}
else
{
// printf("Connect to a new client: %s:%d\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
printf("Connected\n");
}
}
}
}
}
}
// 按下ctrl+c 所有的进程都会结束
for (int i = 0; i < PROCESS_NUMS; ++i)
{
wait(NULL);
printf("one subprocess dead\n");
fflush(stdout);
}
for (int i = 0; i < 4; ++i)
{
close(listen_sockets[i]);
}
close(epoll_fd);
}
有多个epoll_wait返回,但是只有一个accept返回,其他的进程会被阻塞在accept这里,无法返回。
解决方法
可以使用mutex互斥锁解决这一问题。可以提高服务器的处理性能。
#include <stdio.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <pthread.h>
#include <sys/mman.h>
#define PROCESS_NUMS 3
// 监听本地ip
/// @brief 初始化监听socket
/// @param server_ip 服务器的ip地址 SERVER_IP
/// @param port 服务器的端口号 78
/// @return 返回监听的socket
int init_listen(const char *server_ip, int port)
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0)
{
perror("Error creating socket");
close(fd);
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
// s_addr就是32位的一个数
if (inet_pton(AF_INET, server_ip, &addr.sin_addr) <= 0)
{
perror("Error converting Ip address");
close(fd);
return -2;
}
if (bind(fd, &addr, sizeof(addr)) < 0)
{
perror("Error binding Ip address");
close(fd);
return -3;
}
// 标记套接字为被动套接字,等待连接请求
if (listen(fd, 5) == -1)
{
perror("Error listening on socket");
close(fd);
return -4;
}
printf("Server is listening for incoming connections\n");
return fd;
}
int main()
{
// 开启4个进程
int epoll_fd = epoll_create(512);
pthread_mutex_t* mutex_ptr=NULL;
pthread_mutexattr_t mutex_attr;
mutex_ptr=(pthread_mutex_t *)mmap(
NULL, sizeof(pthread_mutex_t), PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0
);
pthread_mutexattr_init(&mutex_attr);
pthread_mutexattr_setpshared(&mutex_attr,PTHREAD_PROCESS_SHARED);
pthread_mutex_init(mutex_ptr,&mutex_attr);
// 监听4个socket
int ports[] = {8888, 8889, 8890, 8881};
int listen_sockets[4];
for (int i = 0; i < 4; ++i)
{
int listen_fd = init_listen("192.168.64.13", ports[i]);
printf("%d ", listen_fd);
listen_sockets[i] = listen_fd;
struct epoll_event ev;
ev.data.fd = listen_fd;
ev.events = EPOLLIN;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev);
}
printf("end listening.");
fflush(stdout);
for (int i = 0; i < PROCESS_NUMS; ++i)
{
pid_t pid = fork();
if (pid == 0)
{
while (1)
{
printf("%d\n", pid);
struct epoll_event evs[10];
// epoll_wait会造成惊群问题
// 需要使用互互斥锁保护这个资源
// 申请锁
// 获取资源
// 释放锁
pthread_mutex_lock(mutex_ptr);
printf("ready to wait epoll return.\n");
int cnt = epoll_wait(epoll_fd, evs, 10, -1);
pthread_mutex_unlock(mutex_ptr);
// printf("%d",cnt);
printf("return from epoll_wait, res=%d\n", cnt);
if (cnt < 0)
{
perror("Error");
return 1;
}
else if (cnt == 0)
{
printf("No IO event ready.\n");
}
else
{
for (int i = 0; i < cnt; ++i)
{
int fd = evs[i].data.fd;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
socklen_t socklen = sizeof(addr);
int conn_fd = accept(fd, (struct sockaddr *)&addr, &socklen);
printf("return from accept function\n");
if (conn_fd < 0)
{
printf("Error Connection\n");
}
else
{
// printf("Connect to a new client: %s:%d\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
printf("Connected\n");
sleep(2);
close(conn_fd);
}
}
}
}
}
}
// 按下ctrl+c 所有的进程都会结束
for (int i = 0; i < PROCESS_NUMS; ++i)
{
wait(NULL);
printf("one subprocess dead\n");
fflush(stdout);
}
for (int i = 0; i < 4; ++i)
{
close(listen_sockets[i]);
}
close(epoll_fd);
}