epoll的实现与深入思考

提契

纸上得来终觉浅,绝知此事要躬行。

正文

前段时间写了一篇epoll的学习文章,但没有自己的心得总觉得比较肤浅,花了一些时间补充一个epoll的实例,并浅析一下过程中遇到的问题。

上epoll_server的例子,epoll的代码都在这里

1 #include<iostream>
  2 #include<stdlib.h>
  3 #include<sys/epoll.h>
  4 #include<sys/socket.h>
  5 #include<netinet/in.h>
  6 #include<sys/types.h>
  7 #include<fcntl.h>
  8 
  9 using namespace std;
 10 const int PORT = 8888;
 11 const int MAX_CLIENT_NUM = 10000;
 12 const int MAX_LEN = 2000;
 13 
 14 bool setfdnoblock(int fd)
 15 {
 16     int flg = fcntl(fd, F_GETFL);
 17     if(flg < 0)
 18     {
 19         cout << "get fd flag failed" << endl;
 20         return false;
 21     }
 22     if(fcntl(fd, F_SETFL, O_NONBLOCK | flg) < 0)
 23     {
 24         return false;
 25     }
 26     return true;
 27 }
 28 
 29 int CreateTcpServer(int port, int listennum)
 30 {
 31     int fd;
 32     fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 33 
 34     sockaddr_in TcpServer;
 35     bzero(&TcpServer, sizeof(TcpServer));
 36     TcpServer.sin_family = AF_INET;
 37     TcpServer.sin_port = htons(8888);
 38     TcpServer.sin_addr.s_addr = htonl(INADDR_ANY);
 39 
 40     int iRet = bind(fd, (struct sockaddr*)&TcpServer, sizeof(TcpServer));
 41     if(-1 == iRet)
 42     {
 43     cout << "server bind error!" << endl;
 44     return -1;
 45     }
 46     if(listen(fd, listennum) == -1)
 47     {
 48         cout << "server listen error" << endl;
 49         return -1;
 50     }
 51     return fd;
 52 }
 53 
 54 int main()
 55 {
 56     int Serverfd = CreateTcpServer(PORT, MAX_CLIENT_NUM);
 57     if(Serverfd == -1)
 58     {
 59         cout << "server create failed" << endl;
 60     }
 61     else
 62     {
 63        cout << "serverfd is :" << Serverfd << endl;
 64     }
 65 
 66     int Epollfd = epoll_create(MAX_CLIENT_NUM);
 67     if(Epollfd == -1)
 68     {
 69         cout << "epoll_create failed" << endl;
 70     }
 71     epoll_event ev, events[MAX_CLIENT_NUM];
 72     int nfds = 0;
 73     int client = 0;
 74     char buff[MAX_LEN];
 75     sockaddr_in CliAddr;
 76     unsigned int iCliSize = sizeof(CliAddr);
 77     ev.events = EPOLLIN|EPOLLOUT;
 78     ev.data.fd = Serverfd;
 79     if(!setfdnoblock(Serverfd))
 80     {
 81         cout << "set serverfd no_block failed" << endl;
 82     }
 83     if(epoll_ctl(Epollfd, EPOLL_CTL_ADD, Serverfd, &ev))
 84     {
 85         cout << "epoll add serverfd error" << endl;
 86     }
 87     while(1)
 88     {
 89         nfds = epoll_wait(Epollfd, events, MAX_CLIENT_NUM, 100000);
 90         if(nfds == -1)
 91         {
 92             cout << "error occur, exit" << endl;
 93             return -1;
 94         }
 95         else if( nfds == 0)
 96         {
 97             cout << "epoll_wait return zero" << endl;
 98         }
 99         else
100         {
101             for(int i = 0; i < nfds; i++)
102             {
103                 cout << "events[i].data.fd is :" << events[i].data.fd << endl;
104                 if(events[i].data.fd == Serverfd)
105                 {
106                     cout << " Serverfd received event" << endl;
107                     client = accept(Serverfd, (struct sockaddr*)&CliAddr, &iCliSize);
108                     if(client == -1)
109                     {
110                         cout << "accept error" << endl;
111                         return -1;
112                     }
113                     ev.data.fd = client;
114                     if(!setfdnoblock(client))
115                     {
116                         cout << "set client fd no_block error" << endl;
117                     }
118                     if(epoll_ctl(Epollfd, EPOLL_CTL_ADD, client, &ev))
119                     {
120                         cout << "epoll add client error" << endl;
121                     }
122                     else
123                     {
124                         cout << "success add client" << endl;
125                     }
126                 }
127                 else if(events[i].events&EPOLLIN)
128                 {
129                     cout << "recv client msg" << endl;
130                     if(events[i].data.fd < 0)
131                     {
132                         cout << " event[i].data.fd is smaller than zero" << endl;
133                         continue;
134                     }
135                     if(read(events[i].data.fd, buff, MAX_LEN) == -1)
136                     {
137                         perror("clifd read");
138                     }
139                     else
140                     {
141                         cout << "read client msg suc" << endl;
142                         printf("%s",buff);
143                     }
144                     char resp[] = "recv a client msg, this is resp msg";
145                     write(events[i].data.fd, resp, strlen(resp)+1);
146                     //read and mod
147                 }
148                 else if(events[i].events&EPOLLOUT)
149                 {
150                     //send and mod
151                 }
152             }
153         }
154     }
155 }

