epoll用法整理 实现回声服务端

http://blog.csdn.net/chenxun_2010/article/details/50493481

1、epoll是什么?

epoll是当前在Linux下开发大规模并发网络程序的热门人选,epoll Linux2.6内核中正式引入,和select相似,都是I/O多路复用(IO multiplexing)技术

Linux下设计并发网络程序,常用的模型有:

      Apache模型(Process Per Connection,简称PPC

     TPCThread PerConnection)模型

     select模型和poll模型。

     epoll模型

2、常用模型的缺点

   PPC/TPC模型

       这两种模型思想类似,就是让每一个到来的连接都有一个进程/线程来服务。这种模型的代价是它要时间和空间。连接较多时,进程/线程切换的开销比较大。因此这类模型能接受的最大连接数都不会高,一般在几百个左右。

   select模型

          最大并发数限制:因为一个进程所打开的fd(文件描述符)是有限制的,由FD_SETSIZE设置,默认值是1024/2048,因此select模型的最大并发数就被相应限制了。

          效率问题select每次调用都会线性扫描全部fd集合,这样效率就会呈现线性下降,FD_SETSIZE改大可能造成这些fd都超时了。

          内核/用户空间内存拷贝问题:如何让内核把fd消息通知给用户空间呢?在这个问题上select采取了内存拷贝方法。

    poll模型

         基本上效率和select是相同的,select缺点的23它都没有改掉。

    epoll的改进

对比其他模型的问题,epoll的改进如下:

       epoll没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于2048, 一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。

       效率提升,Epoll最大的优点就在于它只管你活跃的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于selectpoll

       内存拷贝,Epoll在这点上使用了“共享内存”,这个内存拷贝也省略了。

 

3、 epoll为什么高效

epoll的高效和其数据结构的设计是密不可分的。

首先回忆一下select模型,当有I/O事件到来时,select通知应用程序有事件到了,应用程序必须轮询所有的fd集合,测试每个fd是否有事件发生,并处理事件;代码像下面这样:

int  res = select(maxfd+1, &readfds, NULL, NULL, 120);

if (res > 0)

{

    for (int i = 0; i < MAX_CONNECTION; i++)

    {

        if (FD_ISSET(allConnection[i],&readfds))

        {

            handleEvent(allConnection[i]);

        }

    }

}

// if(res == 0) handle timeout, res < 0 handle error

 

epoll不仅会告诉应用程序有I/0事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个fd集合。

int res = epoll_wait(epfd, events, 20, 120);

for (int i = 0; i < res;i++)

{

    handleEvent(events[n]);

}

 epoll关键数据结构

前面提到epoll速度快和其数据结构密不可分,其关键数据结构就是:

struct  epoll_event {

   __uint32_t      events;  //epoll events

   epoll_data_t   data;   //user data variable

};

 

typedef  union epoll_data {

   void*   ptr;

   int      fd;

   __uint32_t  u32;

   __uint64_t  u64;

}epoll_data_t;

可见epoll_data是一个union结构体,借助于它应用程序可以保存很多类型的信息:fd、指针等等。有了它,应用程序就可以直接定位目标了。

 

使用epoll

epollAPI:

int  epoll_create(int  size);

int  epoll_ctl(int epfd, int op, int fd, structepoll_event *event);

int  epoll_wait(int epfd, struct epoll_event* events, int maxevents. int timeout);

int  epoll_create(int size);


创建一个epoll的文件描述符,参数size告诉内核这个监听的数目共有多大。

  int  epoll_ctl(int epfd, int op, int fd, structepoll_event *event);

     epoll的事件注册函数。

     参数epfd是epoll_create返回值。

    参数op为

          EPOLL_CTL_ADD 注册新的fd到epfd中

          EPOLL_CTL_MOD 修改已经注册的fd的监听事件

          EPOLL_CTL_DEL 从epfd中删除一个fd

参数fd是需要监听文件描述符。

参数event是告诉内核需要监听什么事件。event->events的不同的值表示对应的文件描述符的不同事件:

   EPOLLIN  可以读(包括对端Socket正常关闭)

   EPOLLOUT 可以写

   EPOLLPRI有紧急的数据可读(有带外数据OOB到来,TCP中的URG包)

   EPOLLERR该文件描述符发生错误

   EPOLLHUP该文件描述符被挂断

   EPOLLET     将epoll设置为边缘触发(Edge Triggered)模式。

   EPOLLONESHOT只监听一次事件,监听完之后,如果还想监听需要再次把该文件描述符加入到epoll队列中

 

