网易云音乐网站建设项目规划书,深圳宝安西乡网站建设,网站开发遇到的问题及解决方法,网站如何优化一个关键词http://www.cnblogs.com/venow/archive/2012/11/30/2790031.html 定义#xff1a; epoll是Linux内核为处理大批句柄而作改进的poll#xff0c;是Linux下多路复用IO接口select/poll的增强版本#xff0c;它能显著的减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利… http://www.cnblogs.com/venow/archive/2012/11/30/2790031.html 定义 epoll是Linux内核为处理大批句柄而作改进的poll是Linux下多路复用IO接口select/poll的增强版本它能显著的减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。因为它会复用文件描述符集合来传递结果而不是迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合另一个原因就是获取事件的时候它无须遍历整个被侦听的描述符集只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select\poll那种IO事件的电平触发(Level Triggered)外还提供了边沿触发(Edge Triggered)这就使得用户空间程序有可能缓存IO状态减少epoll_wait/epoll_pwait的调用提供应用程序的效率。 工作方式 LT(level triggered)水平触发缺省方式同时支持block和no-block socket在这种做法中内核告诉我们一个文件描述符是否被就绪了如果就绪了你就可以对这个就绪的fd进行IO操作。如果你不作任何操作内核还是会继续通知你的所以这种模式编程出错的可能性较小。传统的select\poll都是这种模型的代表。 ET(edge-triggered)边沿触发高速工作方式只支持no-block socket。在这种模式下当描述符从未就绪变为就绪状态时内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪并且不会再为那个描述符发送更多的就绪通知直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如你在发送、接受或者接受请求或者发送接受的数据少于一定量时导致了一个EWOULDBLOCK错误)。但是请注意如果一直不对这个fs做IO操作(从而导致它再次变成未就绪状态)内核不会发送更多的通知。 区别LT事件不会丢弃而是只要读buffer里面有数据可以让用户读取则不断的通知你。而ET则只在事件发生之时通知。 使用方式 1、int epoll_create(int size) 创建一个epoll句柄参数size用来告诉内核监听的数目。 2、int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) epoll事件注册函数 参数epfd为epoll的句柄 参数op表示动作用3个宏来表示EPOLL_CTL_ADD(注册新的fd到epfd)EPOLL_CTL_MOD(修改已经注册的fd的监听事件)EPOLL_CTL_DEL(从epfd删除一个fd) 参数fd为需要监听的标示符 参数event告诉内核需要监听的事件event的结构如下 struct epoll_event {__uint32_t events; /* Epoll events */epoll_data_t data; /* User data variable */
}; 其中events可以用以下几个宏的集合 EPOLLIN 表示对应的文件描述符可以读包括对端SOCKET正常关闭 EPOLLOUT表示对应的文件描述符可以写 EPOLLPRI表示对应的文件描述符有紧急的数据可读这里应该表示有带外数据到来 EPOLLERR表示对应的文件描述符发生错误 EPOLLHUP表示对应的文件描述符被挂断 EPOLLET 将EPOLL设为边缘触发(Edge Triggered)模式这是相对于水平触发(Level Triggered)来说的 EPOLLONESHOT只监听一次事件当监听完这次事件之后如果还需要继续监听这个socket的话需要再次把这个socket加入到EPOLL队列里 3、 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout) 等待事件的产生类似于select()调用。参数events用来从内核得到事件的集合maxevents告之内核这个events有多大这个maxevents的值不能大于创建epoll_create()时的size参数timeout是超时时间毫秒0会立即返回-1将不确定也有说法说是永久阻塞。该函数返回需要处理的事件数目如返回0表示已超时。 应用举例 下面我引用google code中别人写的一个简单程序来进行说明。svn路径http://sechat.googlecode.com/svn/trunk/ 该程序一个简单的聊天室程序用Linux C写的服务器主要是用epoll模型实现支持高并发我测试在有10000个客户端连接服务器的时候server处理时间不到1秒当然客户端只是与服务器连接之后接受服务器的欢迎消息而已并没有做其他的通信。虽然程序比较简单但是在我们考虑服务器高并发时也提供了一个思路。在这个程序中我已经把所有的调试信息和一些与epoll无关的信息干掉并添加必要的注释应该很容易理解。 程序共包含2个头文件和3个cpp文件。其中3个cpp文件中每一个cpp文件都是一个应用程序server.cpp服务器程序client.cpp单个客户端程序tester.cpp模拟高并发开启10000个客户端去连服务器。 utils.h头文件就包含一个设置socket为不阻塞函数如下 int setnonblocking(int sockfd)
{CHK(fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK));return 0;
} local.h头文件一些常量的定义和函数的声明如下 #define BUF_SIZE 1024 //默认缓冲区
#define SERVER_PORT 44444 //监听端口
#define SERVER_HOST 192.168.34.15 //服务器IP地址
#define EPOLL_RUN_TIMEOUT -1 //epoll的超时时间
#define EPOLL_SIZE 10000 //epoll监听的客户端的最大数目#define STR_WELCOME Welcome to seChat! You ID is: Client #%d
#define STR_MESSAGE Client #%d %s
#define STR_NOONE_CONNECTED Noone connected to server except you!
#define CMD_EXIT EXIT//两个有用的宏定义检查和赋值并且检测
#define CHK(eval) if(eval 0){perror(eval); exit(-1);}
#define CHK2(res, eval) if((res eval) 0){perror(eval); exit(-1);}//
//函数名 setnonblocking
//函数描述 设置socket为不阻塞
//输入 [in] sockfd socket标示符
//输出 无
//返回 0
//
int setnonblocking(int sockfd);//
//函数名 handle_message
//函数描述 处理每个客户端socket
//输入 [in] new_fd socket标示符
//输出 无
//返回 返回从客户端接受的数据的长度
//
int handle_message(int new_fd); server.cpp文件epoll模型就在这里实现如下 #include local.h
#include utils.husing namespace std;// 存放客户端socket描述符的list
listint clients_list;int main(int argc, char *argv[])
{int listener; //监听socketstruct sockaddr_in addr, their_addr; addr.sin_family PF_INET;addr.sin_port htons(SERVER_PORT);addr.sin_addr.s_addr inet_addr(SERVER_HOST);socklen_t socklen;socklen sizeof(struct sockaddr_in);static struct epoll_event ev, events[EPOLL_SIZE];ev.events EPOLLIN | EPOLLET; //对读感兴趣边沿触发char message[BUF_SIZE];int epfd; //epoll描述符clock_t tStart; //计算程序运行时间int client, res, epoll_events_count;CHK2(listener, socket(PF_INET, SOCK_STREAM, 0)); //初始化监听socketsetnonblocking(listener); //设置监听socket为不阻塞CHK(bind(listener, (struct sockaddr *)addr, sizeof(addr))); //绑定监听socketCHK(listen(listener, 1)); //设置监听
CHK2(epfd,epoll_create(EPOLL_SIZE)); //创建一个epoll描述符并将监听socket加入epollev.data.fd listener;CHK(epoll_ctl(epfd, EPOLL_CTL_ADD, listener, ev));while(1){CHK2(epoll_events_count,epoll_wait(epfd, events, EPOLL_SIZE, EPOLL_RUN_TIMEOUT));tStart clock();for(int i 0; i epoll_events_count ; i){if(events[i].data.fd listener) //新的连接到来将连接添加到epoll中并发送欢迎消息{CHK2(client,accept(listener, (struct sockaddr *) their_addr, socklen));setnonblocking(client);ev.data.fd client;CHK(epoll_ctl(epfd, EPOLL_CTL_ADD, client, ev));clients_list.push_back(client); // 添加新的客户端到listbzero(message, BUF_SIZE);res sprintf(message, STR_WELCOME, client);CHK2(res, send(client, message, BUF_SIZE, 0));}else {CHK2(res,handle_message(events[i].data.fd)); //注意这里并没有调用epoll_ctl重新设置socket的事件类型但还是可以继续收到客户端发送过来的信息}}printf(Statistics: %d events handled at: %.2f second(s)\n, epoll_events_count, (double)(clock() - tStart)/CLOCKS_PER_SEC);}close(listener);close(epfd);return 0;
}int handle_message(int client)
{char buf[BUF_SIZE], message[BUF_SIZE];bzero(buf, BUF_SIZE);bzero(message, BUF_SIZE);int len;CHK2(len,recv(client, buf, BUF_SIZE, 0)); //接受客户端信息if(len 0) //客户端关闭或出错关闭socket并从list移除socket{CHK(close(client));clients_list.remove(client);}else //向客户端发送信息{ if(clients_list.size() 1) { CHK(send(client, STR_NOONE_CONNECTED, strlen(STR_NOONE_CONNECTED), 0));return len;}sprintf(message, STR_MESSAGE, client, buf);listint::iterator it;for(it clients_list.begin(); it ! clients_list.end(); it){if(*it ! client){ CHK(send(*it, message, BUF_SIZE, 0));}}}return len;
} tester.cpp文件模拟服务器的高并发开启10000个客户端去连接服务器如下 #include local.h
#include utils.husing namespace std;char message[BUF_SIZE]; //接受服务器信息
listint list_of_clients; //存放所有客户端
int res;
clock_t tStart;int main(int argc, char *argv[])
{int sock; struct sockaddr_in addr;addr.sin_family PF_INET;addr.sin_port htons(SERVER_PORT);addr.sin_addr.s_addr inet_addr(SERVER_HOST);tStart clock();for(int i0 ; iEPOLL_SIZE; i) //生成EPOLL_SIZE个客户端这里是10000个模拟高并发{CHK2(sock,socket(PF_INET, SOCK_STREAM, 0));CHK(connect(sock, (struct sockaddr *)addr, sizeof(addr)) 0);list_of_clients.push_back(sock);bzero(message, BUF_SIZE);CHK2(res,recv(sock, message, BUF_SIZE, 0));printf(%s\n, message);}listint::iterator it; //移除所有客户端for(it list_of_clients.begin(); it ! list_of_clients.end() ; it)close(*it);printf(Test passed at: %.2f second(s)\n, (double)(clock() - tStart)/CLOCKS_PER_SEC); printf(Total server connections was: %d\n, EPOLL_SIZE);return 0;
} 我就不给出程序的执行结果的截图了不过下面这张截图是代码作者自己测试的可以看出并发10000无压力呀 单个客户端去连接服务器client.cpp文件如下 #include local.h
#include utils.husing namespace std;char message[BUF_SIZE];/*流程调用fork产生两个进程两个进程通过管道进行通信子进程等待客户输入并将客户输入的信息通过管道写给父进程父进程接受服务器的信息并显示将从子进程接受到的信息发送给服务器
*/
int main(int argc, char *argv[])
{int sock, pid, pipe_fd[2], epfd;struct sockaddr_in addr;addr.sin_family PF_INET;addr.sin_port htons(SERVER_PORT);addr.sin_addr.s_addr inet_addr(SERVER_HOST);static struct epoll_event ev, events[2]; ev.events EPOLLIN | EPOLLET;//退出标志int continue_to_work 1;CHK2(sock,socket(PF_INET, SOCK_STREAM, 0));CHK(connect(sock, (struct sockaddr *)addr, sizeof(addr)) 0);CHK(pipe(pipe_fd));CHK2(epfd,epoll_create(EPOLL_SIZE));ev.data.fd sock;CHK(epoll_ctl(epfd, EPOLL_CTL_ADD, sock, ev));ev.data.fd pipe_fd[0];CHK(epoll_ctl(epfd, EPOLL_CTL_ADD, pipe_fd[0], ev));// 调用fork产生两个进程CHK2(pid,fork());switch(pid){case 0: // 子进程close(pipe_fd[0]); // 关闭读端printf(Enter exit to exit\n);while(continue_to_work){bzero(message, BUF_SIZE);fgets(message, BUF_SIZE, stdin);// 当收到exit命令时退出if(strncasecmp(message, CMD_EXIT, strlen(CMD_EXIT)) 0){continue_to_work 0;}else{ CHK(write(pipe_fd[1], message, strlen(message) - 1));}}break;default: // 父进程close(pipe_fd[1]); // 关闭写端int epoll_events_count, res;while(continue_to_work) {CHK2(epoll_events_count,epoll_wait(epfd, events, 2, EPOLL_RUN_TIMEOUT));for(int i 0; i epoll_events_count ; i){bzero(message, BUF_SIZE);if(events[i].data.fd sock) //从服务器接受信息{CHK2(res,recv(sock, message, BUF_SIZE, 0));if(res 0) //服务器已关闭{CHK(close(sock));continue_to_work 0;}else {printf(%s\n, message);}}else //从子进程接受信息{CHK2(res, read(events[i].data.fd, message, BUF_SIZE));if(res 0){continue_to_work 0; }else{CHK(send(sock, message, BUF_SIZE, 0));}}}}}if(pid){close(pipe_fd[0]);close(sock);}else{close(pipe_fd[1]);}return 0;
}