linux下epoll编程

来源:互联网 发布:c语言中extern的用法 编辑:程序博客网 时间:2024/05/22 08:27

#include <unistd.h>#include <sys/types.h>#include <fcntl.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <signal.h>#include <fcntl.h>#include <sys/wait.h>#include <sys/epoll.h>#include <stdlib.h>#include <stdio.h>#include <errno.h>#include <string.h>#include <vector>#include <algorithm>#include <iostream>typedef std::vector<struct epoll_event> EventList;#define ERR_EXIT(m) \        do \        { \                perror(m); \                exit(EXIT_FAILURE); \        } while(0)int main(void){signal(SIGPIPE, SIG_IGN);//客户端关闭套接字,服务器端就会收到SIGPIPE信号(这个信号默认用于退出当前进程),SIG_IGN用于屏蔽这个信号signal(SIGCHLD, SIG_IGN);//signal(SIGCHLD,SIG_IGN) 通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收, 并不再给父进程发送信号int idlefd = open("/dev/null", O_RDONLY | O_CLOEXEC);int listenfd;//if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)if ((listenfd = socket(PF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP)) < 0)ERR_EXIT("socket");struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(servaddr));//清零操作servaddr.sin_family = AF_INET;servaddr.sin_port = htons(5188);servaddr.sin_addr.s_addr = htonl(INADDR_ANY);int on = 1;if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)//设置监听套接字可重用ERR_EXIT("setsockopt");if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)ERR_EXIT("bind");if (listen(listenfd, SOMAXCONN) < 0)//SOMAXCONN表示最大连接数ERR_EXIT("listen");std::vector<int> clients;int epollfd;epollfd = epoll_create1(EPOLL_CLOEXEC);//这个函数用来创建一个文件描述符来唯一标示内核中的一个事件表//)参数EPOLL_CLOEXEC是这个epoll专用描述符epfd所关联的socketfd的最大个数,这个参数实际上是不起什么作用的//EPOLL_CLOEXEC = 02000000,//在新建的epfd上设置FD_CLOEXEC。struct epoll_event event; //事件结构体event.data.fd = listenfd;//指定事件所从属的目标文件描述符event.events = EPOLLIN/* | EPOLLET*/;//监听套接字加入 关注他的读的触发事件(电平触发)epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event);//添加进去关注//向epollfd这个事件表中注册( EPOLL_CTL_ADD)listenfd这个套接字上的事件(event);EventList events(16);//向量数组类的实例化,16个事件struct sockaddr_in peeraddr;socklen_t peerlen;int connfd;int nready;while (1){   nready = epoll_wait(epollfd, &*events.begin(), static_cast<int>(events.size()), -1);//epoll_wait在一段超时时间内等待epollfd上的事件,如果检测到事件,//就将所有就绪的事件从内核事件表epollfd中复制到&*events.begin()指向的数组中,这个数组只用于输出epoll_wait检测到的就绪事件//nready是返回的&*events.begin()这个数组中装啦多少个就绪的文件描述符        //这里就是poll和epoll在使用上的差异//poll必须遍历所有已注册文件描述符并找到其中的就绪着//而epoll只用遍历就绪的nready个(在&*events.begin()中存着)在文件描述符if (nready == -1){if (errno == EINTR) //接收到中断信号continue;ERR_EXIT("epoll_wait");}if (nready == 0)// nothing happended //超时continue;if ((size_t)nready == events.size())//如果events中已经存满了事件(上面定义啦16个),就扩容1倍events.resize(events.size()*2);for (int i = 0; i < nready; ++i) //仅遍历就绪的的nready个文件描述符(这些都是要处理的){if (events[i].data.fd == listenfd){peerlen = sizeof(peeraddr);connfd = ::accept4(listenfd, (struct sockaddr*)&peeraddr,&peerlen, SOCK_NONBLOCK | SOCK_CLOEXEC);if (connfd == -1)//函数发生错误{if (errno == EMFILE)//表示每个进程允许打开的文件描述符数量最大值已经到达{//遇到这种情况的解决方法是/*准备一个空闲的文件描述符。遇到这种情况,先关闭这个空闲文件,获得一个文件描述符名额;再accept(2)拿到socket连接的文件描述符;随后立刻close(2),这样就优雅地断开了与客户端的连接;最后重新打开空闲文件,把“坑”填上,以备再次出现这种情况时使用*/close(idlefd);idlefd = accept(listenfd, NULL, NULL);close(idlefd);idlefd = open("/dev/null", O_RDONLY | O_CLOEXEC);continue;}elseERR_EXIT("accept4");}std::cout<<"ip="<<inet_ntoa(peeraddr.sin_addr)<<" port="<<ntohs(peeraddr.sin_port)<<std::endl;clients.push_back(connfd);event.data.fd = connfd;event.events = EPOLLIN/* | EPOLLET*/;epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &event);}else if (events[i].events & EPOLLIN)//有数据可读事件发生{connfd = events[i].data.fd;if (connfd < 0)continue;char buf[1024] = {0};int ret = read(connfd, buf, 1024);if (ret == -1)ERR_EXIT("read");if (ret == 0){std::cout<<"client close"<<std::endl;close(connfd);event = events[i];epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, &event);//向epollfd这个事件表中删除( EPOLL_CTL_ADD)connfd这个套接字上的事件(event);clients.erase(std::remove(clients.begin(), clients.end(), connfd), clients.end());//删除这个元素continue;//进入下次循环}std::cout<<buf;write(connfd, buf, strlen(buf));//向这个数据套接字中写入数据}}}return 0;}


原创粉丝点击