int  epoll_wait(int epfd, struct epoll_event* events, int maxevents. int timeout);

等待事件的产生。

参数events用来从内核得到事件的集合

参数maxevents告之内核这个events有多大(maxevents不能大于size)

参数timeout是超时时间(毫秒)

 

epoll的模式:

       LT模式:Level Triggered水平触发

            这个是缺省的工作模式。同时支持block socket和non-block socket。内核会告诉程序员一个文件描述符是否就绪了。如果程序员不作任何操作,内核仍会通知。

       ET模式:Edge Triggered 边缘触发

                是一种高速模式。仅当状态发生变化的时候才获得通知。这种模式假定程序员在收到一次通知后能够完整地处理事件,于是内核不再通知这一事件。注意:缓冲区中还          有未处理的数据不算状态变化,所以ET模式下程序员只读取了一部分数据就再也得不到通知了,正确的用法是程序员自己确认读完了所有的字节(一直调用read/write直到          出错EAGAIN为止)。

 

 

一个例子:

[cpp] view plaincopy
  1. #include <netdb.h>  
  2.   
  3. #include <sys/socket.h>  
  4.   
  5. #include <sys/epoll.h>  
  6.   
  7. #include <netinet/in.h>  
  8.   
  9. #include <arpa/inet.h>  
  10.   
  11. #include <fcntl.h>  
  12.   
  13. #include <unistd.h>  
  14.   
  15. #include <stdio.h>  
  16.   
  17. #include <string.h>  
  18.   
  19. #include <stdlib.h>  
  20.   
  21. #include <errno.h>  
  22.   
  23.    
  24.   
  25. /*创建并绑定一个socket作为服务器。 */  
  26.   
  27. static int  create_and_bind (char *port){  
  28.   
  29.     struct  addrinfo hints;  
  30.   
  31.     struct  addrinfo *result, *rp;  
  32.   
  33.     int  s, sfd;  
  34.   
  35.     memset (&hints, 0, sizeof (struct addrinfo));  
  36.   
  37.     hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */  
  38.   
  39.     hints.ai_socktype = SOCK_STREAM; /* 设置为STREAM模式,即TCP链接 */  
  40.   
  41.     hints.ai_flags = AI_PASSIVE;     /* All interfaces */  
  42.   
  43.     s = getaddrinfo (NULL, port, &hints, &result);//获得本地主机的地址  
  44.   
  45.     if (s != 0){  
  46.   
  47.         fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));  
  48.   
  49.         return -1;  
  50.   
  51.     }  
  52.   
  53.     for (rp = result; rp != NULL; rp = rp->ai_next){//本地主机地址可能有多个,任意绑定一个即可  
  54.   
  55.         sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol); //创建socket  
  56.   
  57.         if (sfd == -1)  
  58.   
  59.             continue;  
  60.   
  61.         s = bind (sfd, rp->ai_addr, rp->ai_addrlen); //并绑定socket  
  62.   
  63.         if (s == 0)  
  64.   
  65.         {  
  66.   
  67.             /* 绑定成功 */  
  68.   
  69.             break;  
  70.   
  71.         }  
  72.   
  73.         close (sfd);  
  74.   
  75.     }  
  76.   
  77.     if (rp == NULL){  
  78.   
  79.         fprintf (stderr, "Could not bind\n");  
  80.   
  81.         return -1;  
  82.   
  83.     }  
  84.   
  85.     freeaddrinfo (result);  
  86.   
  87.     return sfd;  
  88.   
  89. }  
  90.   
  91.    
  92.   
  93. /* 
  94.  
  95.    设置socket为非阻塞模式。 
  96.  
  97.    先get flag,或上O_NONBLOCK 再set flag。 
  98.  
  99.  */  
  100.   
  101. static  int   make_socket_non_blocking (int sfd) {  
  102.   
  103.    
  104.   
  105.     int flags, s;  
  106.   
  107.     flags = fcntl (sfd, F_GETFL, 0);  
  108.   
  109.     if (flags == -1){  
  110.   
  111.         perror ("fcntl");  
  112.   
  113.         return -1;  
  114.   
  115.     }  
  116.   
  117.     flags |= O_NONBLOCK;  
  118.   
  119.     s = fcntl (sfd, F_SETFL, flags);  
  120.   
  121.     if (s == -1){  
  122.   
  123.         perror ("fcntl");  
  124.   
  125.         return -1;  
  126.   
  127.     }  
  128.   
  129.     return 0;  
  130.   
  131. }  
  132.   
  133.    
  134.   
  135.    
  136.   
  137. #define  MAXEVENTS 64  
  138.   
  139.    
  140.   
  141. /* 
  142.  
  143.    用法: ./epoll_test 8080 
  144.  
  145.  */  
  146.   
  147. int  main (int argc, char *argv[]) {  
  148.   
  149.     int sfd, s;  
  150.   
  151.     int efd;  
  152.   
  153.     struct  epoll_event event;  
  154.   
  155.     struct  epoll_event *events;  
  156.   
  157.     if (argc != 2) {  
  158.   
  159.         fprintf (stderr, "Usage: %s [port]\n", argv[0]);  
  160.   
  161.         exit (EXIT_FAILURE);  
  162.   
  163.     }  
  164.   
  165.     sfd = create_and_bind (argv[1]); //sfd为绑定后等待连接接入的文件描述符  
  166.   
  167.    
  168.   
  169.     s = make_socket_non_blocking (sfd);  
  170.   
  171.     s = listen (sfd, SOMAXCONN);  
  172.   
  173.     efd = epoll_create1 (0);  
  174.   
  175.     event.data.fd = sfd;  
  176.   
  177.     event.events = EPOLLIN | EPOLLET;  
  178.   
  179.     s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);  
  180.   
  181.     /* Buffer where events are returned,为events数组分配内存 */  
  182.   
  183.     events = (struct  epoll_event*)calloc (MAXEVENTS, sizeof event);  
  184.   
  185.     /* The event loop 事件循环*/  
  186.   
  187.     while (1) {  
  188.   
  189.         int n, i;  
  190.   
  191.         n = epoll_wait (efd, events, MAXEVENTS, -1);  
  192.   
  193.         for (i = 0; i < n; i++) {  
  194.   
  195.             if ((events[i].events & EPOLLERR) ||  (events[i].events & EPOLLHUP) || (!(events[i].events & EPOLLIN))) {  
  196.   
  197.               /* 发生了错误或者被挂断,或者没有数据可读  An error has occured on this fd, or the socket is not ready for reading (why were we notified then?) */  
  198.   
  199.                 fprintf (stderr, "epoll error\n");  
  200.   
  201.                 close (events[i].data.fd);  
  202.   
  203.                 continue;  
  204.   
  205.             }else if (sfd == events[i].data.fd) {//新连接  
  206.   
  207.               /* sfd上有数据可读,则表示有新连接 
  208.  
  209.                * We have a notification on the listening socket, 
  210.  
  211.                * which means one or more incoming connections. */  
  212.   
  213.                 printf("Incoming connection !\n");  
  214.   
  215.                 while (1) {  
  216.   
  217.                     struct sockaddr in_addr;  
  218.   
  219.                     socklen_t in_len;  
  220.   
  221.                     int infd;  
  222.   
  223.                     char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];  
  224.   
  225.                     in_len = sizeof in_addr;  
  226.   
  227.                     infd = accept (sfd, &in_addr, &in_len); //读取到来的连接socket fd。  
  228.   
  229.                     if (infd == -1) {  
  230.   
  231.                         if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {  
  232.   
  233.                             /* 已经读完了sfd上的所有数据(所有连接)。最后一次读(非阻塞读)会返回EAGAIN(=EWOULDBLOCK) 
  234.  
  235.                              * We have processed all incoming connections. */  
  236.   
  237.                             break;  
  238.   
  239.                         } else  {  
  240.   
  241.                             perror ("accept");  
  242.   
  243.                             break;  
  244.   
  245.                         }  
  246.   
  247.                     }  
  248.   
  249.                     s = getnameinfo (&in_addr, in_len, hbuf, sizeof hbuf, sbuf, sizeof sbuf, NI_NUMERICHOST | NI_NUMERICSERV);  
  250.   
  251.                     if (s == 0) {  
  252.   
  253.                         printf("Accepted connection on descriptor %d (host=%s, port=%s)\n", infd, hbuf, sbuf);  
  254.   
  255.                     }  
  256.   
  257.                     s = make_socket_non_blocking (infd);  //设置socket为非阻塞模式  
  258.   
  259.                     event.data.fd = infd;  //将data部分设置为fd  
  260.   
  261.                     event.events = EPOLLIN | EPOLLET;  //监听EPOLLIN事件,使用边缘触发模式  
  262.   
  263.                     s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);  
  264.   
  265.                 }  
  266.   
  267.                 continue;  
  268.   
  269.             } else   
  270.             {//有客户端发来数据  
  271.   
  272.                 /* 有客户端发来数据,因为处于ET模式,所以必须完全读取所有数据(要不然,剩下一部分数据后,就无法再收到内核通知了)。*/  
  273.   
  274.                 int conn = events[i].data.fd;  
  275.                 if (conn < 0)  
  276.                     continue;  
  277.   
  278.                 char recvbuf[1024] = {0};  
  279.                 int ret = read(conn, recvbuf, 1024);  
  280.                 if (ret == -1)  
  281.                     //ERR_EXIT("readline");  
  282.                 if (ret == 0)  
  283.                 {  
  284.                     printf("client close\n");  
  285.                     close(conn);  
  286.   
  287.                     event = events[i];  
  288.                     epoll_ctl(efd, EPOLL_CTL_DEL, conn, &event);  
  289.                     //clients.erase(std::remove(clients.begin(), clients.end(), conn), clients.end());  
  290.                 }  
  291.   
  292.                 fputs(recvbuf, stdout);  
  293.                 write(conn, recvbuf, strlen(recvbuf));  
  294.   
  295.                   
  296.   
  297.             }  
  298.   
  299.         }  
  300.   
  301.     }  
  302.   
  303.     free (events);//释放内存  
  304.   
  305.     close (sfd);   //关闭sfd  
  306.   
  307.     return EXIT_SUCCESS;  
  308.   
  309. }  
  310.   
  311.    


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383856.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HDU3430-扩展中国剩余定理