下面是一个TCP的client例子

1 #include<sys/types.h>
 2 #include<sys/socket.h>
 3 #include<netinet/in.h>
 4 #include<stdlib.h>
 5 #include<arpa/inet.h>
 6 #include<stdio.h>
 7 #include<string.h>
 8 #include<iostream>
 9 
10 using namespace std;
11 const int MAX_BUFF=2048;
12 char notify[] = "i‘m client";
13 
14 int main()
15 {
16     int Clifd;
17     char buff[MAX_BUFF];
18     Clifd = socket(AF_INET, SOCK_STREAM, 0);
19     if(Clifd == -1)
20     {
21         perror("clifd socket");
22     }
23     sockaddr_in CliSock;
24     bzero(&CliSock, sizeof(CliSock));
25     CliSock.sin_family = AF_INET;
26     CliSock.sin_addr.s_addr = inet_addr("203.195.243.17");
27     CliSock.sin_port = htons(8888);
28 
29     if(-1 == connect(Clifd, (struct sockaddr*)&CliSock, sizeof(CliSock)))
30     {
31         perror("clifd connect");
32     }
33     if(write(Clifd, notify, strlen(notify)+1) == -1)
34     {
35         perror("clifd write");
36     }
37     cout << "write over" << endl;
38     if(read(Clifd, buff, MAX_BUFF) == -1)
39     {
40         perror("clifd read");
41     }
42     else
43     {
44         cout << buff << endl;
45     }
46 }

比较简单的代码,算是epoll的典型框架,不再解释代码,将编码过程犯得几个错拿出来供大家借鉴:

1.epoll_ctl(Epollfd, EPOLL_CTL_ADD, Serverfd, &ev)添加Serverfd至epoll监视列表时ev.data.fd并没有设置等于serverfd,结果客户端来连接的时候不停收到EPOLLIN消息,但events[i].data.fd == Serverfd却从不成立,在epoll_ctl之前添加ev.data.fd = Serverfd解决问题,对此我的理解是epoll_ctl将第三参数Serverfd加入监视列表,并将ev结构体与Serverfd关联起来,但epoll不会主动监测和修改ev的其他参数,在epoll_wait时epoll监测到Serverfd上有可读的消息就返回,并将Serverfd关联的ev结构体赋值给events这个事件列表,所以events[i].data.fd其实就是epoll_ctl注册时的ev.data.fd。

2.当client代码调用connect以后立即返回了连接建立成功的结果,此时epoll_server的accept函数还没有调用,那么TCP的连接建立原理是怎样的?

从这张图片来看,TCP的server端在listen以后会被动接受client的请求并建立连接,accept和建立连接没有直接关系,accept只是从serverfd上获取到请求建连的client信息,accept之前已经完成了链路的创建。

延伸话题

同步/异步与阻塞/非阻塞

同步/异步
    同步和异步往往和线程有关系,比如SendMsg,线程A->线程B,发送消息后需要等待其他线程或进程的响应。
    如果SendMsg以后线程A等待线程B返回响应消息,线程才继续处理,这就是同步
    如果SendMsg以后线程A就继续做自己的事情,而注册了一个回调或者响应线程来处理线程B的响应消息,这就是异步
    同步往往是阻塞的
    异步往往是非阻塞的

阻塞/非阻塞
    阻塞read:线程将阻塞,直到有可读的数据。
    非阻塞read:设置O_NOBLOCK以后,如果fd没有可读的数据,read将立即返回-1并设errno为EAGAIN。
    在有可读数据时阻塞与非阻塞read是一样的。
    阻塞write:data从user-mode空间move到kernel-mode空间,之后系统完成kernel-mode到物理缓冲的处理并返回,然后阻塞IO返回。
    非阻塞write:data从user-mode空间move到kernel-mode空间,write返回。

小结

epoll的延伸应用、同步异步、阻塞非阻塞都可以谈很多,以后写对应的文章进行详解。

补充

腾讯云服务器真是一台裸机,ftp服务没有打开,默认只能ssh登陆root账户,man手册用法有点奇怪,用了一天以后还报了hostname错误和eth1找不到网络设备,感觉不是很成熟的产品。

epoll的实现与深入思考

时间: 2024-10-21 16:03:56

epoll的实现与深入思考的相关文章

关于Android中为什么主线程不会因为Looper.loop()里的死循环卡死?引发的思考,事实可能不是一个 epoll 那么 简单。

