epoll实现并发服务器代码
Published: 2024-01-10 07:37:54

## select/poll/epoll区别？

```
select：

        1、select监听的最大文件描述符是1024个；

        2、select需要反复构造表，反复从用户空间向内核空间拷贝表，效率低

        3、当数据准备好后，select需要再次遍历表，找出准备好的文件描述符，效率低

poll：

        1、poll监听的文件描述符没有限制

        2、poll不需要反复构造表，不需要反复拷贝表，效率高

        3、当数据准备好之后，poll需要遍历表，找出准备好的文件描述符，效率低

epoll：

        1、epoll监听的文件描述符没有限制

        2、epoll不需要反复构造表，不需要反复拷贝表，效率高

        3、当数据准备好之后，epoll直接能够找出准备好的文件描述符，不需要遍历表，效率高。
```

```
#include "c.h"
#define ERR_MSG(msg)                          \
    do                                        \
    {                                         \
        fprintf(stderr, "line:%d", __LINE__); \
        perror(msg);                          \
    } while (0)
#define IP "192.168.250.100"
#define PORT 6666
#define BUFLEN 128
int deal_cli_msg(int newfd, struct sockaddr_in cin);

// 回收僵尸进程
void handler(int sig)
{
    while (waitpid(-1, NULL, WNOHANG) > 0)
        ;
}

int main(int argc, const char *argv[])
{
    char buf[BUFLEN];
    // 捕获17号 SIGCHLD信号，回收僵尸进程
    __sighandler_t s = signal(17, handler);
    if (SIG_ERR == s)
    {
        ERR_MSG("signal");
        return -1;
    }
    // 创建流式套接字
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd < 0)
    {
        ERR_MSG("socket");
        return -1;
    }
    printf("sfd=%d\n", sfd);
    // 允许端口快速重用
    int reuse = 1;
    if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
    {
        ERR_MSG("setsockopt");
        return -1;
    }
    printf("允许端口快速重用成功\n");

    // 填充地址信息结构体
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(PORT);
    sin.sin_addr.s_addr = inet_addr(IP);
    // 将ip和端口绑定到套接字上
    if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
    {
        ERR_MSG("bind");
        return -1;
    }
    printf("ip绑定套接字成功__%d__\n", __LINE__);
    // 将套接字设置为可监听状态
    if (listen(sfd, 128) < 0)
    {
        ERR_MSG("listen");
        return -1;
    }
    printf("将套接字设置为可监听状态成功__%d__\n", __LINE__);
    // 存储连接成功的客户端地址信息
    struct sockaddr_in cin;
    socklen_t addrlen = sizeof(cin);
    int newfd = -1;
    int epfd,epct,i,ret;
	struct epoll_event event;       //定义epoll 事件
	struct epoll_event events[20];  //定义epoll 事件集合
	epfd = epoll_create(1); // 创建epoll 的fd //红黑树根节点

	event.data.fd = sfd;           //填充事件的fd
	event.events = EPOLLIN | EPOLLET;   //填充 事件类型 
	epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event);  //把serverFd（监听FD）注册到epfd中
	
	
	while(1){
		
		epct = epoll_wait(epfd,events,20,-1); // 等待事件到来，阻塞模式 
		for(i=0;i<epct;i++){  //根据epoll返回的值来查询事件
			
			if(events[i].data.fd == sfd){ // 如果事件的fd是监听fd，调用accept处理
				newfd = accept(events[i].data.fd,(struct sockaddr *)&cin,&addrlen);
				event.data.fd = newfd;
				event.events = EPOLLIN | EPOLLET;
				epoll_ctl(epfd,EPOLL_CTL_ADD,newfd,&event);
				}
            else {   //如果不是serverFd，应是client数据事件，调用读数据
				
				memset(buf,0,BUFLEN);
				ret = read(events[i].data.fd,buf,BUFLEN);
				if(ret <=0){ //客户端断开 
					printf("connect disconnected\n");
					close(events[i].data.fd); //关闭文件描述符
					epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,&event); //删除监听的客户端fd
					continue;
				}
            }
        }
    }
    // 关闭进程的所有的文件描述符
    for (i = 0; i < getdtablesize(); i++)
    {
        close(i);
    }
    return 0;
}
```