刚开始一直把题意看错了。。。体测完智商急剧下降 正确理解题意以后自己写一直wa&#xff0c;而且并不知道是哪里的问题&#xff0c;在网上看了一下其他人写的改了改自己的就过了&#xff0c;可是之前的还是不知道为什么不对。 题意大概就是有一个置换群&#xff0c;问运算多…

linux shell编程多线程和wait命令学习

http://blog.csdn.net/shuanghujushi/article/details/38186303最近在使用shell做一些部署工作&#xff0c;在使用过程中&#xff0c;效率一直不高。想提高效率&#xff0c;经过分析发现&#xff0c;并不是所有操作都是需要串行的&#xff0c;一些操作是可以进行并行操作的。经…

#ifndef的作用

#ifndef是一条预编译指令&#xff0c;就是说实在编译的时候就会运行的指令。这个指令的作用很简单&#xff0c;就是字面意思&#xff0c;如果没有定义的话&#xff0c;但是却经常使用。 因为使用这个可以避免一个源文件中两次两次包含同一个文件&#xff0c;或者一个工程文件中…

C++中结构体和类的区别和联系

最主要的不同点就是结构体的访问权限为public而且不能改变&#xff0c;而类的访问权限可以改变&#xff0c;public的类和结构体基本一样。 继承上同样表现出这样的特点&#xff0c;struct是public继承的&#xff0c;而class是private继承的&#xff0c;继承的子类的访问权限取…

