epoll_wait造成惊群问题

文章讲述了在多进程服务器中,由于epoll_wait可能导致惊群问题,通过添加互斥锁解决accept函数被阻塞的问题,从而提高并发性能。
摘要由CSDN通过智能技术生成

惊群问题

使用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);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值