• epoll+多进程实现简单的服务器端


      最近项目组中有个同事使用epoll+多线程实现了一个简单的服务器,但是经过压测后,发现如果使用边缘触发模式的话,就会出现丢包现象,水平触发设置等待时间确实解决了丢包问题,但却影响了服务器的性能。所以我就写了一个epoll+多进程的模型,代码如下:

    #include <sys/socket.h>  
    #include <sys/epoll.h>  
    #include <netinet/in.h>  
    #include <arpa/inet.h>  
    #include <fcntl.h>  
    #include <unistd.h>  
    #include <stdio.h>  
    #include <errno.h>  
    #include <sys/types.h>
    #include <sys/mman.h>
    #include <iostream>  
    using namespace std;  
    #define MAX_EVENTS 10000  
    
    
    void Process(int listenFd);
    
    struct shmstruct
    {
    	int count;
    };
    
    int main(int argc, char **argv)  
    {  
    	short port = 6666; // default port  
    	if(argc == 2){  
    		port = atoi(argv[1]);  
    	}
    
    	shm_unlink("test");
    	int fd1 = shm_open("test",O_RDWR|O_CREAT|O_EXCL,666);
    	struct shmstruct* ptr;
    	ftruncate(fd1,sizeof(struct shmstruct));
    	ptr = (struct shmstruct*)mmap(NULL,sizeof(struct shmstruct),PROT_READ|PROT_WRITE,MAP_SHARED,fd1,0);
    	close(fd1);
    
    	int listenFd = socket(AF_INET, SOCK_STREAM, 0); 
    	fcntl(listenFd, F_SETFL, O_NONBLOCK); // 设置非阻塞方式
    	sockaddr_in sin;  
    	bzero(&sin, sizeof(sin));  
    	sin.sin_family = AF_INET;  
    	sin.sin_addr.s_addr = INADDR_ANY;  
    	sin.sin_port = htons(port);  
    	bind(listenFd, (const sockaddr*)&sin, sizeof(sin));  
    	listen(listenFd, 5);  
    
    	for (int i=0; i<5 ;i++)
    	{
    		pid_t pid = fork();
    		if (pid == 0)	// 子进程
    		{
    			Process(listenFd); // 工作进程
    			return 0;
    		}
    	}
    	while (true)
    	{
    		sleep(10);
    	}
    
    	return 0;  
    }   
    
    
    void Process(int listenFd)
    {
    	int fd1 = shm_open("test",O_RDWR,666);
    	struct shmstruct* ptr;
    	ptr = (struct shmstruct*)mmap(NULL,sizeof(struct shmstruct),PROT_READ|PROT_WRITE,MAP_SHARED,fd1,0);
    	close(fd1);
    	struct epoll_event ev, events[MAX_EVENTS];
    	//生成用于处理accept的 epoll专用的文件描述符
    	int epfd = epoll_create( MAX_EVENTS );
    	//设置与要处理的事件相关的文件描述符
    	ev.data.fd = listenFd;
    	//设置要处理的事件类型
    	ev.events = EPOLLIN | EPOLLET;
    	//注册epoll事件
    	if ( epoll_ctl( epfd, EPOLL_CTL_ADD, listenFd, &ev ) < 0 )
    	{
    		printf( "worker epoll_ctl error = %s.", strerror(errno) );
    		exit(1);
    	}
    
    	while (true)
    	{
    		// 等待epoll事件的发生
    		int nfds = epoll_wait( epfd, events, MAX_EVENTS, -1 );
    		// 处理所发生的所有事件
    		for( int i=0; i<nfds; ++i ) // for循环中可以修改为线程池处理epoll事件
    		{
    			if( events[i].data.fd == listenFd )
    			{
    				socklen_t clilen;
    				struct sockaddr_in clientaddr;
    
    				int sockfd = accept( listenFd, (sockaddr *)&clientaddr, &clilen );
    				if( sockfd < 0 )
    				{
    					continue;
    				}
    
    				// 设置非阻塞
    				if (fcntl( sockfd, F_SETFL, fcntl( sockfd, F_GETFD, 0)|O_NONBLOCK) == -1)
    				{
    					continue;
    				}
    				//设置用于读操作的文件描述符
    				ev.data.fd = sockfd;
    				//设置用于注测的读操作事件
    				ev.events = EPOLLIN | EPOLLET;
    				//注册ev
    				epoll_ctl( epfd, EPOLL_CTL_ADD, sockfd, &ev);
    			}
    			else if (events[i].events & EPOLLIN)
    			{
    				int sockfd = events[i].data.fd;
    				if ( sockfd < 0 )
    				{
    					continue;
    				}
    
    				char buf[1024] = {0};
    				// 开始处理每个新连接上的数据收发
    				bzero( buf, sizeof(buf) );
    				int len = read( sockfd, buf, 1023 ); 
    				if ( len < 0)
    				{
    					if (errno == ECONNRESET)
    					{
    						close(sockfd);
    						events[i].data.fd = -1;
    					}
    					else
    					{
    						printf( "worker read data error = %s.", strerror(errno) );
    					}
    				}
    				else if ( len == 0 )
    				{
    					events[i].data.fd = -1;
    				}
    				else
    				{
    					if( send(sockfd, "asdf", 4, 0) < 0)    
    					{
    						printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
    						exit(0);
    					}
    					printf("count:%d\n",++ptr->count);
    				}
    			}
    		}
    	}
    }
    

      

      注:代码在redhat下能正常编译,如果其他平台,可能需要修改共享内存部分的代码,因为有些linux平台共享内存的创建路径需要写绝对路径。

      通过测试,压测的结果与子进程数成线性关系,也与服务器的配置有一定关系,我用的机器在5个子进程下,能达到5000的并发不丢包。其实如果在for循环内使用线程池进行业务处理的话,可能启用的子进程数不需要很多就能达到很大数量的并发,这个epoll+多进程+线程池也是目前多数服务器使用的epoll模型。等有时间了,再写个加入线程池的模型也测试测试。

  • 相关阅读:
    前端开发者也可以酷酷地开发桌面程序
    手把手教你怎么搭建angular+gulp的项目(一)
    在Angular中,如果权限值是异步请求所得,如何将其设置为HTTP请求头的Authorization?
    AngularJs ng-repeat指令中怎么实现含有自定义指令的动态html
    第一篇随笔,练练手
    我参与 Seata 开源项目的一些感悟
    一次 kafka 消息堆积问题排查
    图解 Kafka 水印备份机制
    Seata 动态配置订阅与降级实现原理
    记一次 Kafka 集群线上扩容
  • 原文地址:https://www.cnblogs.com/osyun/p/2162104.html
Copyright © 2020-2023  润新知