poll函数实现多路复用

http://blog.csdn.net/xluren/article/details/8206371 结构体pollfd struct pollfd { int fd; //file descriptor short event; //event of interest on fd short reven; //event that occurred on fd } 每一个pollfd结构体指定了一个被监视的文件描述符&…

抽象类(纯虚函数、虚函数)和虚基类(虚继承)

C多态 C的多态包括静态多态和动态多态&#xff0c;静态多态包括函数重载和泛型编程&#xff0c;动态多态包括虚函数。静态多态实在编译期间就能确定&#xff0c;动态多态实直在程序运行时才能确定。 抽象类 虚函数 在默认情况下对函数成员调用实施的是静态连编&#xff0c;…

socket通信之最简单的socket通信

http://blog.csdn.net/xluren/article/details/8043484#t15 套接字有三种类型 流式套接字&#xff08;SOCK_STREAM&#xff09;&#xff0c;数据报套接字&#xff08;SOCK_DGRAM&#xff09;及原始套接字。 1.流式套接字提供面向连接、可靠的数据传输服务&#xff0c;数据按字节…

Java环境配置

自己安装的时候按照一般的安装方法先配置了JDK的环境&#xff0c;能够成功显示java版本后我在安装eclipse的时候一直提示错误&#xff1a; Unfortunately the Java version needed to run Eclipse Installer couldn’t be found on your system. You need the following versio…

Linux I/O复用之select函数详解

