从上篇可以看到,由于每次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通信模型(一)相同