精华内容
下载资源
问答
  • C++ SOCKET通信模型(六)同步epoll
    2017-09-01 21:50:53

    从上篇可以看到,由于每次poll都需要pollfd的数组地址,所以整个设计里面还包含与Client*的对应关系,移除的时候还需要去维护,让人感到十分的不便,就和IOEvent,select一样的感觉。每次还需要把整个集合分配给线程是遍历,虽说比select好,但仍有不足。EPoll就真的很刁了,不会随连接数的增多而有额外的性能损耗,每次分配给线程的是此次需要处理的event,使用起来也非常简单,不再需要自己去维护一个结构数组,其他基本可以和select poll保持一致的基本结构,主要就那几个函数,和ET LT两种模式需要介绍下。

    epoll_create(int size) 这个size现在看来没什么用了,据说在2.4.32 之前epoll是采用哈希表管理所有文件描述符,所以最开始是用来初始化哈希表的。但2.6.10后用的是红黑树管理,所以无论多少都无所谓了。epoll.h 对size的描述如下: The "size" parameter is a hint specifying the number of file descriptors to be associated with the new instance,可见英文翻译过来也是对要关注的描述符数目的暗示,可能是给程序员看的而不具有任何的约束力了。还有就动态创建销毁epoll_create的场景,不用了的话一定要close

    int epoll_ctl (int __epfd, int __op, int __fd,
         struct epoll_event *__event) __THROW; 这个没什么说的,就是注册事件、修改事件、删除事件

    /* Valid opcodes ( "op" parameter ) to issue to epoll_ctl().  */
    #define EPOLL_CTL_ADD 1 /* Add a file descriptor to the interface.  */
    #define EPOLL_CTL_DEL 2 /* Remove a file descriptor from the interface.  */
    #define EPOLL_CTL_MOD 3 /* Change file descriptor epoll_event structure.  */

    int epoll_wait (int __epfd, struct epoll_event *__events,
          int __maxevents, int __timeout);主要就想说下将timeout设为-1的时候 最开始返回-1 errno==4 并且提示interrupt system call ,我查了下,这是因为慢系统调用。适用于慢系统调用的基本规则是:当阻塞于某个慢系统调用的一个进程捕获某个信号且相应处理函数返回时,该系统调用可能返回一个EINTR错误。所以,我们必须对慢系统调用返回的EINTR有所准备。在这个地方直接continue无视掉这个错误就行。


    Edge Triggered (ET)  有信号的时候仅返回一次,需要循环处理recv accept send,必须为非阻塞。ET模式处理起来真的非常麻烦,recv需要判断是否是断开还是接收完成或者接收中途断开,send需要判断缓冲区是否已被写满等等。当然,其实也更加灵活,比如发送缓冲区被写满了,正常来说可能是网络问题,直接将这个socket剔除或者移到阻塞处理策略上,从而不影响其他正常网络连接的用户
    Level Triggered (LT)  只要内核缓冲区有东西,那每次都会返回,同时可以采用阻塞的办法就避免上述的问题


    其实我这代码还有很大的优化空间,可以参照IOCP进一步设计,我这为了简便仍然是同步IO,若想榨干CPU,那就一定得用异步IO进行处理


    #include <iostream>
    #include<list>
    #include<map>
    #include<vector>
    #include<deque>
    #include<mutex>
    #include <atomic>
    #include<condition_variable>
    #include<sys/socket.h>
    #include<sys/epoll.h>
    #include<netinet/in.h>
    #include <pthread.h>
    #include<unistd.h>
    #include<memory.h>
    #include<fcntl.h>
    using namespace std;
    struct Client
    {
    	int id;
    };
    
    void* CreateServ(void* args);
    void* Proc(void* args);
    using namespace std;
    const int _thread_count = 8;
    int _epfd;
    int _events_num = -1;
    int sockSrv;
    int id=0;
    epoll_event _events[1024]={0};
    epoll_event ev[_thread_count];
    mutex m;
    deque<epoll_event*> _deque;
    
    map<int,Client*> _sockMap;
    list<int> _removeList;
    mutex lock4cv;
    mutex lock4cv2;
    condition_variable cv;
    condition_variable cv2;
    int _thread_unfinish;
    vector<int> _vec;
    char* buf2 = "hello client";
    int main()
    {
    	pthread_t tid;
    	for (int i = 0; i < _thread_count; i++)
    	{
    		_vec.push_back(0);
    	}
    	pthread_create(&tid, 0, CreateServ, 0);
    	for (int i = 0; i < _thread_count; i++)
    	{
    		int* temp = new int(i);
    		pthread_create(&tid, 0, Proc, temp);
    	}
    	cin.get();
    	cin.get();
    	return 0;
    }
    bool _isFinish()
    {
    	return _thread_unfinish <= 0;
    }
    
    
    void* Proc(void* args)
    {
    	int* I = (int*)args;
    
    	while (true)
    	{
    		{
    			unique_lock<mutex> l(lock4cv);
    			if (_vec[*I] <= 0)
    			{
    				cv.wait(l);
    			}
    			_vec.at(*I) = 0;
    		}
    		while(true)
    		{
    			m.lock();
    			if(_deque.size()==0)
    			{
    				m.unlock();
    				break;
    			}
    			epoll_event* e = _deque.front();
    			_deque.pop_front();
    			m.unlock();
    
    			if (e->data.fd == -1)
    			{
    				continue;
    			}
    			if (e->data.fd == sockSrv)
    			{
    
    			}
    			else if (e->events == EPOLLIN)
    			{
    				int fd = e->data.fd;
    				cout << "proc by:" << *I << endl;
    				char buf[128];
    				int len = 0;
    				bool needRemove = false;
    				do
    				{
    					int len2 = recv(fd, buf + len, 128, 0);
    					if (len2 <= 0)
    					{
    						if (len2 == 0 || errno == 11)
    						{
    							needRemove = true;
    							break;
    						}
    					}
    					len += len2;
    				} while (len < 128);
    				if (needRemove)
    				{
    					e->data.fd = -1;
    					close(fd);
    					_removeList.push_back(fd);
    					continue;
    				}
    				send(fd, buf2, 128, 0);
    				//cout << buf << endl;
    				e->events = 0;
    			}
    
    		}
    		//此处一定要加锁。当wait还未准备好,但_isFinish刚刚检测完时_thread_unfinish -= 1;cv2.notify_all();这两句恰巧在之后执行,那就GG了,两边进入互相等待的状态
    		lock4cv2.lock();
    		_thread_unfinish -= 1;
    		cv2.notify_all();
    		lock4cv2.unlock();
    	}
    
    }
    void release(int fd)
    {
    	//cout << "release:"<<fd << endl;
    	ev[0].data.fd = fd;
    	epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, &ev[0]);
    	for(auto iter=_sockMap.begin();iter!=_sockMap.end();++iter)
    	{
    		if(iter->first==fd)
    		{
    			delete iter->second;
    			_sockMap.erase(iter);
    			break;
    		}
    	}
    
    }
    
    void* CreateServ(void* args) {
    	sockSrv = socket(AF_INET, SOCK_STREAM, 0);
    
    	int nSendBufLen = 16 * 1024 * 1024;
    	setsockopt(sockSrv, SOL_SOCKET, SO_SNDBUF, (const char*)&nSendBufLen, sizeof(int));
    
    	fcntl(sockSrv, F_SETFL, O_NONBLOCK);
    	//	struct in_addr s;
    	//	inet_pton(AF_INET, "127.0.0.1",(void*)&s);
    	sockaddr_in addrSrv;
    	addrSrv.sin_addr.s_addr = htonl(INADDR_ANY);
    	addrSrv.sin_family = AF_INET;
    	addrSrv.sin_port = htons(6001);
    
    	::bind(sockSrv, (sockaddr*)&addrSrv, sizeof(sockaddr));
    
    	int err = listen(sockSrv, 100);
    	if (err == -1) {
    		cout << "listen failed" << endl;
    		return 0;
    	}
    
    	_epfd=epoll_create(1024);
    	ev[0].data.fd = sockSrv;
    	ev[0].events = EPOLLIN|EPOLLET;
    	epoll_ctl(_epfd, EPOLL_CTL_ADD, sockSrv, &ev[0]);
    	//accept loop
    	while (true) {
    
    		_events_num =epoll_wait(_epfd, _events, 1024, -1);
    
    		if(_events_num<0)
    		{
    			continue;
    		}
    		while (true) {
    			int s = accept(sockSrv, 0, 0);
    			if(s<=0)
    			{
    				break;
    			}
    			fcntl(s, F_SETFL, O_NONBLOCK);
    			Client* c = new Client;
    			c->id = id;
    			id += 1;
    			_sockMap.insert(pair<int, Client*>(s, c));
    			ev[0].data.fd = s;
    			ev[0].events = EPOLLIN | EPOLLET;
    			epoll_ctl(_epfd, EPOLL_CTL_ADD, s, &ev[0]);
    		}
    
    		_thread_unfinish = _thread_count;
    
    		for(int i=0;i<_events_num;i++)
    		{
    			_deque.push_back(&_events[i]);
    		}
    
    		for(int i=0;i<_thread_count;i++)
    		{
    			_vec.at(i) = 1;
    		}
    		cv.notify_all();
    
    		unique_lock<mutex> l(lock4cv2);
    		cv2.wait(l, _isFinish);
    
    		for (auto iter = _removeList.begin(); iter != _removeList.end(); ++iter)
    		{
    			release(*iter);
    			id-=1;
    		}
    		_removeList.clear();
    
    	}
    }



    客户端 与C++ SOCKET通信模型(一)相同




    更多相关内容
  • 说说关于腾讯面试官问的epoll到底是同步的还是异步的? 你认为呢?我们该如何理解从哪个角度哪个层面来理解提问者提出的“同步异步”问题呢? 也许我们应该问一问面试官,请他提供一个具体的讨论背景.或者我们可以...

    UNIX的五种IO模型 

    POSIX defines these two terms as follows:
    · A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes.
    · An asynchronous I/O operation does not cause the requesting process to be blocked.
    Using these definitions, 
    the first four I/O models—blocking, nonblocking, I/O multiplexing, and signal-driven I/O—are all synchronous because
     the actual I/O operation (recvfrom) blocks the process. Only the asynchronous I/O model matches the asynchronous 
    I/O definition.
    
    <UNIX网络编程>> 第三版 6.2节
    
    
    这段是关于IO同步异步的描述,书中说到:
    一个同步的IO操作使得请求进程一直被阻塞,直到IO操作完成;
    一个异步的IO操作不会导致请求进程被阻塞.
    
    说说关于腾讯面试官问的epoll到底是同步的还是异步的?
    你认为呢?我们该如何理解从哪个角度哪个层面来理解提问者提出的“同步异步”问题呢?
    也许我们应该问一问面试官,请他提供一个具体的讨论背景.或者我们可以直接从不同维度
    给出相对较全面的答案.
    
    
    
    IO层面
    消息处理层面
    从IO层面来看,epoll绝对是同步的;
    从消息处理层面来看,epoll是异步的.
    
    作为提问者,也许我们可以提出这样一个含糊的问题,但是作为答题者和一个研究者,也许我们需要
    更加全面的了解这其中的方方面面,从不同角度来思考提问者给我们抛来的问题.
    

     

     

    展开全文
  • 这篇文章的结论就是epoll属于同步非阻塞模型 这是一次概念的纠结过程,对写代码没有太大意义。 过程是这样的: 首先,我的概念里往往只有同步和异步,没有太多去区别同异步IO和同异步通知两种。 另外还...
    这篇文章的结论就是epoll属于同步非阻塞模型,这个东西貌似目前还是有争议,在新的2.6内核之后,epoll应该属于异步io的范围了,golang的高并发特性就是底层封装了epoll模型的函数,但也有文章指出epoll属于“伪AIO”,真正的推动力实际在系统内核,另外mmap的应用加快了用户层和内核层的消息交换,对并发效率也有极大的提升。
    还有一点,在DMA控制器的帮助下,实际上算是异步了,所以epoll可以说就是异步非阻塞。

    《UNIX网络编程:卷一》第六章——I/O复用。书中向我们提及了5种类UNIX下可用的I/O模型:

    • 阻塞式I/O;

    • 非阻塞式I/O;

    • I/O复用(select,poll,epoll...);

    • 信号驱动式I/O(SIGIO);

    • 异步I/O(POSIX的aio_系列函数);

    阻塞式I/O模型:默认情况下,所有套接字都是阻塞的。怎么理解?先理解这么个流程,一个输入操作通常包括两个不同阶段:

    (1)等待数据准备好;
    (2)从内核向进程复制数据。


    对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所有等待分组到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用程序缓冲区。 好,下面我们以阻塞套接字的recvfrom的的调用图来说明阻塞

    &amp;lt;img src=&quot;https://pic2.zhimg.com/50/e83d68da03da2e8c1568b4b4b630edfd_hd.jpg&quot; data-rawwidth=&quot;1058&quot; data-rawheight=&quot;556&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;1058&quot; data-original=&quot;https://pic2.zhimg.com/e83d68da03da2e8c1568b4b4b630edfd_r.jpg&quot;&amp;gt;

    标红的这部分过程就是阻塞,直到阻塞结束recvfrom才能返回。

    非阻塞式I/O: 以下这句话很重要:进程把一个套接字设置成非阻塞是在通知内核,当所请求的I/O操作非得把本进程投入睡眠才能完成时,不要把进程投入睡眠,而是返回一个错误。看看非阻塞的套接字的recvfrom操作如何进行

    &amp;lt;img src=&quot;https://pic1.zhimg.com/50/4bc31cab27a9a732ab7d1ba9e674ed64_hd.jpg&quot; data-rawwidth=&quot;1064&quot; data-rawheight=&quot;631&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;1064&quot; data-original=&quot;https://pic1.zhimg.com/4bc31cab27a9a732ab7d1ba9e674ed64_r.jpg&quot;&amp;gt;

    可以看出recvfrom总是立即返回。

    I/O多路复用:虽然I/O多路复用的函数也是阻塞的,但是其与以上两种还是有不同的,I/O多路复用是阻塞在select,epoll这样的系统调用之上,而没有阻塞在真正的I/O系统调用如recvfrom之上。如图

    &amp;lt;img src=&quot;https://pic1.zhimg.com/50/b1ec6a4f16844a27c175d5a6a94cd7f8_hd.jpg&quot; data-rawwidth=&quot;1136&quot; data-rawheight=&quot;732&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;1136&quot; data-original=&quot;https://pic1.zhimg.com/b1ec6a4f16844a27c175d5a6a94cd7f8_r.jpg&quot;&amp;gt;

    信号驱动式I/O:用的很少,就不做讲解了。直接上图

    &amp;lt;img src=&quot;https://pic1.zhimg.com/50/6294fb7f7f5c22e39187a490c35ac6f0_hd.jpg&quot; data-rawwidth=&quot;1139&quot; data-rawheight=&quot;711&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;1139&quot; data-original=&quot;https://pic1.zhimg.com/6294fb7f7f5c22e39187a490c35ac6f0_r.jpg&quot;&amp;gt;

    异步I/O:这类函数的工作机制是告知内核启动某个操作,并让内核在整个操作(包括将数据从内核拷贝到用户空间)完成后通知我们。如图:

    &amp;lt;img src=&quot;https://pic2.zhimg.com/50/5819fd0fdff2bd4fdc9652291aca1831_hd.jpg&quot; data-rawwidth=&quot;1109&quot; data-rawheight=&quot;603&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;1109&quot; data-original=&quot;https://pic2.zhimg.com/5819fd0fdff2bd4fdc9652291aca1831_r.jpg&quot;&amp;gt;

    注意红线标记处说明在调用时就可以立马返回,等函数操作完成会通知我们。

    等等,大家一定要问了,同步这个概念你怎么没涉及啊?别急,您先看总结。 其实前四种I/O模型都是同步I/O操作,他们的区别在于第一阶段,而他们的第二阶段是一样的:在数据从内核复制到应用缓冲区期间(用户空间),进程阻塞于recvfrom调用。相反,异步I/O模型在这两个阶段都要处理。

    &amp;lt;img src=&quot;https://pic4.zhimg.com/50/8244d924a12eaf42d61b41718c559bff_hd.jpg&quot; data-rawwidth=&quot;3200&quot; data-rawheight=&quot;1800&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;3200&quot; data-original=&quot;https://pic4.zhimg.com/8244d924a12eaf42d61b41718c559bff_r.jpg&quot;&amp;gt;

    再看POSIX对这两个术语的定义:

    • 同步I/O操作:导致请求进程阻塞,直到I/O操作完成;

    • 异步I/O操作:不导致请求进程阻塞。

    好,下面我用我的语言来总结一下阻塞,非阻塞,同步,异步

    • 阻塞,非阻塞:进程/线程要访问的数据是否就绪,进程/线程是否需要等待;

    • 同步,异步:访问数据的方式,同步需要主动读写数据,在读写数据的过程中还是会阻塞;异步只需要I/O操作完成的通知,并不主动读写数据,由操作系统内核完成数据的读写。




    这是一次概念的纠结过程,对写代码没有太大意义。

    过程是这样的:
    首先,我的概念里往往只有同步和异步,没有太多去区别同异步IO和同异步通知两种。
    另外还记得apu(2rd)中有一句“select和poll可以实现异步形式的通知”。

    接着,听到了epoll是同步IO这个概念,比较意外。坚持了一下后,查到如下概念:
    在unp(3rd)里 的定义是:
    第一是IO操作的概念:
    IO操作包括:
    1.等待数据准备好。
    2.从内核到进程拷贝数据。

    第二就是是同步IO和异步IO的区别:
    同步IO导致请求进程阻塞,直到IO操作完成。
    异步IO不导致请求进程阻塞。

    得到的结论:
    阻塞IO模型,非阻塞IO模型,IO复用模型,信号驱动IO模型都是同步IO。
    epoll也是IO复用模型,应该是同步IO。

    此时又意外了,再看到一个解释:
    更为重要的是, epoll 因为采用 mmap的机制, 使得 内核socket buffer和 用户空间的 buffer共享, 从而省去了 socket data copy, 这也意味着, 当epoll 回调上层的 callback函数来处理 socket 数据时, 数据已经从内核层 "自动" 到了用户空间, 虽然和 用poll 一样, 用户层的代码还必须要调用 read/write, 但这个函数内部实现所触发的深度不同了.

    用 poll 时, poll通知用户空间的Appliation时, 数据还在内核空间, 所以Appliation调用 read API 时, 内部会做 copy socket data from kenel space to user space.

    而用 epoll 时, epoll 通知用户空间的Appliation时?, 数据已经在用户空间, 所以 Appliation调用 read API 时?, 只是读取用户空间的 buffer, 没有 kernal space和 user space的switch了.

    于是想了一下:
    明显没有IO操作的拷贝数据到内核空间了,stevens应该在99年就挂了,2.6内核的epoll才采用mmap机制,书籍偏旧了吧。
    那么epoll是异步IO了吧。

    然后再一看,你妹的,这还是不符合异步IO啊,毕竟epoll在告知OK前,是阻塞了,虽然是拷贝数据结束了。
    看来好像应该修正的是IO操作定义的第二步才对,而不是那个区别。

    好吧,你就暂时属于同步IO了,专心看代码,不纠结概念了。

    展开全文
  • 但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的(可能通过while循环来检测内核将数据准备的怎么样了, 而不是属于内核的一种通知

    select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的(可能通过while循环来检测内核将数据准备的怎么样了, 而不是属于内核的一种通知用户态机制),仍然需要read、write去读写数据, 只是因为, mmap实现的零拷贝, 而导致的调用深度不同。 当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
    下图能看基于事件驱动的 Netty 如何实现的异步, channel绑定到worker线程池中的某一个Eventloop上, 并加入了各种处理事件的回调, 比如步骤5.
    这里写图片描述

    关于这三种IO多路复用的用法,前面三篇总结写的很清楚,并用服务器回射echo程序进行了测试。连接如下所示:

    select:http://www.cnblogs.com/Anker/archive/2013/08/14/3258674.html

    poll:http://www.cnblogs.com/Anker/archive/2013/08/15/3261006.html

    epoll:http://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html

      今天对这三种IO多路复用进行对比,参考网上和书上面的资料,整理如下:

    select实现

    select的调用过程如下所示:

    这里写图片描述
    (1)使用copy_from_user从用户空间拷贝fd_set到内核空间

    (2)注册回调函数__pollwait

    (3)遍历所有fd,调用其对应的poll方法(对于socket,这个poll方法是sock_poll,sock_poll根据情况会调用到tcp_poll,udp_poll或者datagram_poll)

    (4)以tcp_poll为例,其核心实现就是__pollwait,也就是上面注册的回调函数。

    (5)__pollwait的主要工作就是把current(当前进程)挂到设备的等待队列中,不同的设备有不同的等待队列,对于tcp_poll来说,其等待队列是sk->sk_sleep(注意把进程挂到等待队列中并不代表进程已经睡眠了)。在设备收到一条消息(网络设备)或填写完文件数据(磁盘设备)后,会唤醒设备等待队列上睡眠的进程,这时current便被唤醒了。

    (6)poll方法返回时会返回一个描述读写操作是否就绪的mask掩码,根据这个mask掩码给fd_set赋值。

    (7)如果遍历完所有的fd,还没有返回一个可读写的mask掩码,则会调用schedule_timeout是调用select的进程(也就是current)进入睡眠。当设备驱动发生自身资源可读写后,会唤醒其等待队列上睡眠的进程。如果超过一定的超时时间(schedule_timeout指定),还是没人唤醒,则调用select的进程会重新被唤醒获得CPU,进而重新遍历fd,判断有没有就绪的fd。

    (8)把fd_set从内核空间拷贝到用户空间。

    总结:

    select的几大缺点:

    (1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

    (2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

    (3)select支持的文件描述符数量太小了,默认是1024

    poll实现

      poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多。

    关于select和poll的实现分析,可以参考下面几篇博文:

    http://blog.csdn.net/lizhiguo0532/article/details/6568964#comments

    http://blog.csdn.net/lizhiguo0532/article/details/6568968

    http://blog.csdn.net/lizhiguo0532/article/details/6568969

    http://www.ibm.com/developerworks/cn/linux/l-cn-edntwk/index.html?ca=drs-

    http://linux.chinaunix.net/techdoc/net/2009/05/03/1109887.shtml

    epoll

      epoll既然是对select和poll的改进,就应该能避免上述的三个缺点。那epoll都是怎么解决的呢?在此之前,我们先看一下epoll和select和poll的调用接口上的不同,select和poll都只提供了一个函数——select或者poll函数。而epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait,epoll_create是创建一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生。

      对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。

      对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果,和select实现中的第7步是类似的)。

      对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

    epoll原理概述

    调用epoll_create时,做了以下事情:

    内核帮我们在epoll文件系统里建了个file结点;
    在内核cache里建了个红黑树用于存储以后epoll_ctl传来的socket;
    建立一个list链表,用于存储准备就绪的事件。
    调用epoll_ctl时,做了以下事情:

    把socket放到epoll文件系统里file对象对应的红黑树上;
    给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。
    调用epoll_wait时,做了以下事情:

    观察list链表里有没有数据。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。而且,通常情况下即使我们要监控百万计的句柄,大多一次也只返回很少量的准备就绪句柄而已,所以,epoll_wait仅需要从内核态copy少量的句柄到用户态而已。

    即:

    一颗红黑树,一张准备就绪句柄链表,少量的内核cache,解决了大并发下的socket处理问题。

    执行epoll_create时,创建了红黑树和就绪链表;
    执行epoll_ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据;
    执行epoll_wait时立刻返回准备就绪链表里的数据即可。

    总结:

    (1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。

    (2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。

    参考资料:

    http://www.cnblogs.com/apprentice89/archive/2013/05/09/3070051.html

    http://www.linuxidc.com/Linux/2012-05/59873p3.htm

    http://xingyunbaijunwei.blog.163.com/blog/static/76538067201241685556302/

    http://blog.csdn.net/kkxgx/article/details/7717125

    https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/epoll-example.c

    展开全文
  • 1、支持协程,可以用同步的方式编程异步程序 开发中的功能: 2、异步dns 3、协程通信机制 benchmark: the gamelibdtest running in virtual machine, comes this result: 67000response/second, when buffer is 100...
  • 1.简介Linux I/O多路复用技术在比较多的TCP网络服务器中有使用,即比较多的用到select函数。...要使用epoll只需要以下的三个系统函数调用: epoll_create(2),epoll_ctl(2),epoll_wait(2)。2.sele...
  • 同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案是不同的。所以先限定一下本文的上下文。本文讨论的背景是Linux环境下的networkIO。一概念说明在进行解释之前,...
  • IO多路复用之select、poll、epoll综述 目前支持I/O多路复用的系统调用有 select,pselect,poll,epoll,I/O...但select,pselect,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写...
  • 1 Epoll vs. IOCP Epoll 和 IOCP 都是为高性能网络服务器而设计的高效 I/O 模型;都是基于事件驱动的。事件驱动有个著名的好莱坞原则(“不要打电话给我们,我们会打电话给你”)。   不同之处在于: 1. ...
  • 无论epoll处于LT模式还是ET模式下,一个socket上的某个事件都可能会被触发多次。这在并发编程中会引起一个问题,比如一个线程在读取完某个socket上的数据后开始处理数据,而在处理数据的过程中又触发可读事件,这样...
  • 在原来服务器的基础之上加了多线程处理提高效率,同时将使用数组的地方换成了动态类存分配(应该不会内存泄漏)。四个线程处理消息队列的消息然后放到待发送的队列里面,主线程每帧发送一次消息,发送完了delete指针...
  • op参数说明操作类型: EPOLL_CTL_ADD:向interest list添加一个需要监视的描述符EPOLL_CTL_DEL:从interest list中删除一个描述符EPOLL_CTL_MOD:修改interest list中一个描述符struct epoll_event结构描述一个文件...
  • 相关视频解析 ...同步(synchronous)/异步(asynchronous),阻塞(blocking)/非阻塞(non-blocking),阻塞IO/非阻塞IO/同步IO/异步IO/IO复用(IO Multiplexing),select/poll/epoll这些概念困扰我许久,下面给出这一
  • epoll是Linux内核为处理大批量句柄而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本, 它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。 简介: epoll是Linux下多路复用IO...
  • select poll epoll之间的区别

    千次阅读 2022-04-06 10:58:49
    同步阻塞、 同步非阻塞 异步非阻塞 同步和异步的概念: 同步是指用户线程发起IO请求后,需要等待或者轮询内核IO操作完成后才能继续执行; 异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知...
  • Windows完成端口介绍 Linux EPOLL介绍 同步I/O与异步I/O 说起完成端口,它的实现机制其实是重叠I/O实现异步I/O操作,下面就结合同步I/O来解释下什么是异步I/O
  • EPOLL模型详解

    2013-09-24 17:56:40
    不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以...
  • 一、同步、异步、阻塞与非阻塞 1.同步与异步 1.1 同步与异步概念 同步和异步的概念与消息的通知机制有关。同步与异步主要是从消息通知机制角度来说的。 同步就是一个任务的完成需要依赖另外一个任务时,只有...
  • selector、epoll详解

    2021-05-15 20:54:04
    文章目录基础一、Selector1.1 原理1.2 示例二、epoll2.1 原理2.2 示例2.3 总体流程三、Reactor 基础 首先,我们要明确同步、异步的层级。在这里讨论经常会出现各种同步、异步的概念让人迷惑,例如Netty用的是多路...
  • 同步阻塞模型,一个客户端连接对应一个处理线程 BIO代码示例: import java.net.ServerSocket; import java.net.Socket; import java.util.logging.Handler; public class SocketServer { public static
  • epoll貌似是同步的吧! 那nginx所谓的异步非阻塞到底异步在哪里? 为什么使用了epoll却叫异步? epoll确实是阻塞的. nginx的异步并不是指epoll的wait是异步的,而是指对于每个socketfd来说,它的handle是异步的. ...
  • select和epoll的优缺点

    2020-04-19 19:16:55
    select和epoll的优缺点 ...但select,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据...
  • 一、select,poll,epoll对比 1.select 2.poll 3.epoll 4.总结 二、epoll空轮询bug及处理 1.Selector BUG出现的原因 2.Netty的解决办法 一、select,poll,epoll对比 1.select 支持一个进程所能打开的最大...
  • epoll 详解

    2020-08-07 19:45:21
    epoll是Linux内核为处理大批量文件描述符而设计的IO多路复用机制,它能显著提高程序在存在大量并发连接而只有少部分活跃连接情况下的系统CPU利用率。epoll之所以可以做到如此高的效率是因为它在获取就绪事件的时候,...
  • 在 Linux 系统之中有一个核心武器:epoll 池,在高并发的,高吞吐的 IO 系统中常常见到 epoll 的身影。IO 多路复用在 Go 里最核心的是 Goroutine ,也就是所...
  • iocp-epoll

    2013-12-18 11:30:08
    iocp-epoll的几个例子,提供参考。 其中,iocp包含了几种使用方式,同步异步处理方式, epoll为linux下面使用,编译时可以使用下面简单命令, g++ -p -g -o hhh ***.cpp -l pthread
  • 这篇文章的结论就是epoll属于同步非阻塞模型,这个东西貌似目前还是有争议,在新的2.6内核之后,epoll应该属于异步io的范围了,golang的高并发特性就是底层封装了epoll模型的函数,但也有文章指出epoll属于“伪AIO”...
  • Linux中的多路复用技术---epoll的详解 epoll完整例子
  • #include <algorithm> #include <iostream> #include <cstring> #include <cstdint> #include <cstdlib> #include <cstdio> #include <string> #include <...#

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 39,928
精华内容 15,971
关键字:

同步epoll