http://blog.csdn.net/y396397735/article/details/55004775 select函数的功能和调用顺序 使用select函数时统一监视多个文件描述符的&#xff1a; 1、 是否存在套接字接收数据&#xff1f; 2、 无需阻塞传输数据的套接字有哪些? 3、 哪些套接字发生了异常&#xff1f; sel…

【Java学习笔记一】类和对象

面向对象程序设计的一个一个重要特点是&#xff1a;封装性。 这里的封装性有两方面含义&#xff1a;一是将有关的数据和操作代码封装在一个对象中形成一个基本单位&#xff0c;各个对象之间相互独立互不干扰&#xff0c;二是将对象中某些部分对外隐蔽&#xff0c;即隐蔽其内部细…

深入研究socket编程(3)——使用select函数编写客户端和服务器

http://blog.csdn.net/chenxun_2010/article/details/50488394 首先看原先《UNIX网络编程——并发服务器&#xff08;TCP&#xff09;》的代码&#xff0c;服务器代码serv.c&#xff1a; [cpp] view plaincopy #include<stdio.h> #include<sys/types.h> #inclu…

Java简单输入输出

不同于面向过程中有直接的输入输出函数&#xff0c;Java中的输入输出只能通过类来实现。 比较常见的一种是使用Scanner类 需要引入java.util包&#xff0c;即在文件开始加上语句import java.util.*;创建Scanner类对象&#xff0c;属于标准输入流。 例如Scanner snew Scanner(S…

Ubuntu安装搭建Clion环境

呜呜呜&#xff0c;太辛苦了&#xff0c;我终于安装好这个了。 大概过程就是先在官网下载安装包&#xff0c;然后解压以后用终端移动到对应文件夹下运行clin.sh 运行完以后会有一些窗口&#xff0c;第一个选择don’t~~&#xff0c;然后点击ok 接受&#xff08;你可以不接受…

UNIX网络编程——select函数的并发限制和 poll 函数应用举例

http://blog.csdn.net/chenxun_2010/article/details/50489577 一、用select实现的并发服务器&#xff0c;能达到的并发数&#xff0c;受两方面限制 1、一个进程能打开的最大文件描述符限制。这可以通过调整内核参数。可以通过ulimit -n来调整或者使用setrlimit函数设置&#x…

【Java学习笔记二】继承和多态

与C不同的是&#xff0c;在Java中&#xff0c;一个类只能直接继承另一个类&#xff0c;而不允许继承多个类&#xff0c;这个新类称为继承类、派生类或者子类&#xff0c;而被继承的类称为基类或者父类。 继承类能够继承基类的群不属性和行为。 面向对象程序设计的三大特点为&…

使用poll实现的io多路复用服务端和客户端

http://blog.csdn.net/robertkun/article/details/52269313 参考&#xff1a;http://www.cnblogs.com/Anker/p/3261006.html 使用poll实现的io多路复用服务端和客户端。 客户端通过子进程创建多个客户端连接。 客户端每隔1秒向服务端发送一个时间戳&#xff0c; 服务端接收到时…

【Java学习笔记三】抽象类与接口

对象的类型转换分为自动转换和强制转换两种 派生类向基类转换是自动转换&#xff0c;因为派生类中包含基类基类向派生类的转换是强制转换 强制类型转换是通过在转换对象前面使用圆括号运算符来实现&#xff0c;圆括号内为要转换的目标类型&#xff0c;格式为&#xff1a; (&…

Epoll 的tcp通信代码(服务器+客户端)

http://blog.csdn.net/libinbin_1014/article/details/50096187 Epoll 的tcp通信代码&#xff08;服务器客户端&#xff09; /* gcc -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS64 -I${ORACLE_HOME}/rdbms/public -I${ORACLE_HOME}/rdbms/demo -L${ORACLE_HOME}/lib -lclntsh …

【Java学习笔记四】Java中的包

包的声明和引入&#xff1a;在Java语言系统中&#xff0c;Java编译器为每一个类生成一个字节码文件&#xff08;.class&#xff09;&#xff0c;为了对类文件进行分层和按用途分类管理&#xff0c;同时也为了解决相同类名的文件冲突的问题&#xff0c;Java提供了包机制来管理类…

Linux系统编程——线程池

http://blog.csdn.net/tennysonsky/article/details/46490099# 线程池基本原理 在传统服务器结构中&#xff0c;常是有一个总的监听线程监听有没有新的用户连接服务器&#xff0c;每当有一个新的用户进入&#xff0c;服务器就开启一个新的线程用户处理这 个用户的数据包。这个线…