( 转载请务必标明出处:http://www.cnblogs.com/linguanh/, 本文出自:[林冠宏(指尖下的幽灵)的博客]) 前序 本文将会把一下三个问题阐述清楚以及一个网上的普遍观点的补充: 1,安卓 APP 启动过程,对于Activity 的 onCreate 等生命周期的函数为什么不会因为 Looper.loop()里的死循环卡死而永无机会执行. 2,在 1 的基础上,View 的绘制到底是怎样完成的,它又为什么不会因为 Looper.loop()里的死循环卡死而永无机会刷新.

I/O多路复用之epoll

1.select.poll的些许缺点 先回忆下select和poll的接口 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); int poll(struct pollfd *fds, nfds_t nfds, int timeout); 这两个多路复用实现的特点是: 每次调用select和poll都要把用户关心的事件集合(select为readf

游戏服务器架构的思考

时间总是在不经意的时候就流走了,突然回想我已经做了四年游戏开发,经历了几个游戏项目,以前项目中的游戏服务器框架都不是我心中理想的框架,虽然不知道是不是我见识还不够.下面记录下我对游戏服务器架构的简单思考.好的游戏框架可以提高开发效率,节省人力成本.首先最简单的服务器框架,那就是只要一个网关和一个游戏服务器.如图: 图中agentserver负责客户端连接,客户端收发数据,将客户端数据转发给服务器,将服务器数据转发给客户端,几乎没有逻辑,这样可以应对大并发io.所以agent可以采用一个epoll

Linux内核中网络数据包的接收-第二部分 select/poll/epoll

和前面文章的第一部分一样,这些文字是为了帮别人或者自己理清思路的,而不是所谓的源码分析,想分析源码的,还是直接debug源码最好,看任何文档以及书都是下策.因此这类帮人理清思路的文章尽可能的记成流水的方式,尽可能的简单明了. Linux 2.6+内核的wakeup callback机制 Linux 内核通过睡眠队列来组织所有等待某个事件的task,而wakeup机制则可以异步唤醒整个睡眠队列上的task,每一个睡眠队列上的节点都拥有一个 callback,wakeup逻辑在唤醒睡眠队列时,会遍历

关于高性能网络编程的一些总结与思考

从IO说起 一直以来我很欣赏Google I/O大会,一方面是由于一些新技术与新产品的介绍让人兴奋,更重要的是我太爱这个名字了^_^,我觉得正好符合了我对整个互联网技术的浅薄思考与认识.I/O系统就是整个互联网的关键接口,整个互联网的底层就是I/O系统织成的抽象意义上的net,建立在物理线路之上.而网络编程显然是合理创造与利用I/O的重要工具.而以太网的TCP/UDP则在通常的网络编程中占据了核心的地位.本文主要基于Linux平台,其他如Windows NT以及一些Unix衍生版都提供了对应的解

详细说说select poll epoll

(以下内容来自网络和自己的总结,再次感谢网络中的大神们提供的见解) 在探索select poll  epoll之前我们首先要知道什么叫多路复用: 下来探索一下为什么会用到多路复用: 首先我们看看一个客户端请求服务器的完整过程.首先,请求过来,要建立连接,然后再接收数据,接收数据后,再发送数据. 具体到系统底层,就是读写事件,而当读写事件没有准备好时,必然不可操作,如果不用非阻塞的方式来调用,那就得阻塞调用了,事件没有准备好,那就只能等了,等事件准备好了,你再继续吧.阻塞调用会进入内核等待,cpu

accept与epoll惊群 转载

今天打开 OneNote,发现里面躺着一篇很久以前写的笔记,现在将它贴出来. 1. 什么叫惊群现象 首先,我们看看维基百科对惊群的定义: The thundering herd problem occurs when a large number of processes waiting for an event are awoken when that event occurs, but only one process is able to proceed at a time. After

分布式MySQL集群方案的探索与思考

转载:http://www.infoq.com/cn/articles/exploration-of-distributed-mysql-cluster-scheme?utm_campaign=rightbar_v2&utm_source=infoq&utm_medium=articles_link&utm_content=link_text 背景 数据库作为一个非常基础的系统,任何一家互联网公司都会使用,数据库产品也很多,有Oracle.SQL Server .MySQL.Pos

nginx 并发数问题思考:worker_connections,worker_processes与 max clients

我相信,很多人都跟我一样,看书都不会太细致也不太认真思考,感觉书中讲的东西都应该是对的,最近读书时我发现以前认为理所当然的东西事实上压根都没有弄明白,最终的结果是,书是别人的,书中的知识也是别人的. 无论是看过的nginx有关书还是网上看到的有关nginx 配置说明的文章(http://wiki.nginx.org/EventsModule#worker_connections),无一例外,在讲 到 worker_connections 和 max_clients这两个概念的关系时都一致的一笔带