Linux socket编程(四) 简单聊天室之epoll版
来源:互联网 发布:人文社会科学类软件 编辑:程序博客网 时间:2024/05/23 15:06
http://www.cnblogs.com/-Lei/archive/2012/09/12/2681475.html
这一篇我们用epoll改写之前写的简单聊天室,Epoll是Linux内核为处理大批量句柄而作了改进的poll。
我们要用到epoll的三个函数,分别是:int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
下面对要用到epoll的操作进行封装
Epoll.h
#ifndef EPOLL_H#define EPOLL_H#include "Socket.h"#include <sys/epoll.h>#include <sys/resource.h>const int MAXEPOLLSIZE=MAXCONNECTION+5;class Epoll{ public: Epoll(); bool Add(int fd,int eventsOption); //Returns the number of triggered events int Wait(); bool Delete(const int eventIndex); int GetEventOccurfd(const int eventIndex) const; int GetEvents(const int eventIndex) const; private: int epollfd; int fdNumber; struct epoll_event event; struct epoll_event events[MAXEPOLLSIZE]; struct rlimit rt;};#endif
Socket类的实现见我的这篇博文Linux socket编程(一) 对套接字操作的封装
更好一点的做法是把Socket类做成一个共享函数库
Epoll.cpp
#include "Epoll.h"#include <stdio.h>#include <stdlib.h>Epoll::Epoll():fdNumber(0){ //set resource limits respectively rt.rlim_max=rt.rlim_cur=MAXEPOLLSIZE; if(::setrlimit(RLIMIT_NOFILE, &rt) == -1) { perror("setrlimit"); exit(1); } //create epoll epollfd=epoll_create(MAXEPOLLSIZE);}bool Epoll::Add(int fd,int eventsOption){ //handle readable event,set Edge Triggered event.events=eventsOption;//EPOLLIN | EPOLLET; event.data.fd=fd; if(epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event)<0) return false; fdNumber++; return true;}bool Epoll::Delete(const int eventIndex){ if(epoll_ctl(epollfd,EPOLL_CTL_DEL, events[eventIndex].data.fd,&event)<0) return false; fdNumber--; return true;}int Epoll::Wait(){ int eventNumber; eventNumber=epoll_wait(epollfd,events,fdNumber,-1); if(eventNumber<0) { perror("epoll_wait"); exit(1); } return eventNumber;}int Epoll::GetEventOccurfd(const int eventIndex) const{ return events[eventIndex].data.fd;}int Epoll::GetEvents(const int eventIndex) const{ return events[eventIndex].events;}
现在考虑如何把epol用到socket的通信中
参考了这篇博文 http://www.cnblogs.com/OnlyXP/archive/2007/08/10/851222.html
epoll有两种触发模式:
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你 的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.
ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。
接下来我们使用边沿触发这种方式(ET),先看一下手册是怎么说的(man epoll):
Q9 Do I need to continuously read/write a file descriptor until EAGAIN when using the EPOLLET flag (edge-triggered behavior) ? A9 Receiving an event from epoll_wait(2) should suggest to you that such file descriptor is ready for the requested I/O operation. You must consider it ready until the next (nonblocking) read/write yields EAGAIN. When and how you will use the file descriptor is entirely up to you. For packet/token-oriented files (e.g., datagram socket, terminal in canonical mode), the only way to detect the end of the read/write I/O space is to continue to read/write until EAGAIN. For stream-oriented files (e.g., pipe, FIFO, stream socket), the condition that the read/write I/O space is exhausted can also be detected by checking the amount of data read from / written to the target file descriptor. For example, if you call read(2) by asking to read a certain amount of data and read(2) returns a lower number of bytes, you can be sure of having exhausted the read I/O space for the file descriptor. The same is true when writing using write(2). (Avoid this latter technique if you cannot guarantee that the monitored file descriptor always refers to a stream-oriented file.)
意思大概是说当使用ET这种方式时,要不断地对文件描诉符进行读/写,直至遇到EAGAIN为止。
为什么要这样呢:
假如发送端流量大于接收端的流量 (意思是epoll所在的程序读比转发的socket要快),由于是非阻塞的socket,那么send()函数虽然返回,但实际缓冲区的数据并未真正发 给接收端,这样不断的读和发,当缓冲区满后会产生EAGAIN错误(参考man send),同时,不理会这次请求发送的数据.所以,需要封装socket_send()的函数用来处理这种情况,该函数会尽量将数据写完再返回,同样对于recv函数也要进行相应的封装。
以下是我的封装:(英文注释写的不是很好,大家凑合着看吧)
void EpollServerSocket::SendMessage(Socket& clientSocket,const std::string& message) const{ while(true) { if(Socket::Send(clientSocket,message)==false) { // if(errno == EINTR) return; //this means the cache queue is full, //sleep 1 second and send again if(errno==EAGAIN) { sleep(1); continue; } } return; }}void EpollServerSocket::ReceiveMessage(Socket& clientSocket,std::string& message){ bool done=true; while(done) { int receiveNumber=Socket::Receive(clientSocket,message); if(receiveNumber==-1) { //if errno == EAGAIN, that means we have read all data. //so return if (errno != EAGAIN) { perror ("ReceiveMessage error"); DeleteClient(clientSocket.GetSocketfd()); } return; } else if(receiveNumber==0) { // End of file. The remote has closed the connection. DeleteClient(clientSocket.GetSocketfd()); } //if receiveNumber is equal to MAXRECEIVE, //maybe there is data still in cache,so it has to read again if(receiveNumber==MAXRECEIVE) done=true; else done=false; }}
好了接下来是Socket类的派生类,EpollServerSocket类
EpollServerSocket.h
#ifndef EPOLLSERVERSOCKET_H#define EPOLLSERVERSOCKET_H#include "Socket.h"#include "Epoll.h"#include <map>class EpollServerSocket:public Socket{ public: EpollServerSocket(const int port); virtual ~EpollServerSocket(); void Run(); private: //when using the EPOLLET flag, //need to continuously read/write a file descriptor until EAGAIN, //so we write these two functions for read/write void SendMessage(Socket& clientSocket,const std::string& message) const; void ReceiveMessage(Socket& clientSocket,std::string& message); void ProcessMessage(Socket& clientSocket); void SendToAllUsers(const std::string& message) const; //add event to epoll bool AddNewClient(Socket& clientSocket); //delete client from map clientSockets void DeleteClient(int sockfd); std::map<int,Socket*> clientSockets; Epoll epoll;};#endif
以下是EpollServerSocket类的实现
(以前写的客户端不用更改,直接可以与这个服务器通信)
对于大数据量的传输,很明显要不断地进行读/写,这样就会出现长时间的阻塞,甚至成为系统的性能瓶颈
但是对于只有较少活跃的socket,同时数据量较小的情况,epoll的效率应该是比select和poll高的(呃,不过没有很好的测试过)
不过好像有一种做法可以避免阻塞,就是利用EPOLLOUT事件
“EPOLLOUT事件的意思就是 当前这个socket的发送状态是空闲的,此时处理能力很强,告知用户可以发送数据。
所以在正常情况下,基本上socket在epoll_wait后,都会得到一个socket的EPOLLOUT事件。
【如果你不是一直在写数据或者你不是在传送一个几百M的数据文件,send一半都处于空闲状态】
而这个特性刚好可以处理 阻塞问题。
当数据发送不出去的时候,说明网络阻塞或者延迟太厉害了。
那么将要发送的数据放在一个buffer中,当下次你发现了EPOLLOUT事件时,说明现在网络处于空闲状态,OK,此时你可以用另外一个线程来发送上次堆积在buffer中的数据了。这样就不会阻塞了“
本文为原创博文,转载请注明原作者博客地址:http://www.cnblogs.com/-Lei/archive/2012/09/12/2681475.html
- Linux socket编程(四) 简单聊天室之epoll版
- Linux socket编程(四) 简单聊天室之epoll版
- Linux socket编程(三) 简单的多线程聊天室
- Linux socket编程(三) 简单的多线程聊天室
- Linux socket编程(三) 简单的多线程聊天室
- Java之--------Socket编程(UDP简单聊天室)
- linux socket网络编程之epoll
- java socket编程之聊天室(一)
- java socket编程之聊天室(二)
- java socket编程之聊天室(三)
- socket编程之epoll
- Android网络编程之Socket通信实现简单聊天室
- Linux socket网络编程之聊天室(三):select异步通讯实现
- Linux socket网络编程之聊天室(三):select异步通讯实现
- Linux socket网络编程之聊天室(三):select异步通讯实现
- 网络编程之socket、epoll
- socket编程之epoll多路复用
- socket编程:简单的多客户端聊天室
- Oracle找回密码
- 第17周项目3-胖子伤不起
- Linux socket编程(三) 简单的多线程聊天室
- sqrt(x)
- 深入探索3D拾取技术
- Linux socket编程(四) 简单聊天室之epoll版
- Linux socket编程(五) 文件传输
- leetcode - remove duplicates
- VmWare下安装CentOS6图文安装教程
- OC-NSArray
- 十进制转换二进制
- JVM 内部运行线程介绍
- memcpy的用法 总结
- Linux 编程 共享内存