(OK) Linux epoll模型—socket epoll server client chat
http://blog.csdn.net/denkensk/article/details/41978015
定义:
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++写的,server主要是用epoll模型实现。支持高并发,我測试在有10000个client连接server的时候。server处理时间不到1秒,当然client仅仅是与server连接之后,接受server的欢迎消息而已,并没有做其它的通信。
尽管程序比較简单。可是在我们考虑server高并发时也提供了一个思路。在这个程序中,我已经把全部的调试信息和一些与epoll无关的信息干掉。并加入必要的凝视,应该非常easy理解。
程序共包括3个头文件和3个cpp文件。当中3个cpp文件里,每个cpp文件都是一个应用程序,server.cpp:server程序。client.cpp:单个client程序,tester.cpp:模拟高并发,开启10000个client去连server。
utils.h头文件,就包括一个设置socket为不堵塞函数。例如以下:
#include <fcntl.h>int setnonblocking(int sockfd){CHK(fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0) | O_NONBLOCK));return 0;}
local.h头文件,一些常量的定义和函数的声明,例如以下:
#include "head.h"#define BUF_SIZE 1024 //默认缓冲区#define SERVER_PORT 11111 //监听port#define SERVER_HOST "127.0.0.1" //服务器IP地址#define EPOLL_RUN_TIMEOUT -1 //epoll的超时时间#define EPOLL_SIZE 10000 //epoll监听的client的最大数目#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 expect 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);}int setnoblock(int sockfd);int handle_message(int new_fd);
head.h文件。程序中所要用到的头文件,例如以下:
//#ifndef __HEAD__H__//#define __HEAD__H__#include <stdio.h>#include <iostream>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/socket.h>#include <unistd.h>#include <sys/epoll.h>#include <stdlib.h>#include <list>#include <string.h>#include <iterator>#include <time.h>//#endif
server.cpp文件,epoll模型就在这里实现,例如以下:
#include "local.h"#include "utils.h"#include "head.h"using namespace std;// 存放客户端socket描写叙述符的listlist < int >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);list < int >::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文件,模拟server的高并发,开启10000个client去连接server。例如以下:
#include "local.h"#include "utils.h"#include "head.h"using namespace std;char message[BUF_SIZE]; //接受服务器信息list < int >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 i = 0; i < EPOLL_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);}list < int >::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无压力呀
单个客户端去连接server。client.cpp文件,例如以下:
#include "local.h"#include "utils.h"#include "head.h"using namespace std;char message[BUF_SIZE];/*流程:调用fork产生两个进程,两个进程通过管道进行通信子进程:等待客户输入,并将客户输入的信息通过管道写给父进程父进程:接受server的信息并显示,将从子进程接受到的信息发送给server*/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) //从server接受信息{CHK2(res, recv(sock, message, BUF_SIZE, 0));if (res == 0) //server已关闭{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;}
源码下载:
linux-epoll-server-client-chat.tar.gz
[root@localhost test]# g++ server.cpp -o server
[root@localhost test]# g++ tester.cpp -o tester
[root@localhost test]# g++ client.cpp -o client
注意:
linux下输入命令ulimit -a,能够看到open files 一般时1024,所以未经改动时做本实验,最大打开到1023号。所以要改动。
改动方法:ulimit -n <能够同一时候打开的文件数目> 设置用户能够同一时候打开的最大文件数
假设本參数设置过小,对于并发訪问量大的站点,可能会出现too many open files 的错误。
(OK) Linux epoll模型—socket epoll server client chat相关推荐
- epoll 入门例子 tcp server/client
原文: https://rebootcat.com/2020/09/26/epoll_examples/ 复习一下 上一篇博文 epoll原理深入分析 详细分析了 epoll 底层的实现原理,如果对 ...
- Linux操作系统之简易实现server/client
1.首先将出错处理封装成函数,具体封装思想:是在底层函数的基础上将首字母大写(方便在编程中查找函数文件的manpage,而不需要使用命令,直接用shift+k),然后调用底层库函数并考虑出错处理,具体 ...
- Linux网络编程---I/O复用模型之epoll
https://blog.csdn.net/men_wen/article/details/53456474 Linux网络编程-I/O复用模型之epoll 1. epoll模型简介 epoll是Li ...
- LinuxI/O多路复用转接服务器——epoll模型实现
LinuxI/O多路复用转接服务器--epoll模型实现 epoll函数 epoll函数组 epoll_create函数 epoll_ctl函数 epoll_wait函数 epoll实现实现I/O多路 ...
- Redis的epoll模型
之前相关文章推荐:Redis高性能与epoll 本文,我们从源代码的角度,简单理解Redis是如何使用epoll以及epoll的实现原理.浅入浅出~ 找我交流 通过本文了解如下三件事儿,就算是达到了本 ...
- 了解epoll模型的作用原理
目录 一.事件就绪前的准备工作 1.创建epoll模型 2.添加文件描述符及监听事件 (1) 向红黑树中添加结点 (2) 为该结点建立对应的回调策略 二.事件就绪后的处理工作 1.外设拷贝数据到内核 ...
- Linux网络服务器epoll模型的socket通讯的实现(一)
准备写一个网络游戏的服务器的通讯模块,参考网上看到的一些代码,在linux下面实现一个多线程的epoll模型的socket通讯的代码,以下是第一部分多线程的切换代码: 1 #include <s ...
- linux socket编程epoll模型实现群发消息
1.实现功能 本代码主要实现了socket编程epoll模型实现多个客户端连接服务器,客户端可以进行群发消息和接收用户输入文本信息,然后发送该信息给服务器,服务器收到后发送应答信息.客户端接收并显示该 ...
- linux线程同步 epoll,Linux网络编程--epoll 模型原理详解以及实例
1.简介 Linux I/O多路复用技术在比较多的TCP网络服务器中有使用,即比较多的用到select函数.Linux 2.6内核中有提高网络I/O性能的新方法,即epoll . epoll是什么?按 ...
- socket epoll模型
linux 使用epoll主要目的是啥 为了实现非阻塞么? socket本来就有阻塞和非阻塞两种模式,与epoll无关. epoll是针对多socket操作(从select升级到poll再到epoll ...
最新文章
- Strategy_Requirement1
- asp.net core学习笔记
- 发布一个定制的webpart:R***eader
- CMD中文乱码出现的原因及解决办法
- [转载] java中数组的反射的探究
- python | while循环与for循环 | 循环嵌套 | pass通用类型,循环整体结束或开始下一轮循环
- 程序在Linux下后台运行,进程查看及终止
- Python 揭秘斐波那契定律,如何帮助码农分析股票?| 技术头条
- 信息系统项目管理系列之六:项目范围管理
- .net 2.0 只读TextBox取值问题
- html同时用多个css,多类选择器的运用_html/css_WEB-ITnose
- 以太坊智能合约开发第五篇:字符串拼接—Solidity
- 新面貌,新征程—读《新程序员》有感
- 单表七千六百万数据量(oracle)进行实时汇总,sql很慢客户不能忍一下,该怎么办?
- 第七章 本源时空(补充)
- TensorFlow练习6: 基于WiFi指纹的室内定位(autoencoder)
- Hexo个人博客绑定域名
- C++ COM组件编写初探(上)
- CP Editor安装配置
- 【GlobalMapper精品教程】050:点线面缓冲区分析案例
热门文章
- Atitit etl之道 attilax著 1. ETL	1 1.1. (数据仓库技术)	2 1.2. ETL的质量问题具体表现为正确性、完整性、一致性、完备性、有效性、时效性和可获取性等几个特性
- Atitit.java图片图像处理attilax总结
- paip.SVN无法提交--提示冲突的解决
- (转载)lib 和 dll 的区别、生成以及使用详解
- joinquant : 通过query获取申万各级指数行情、估值等数据
- Jibun 银行:一家纯网络银行,利用智能手机打开金融服务领域的新天地
- 机器学习笔记(二十四):召回率、混淆矩阵
- 三年磨一剑,钉的真好听 | 凌云时刻
- 木兰宽松许可证(MulanPSL v2)解析
- java tsp问题_蚁群算法(Java)tsp问题