Epoll model comprehension encapsulation and application, epoll model Encapsulation
I used to write a TCP server without having to consider the concurrency and resource issues. I used a separate thread to process a single TCP connection.PPC/TPCModel ). Now, you have to handle these problems if you are a high-concurrency server. Because linux2.6 is usedEpollAsI/OMultiplexing Technical InterfaceNo matter how good it is (hehahaha ).
In layman's terms, epoll is to tell you which sockets are ready to do. InSelectIn the model,SelectUsed to detect the socket status. The two methods are different, but the mechanism is different. The select Check Method traverses all the sockets to be checked each time and returns an action socket. Epoll does not detect all handle statuses. With Kernel support, it can avoid meaningless detection.
When the number of socket handles is very large, the PPC/TPC model will definitely fail. Because the select statement traverses all the handles each time, it takes a lot of time to traverse the handles. If the number of concurrent operations is close to the total number of handles, the select statement does not waste much time, however, if the number of concurrent connections is much lower than the number of connections, for example, the select statement may be a waste of time. Therefore, epoll is quite efficient.
Before encapsulating epoll into a c ++ class, give a brief introduction to the epoll data structure and interface:
Epoll event struct:
struct epoll_event { __uint32_t events; // Epoll events epoll_data_t data; // User datavariable };
The events here are the types of events that are commonly used:
EPOLLINThe handle is readable.
EPOLLOUTThe handle is writable.
EPOLLERRThis handle has an error
EPOLLETEpoll is the edge trigger mode.
Epoll Event date
typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64;} epoll_data_t;
Note that epoll_data is a union. We want to easily mount the handle or Data Pointer.
Epoll creation:
Int epoll_create (int size );
Call this function to create an epoll handle. The parameter size is the maximum number of listeners.
Epoll control:
Int epoll_ctl (int epfd, int op, int fd, struct epoll_event * event );
This interface is used to register, modify, and delete the handles on the epdf.
Op is the operation to be performed, including:
EPOLL_CTL_ADDAdd file handle to be monitored fd
EPOLL_CTL_MODChange the mode of the fd handle
EPOLL_CTL_DELRemove the handle.
Event is the fd event to be set.
Epoll collection information:
Int epoll_wait (int epfd, struct epoll_event * events, int maxevents, int timeout );
After this function is called, if an event of the corresponding type occurs for the handle managed by epoll, The epoll_event of these event handlers will be written into the events array, we can execute subsequent I/O and other operations based on these handles. Maxevents is the maximum number of events that are obtained by wait each time. If the ET edge trigger mode is used, epoll_wait will no longer notify epoll_wait when the status of the event does not change after an event is returned.
After introducing epoll, You can encapsulate epoll to enhance code reuse.
Before encapsulating epoll, I first gave the socket I encapsulated for tcp:
// A total of header files required, some of them are redundant. # include <iostream> # include <cstdio> # include <cstring> # include <string> # include <cstdlib> # ifdef WIN32 # include <winsock2.h> # else # include <fcntl. h> # include <sys/ioctl. h> # include <sys/socket. h> # include <sys/epoll. h> # include <unistd. h> # include <netdb. h> # include <errno. h> # include <arpa/inet. h> # include <netinet/in. h> # include <sys/types. h> # define SOCKET int # define SOCKET_ERROR-1 # define INVALID_SOCKET-1 # endif
Here is my own encapsulation of common tcp socket:
class msock{public: SOCKET sock; sockaddr_in addr; msock() { addr.sin_family=AF_INET; } void setsock(SOCKET fd) { sock=fd; } SOCKET getsock() { return sock; } void createsock() { sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(sock==INVALID_SOCKET) { puts("socket build error"); exit(-1); } } void setioctl(bool x) { fcntl(sock, F_SETFL, O_NONBLOCK); } bool setip(string ip) { hostent *hname=gethostbyname(ip.c_str()); if(!hname) { puts("can't find address"); return false; }//puts(inet_ntoa(addr.sin_addr)); addr.sin_addr.s_addr=*(u_long *)hname->h_addr_list[0]; return true; } void setport(int port) { addr.sin_port=htons(port); } int msend(const char *data,const int len) { return send(sock,data,len,0); } int msend(const string data) { return msend(data.c_str(),data.length()); } int msend(mdata *data) { return msend(data->buf,data->len); } int mrecv(char *data,int len) { return recv(sock,data,len,0); } int mrecv(char *data) { return recv(sock,data,2047,0); } int mclose() { return close(sock); } int operator == (msock jb) { return sock==jb.sock; }};
The sock used by listen inherits from msock:
class mssock:public msock{public: sockaddr_in newaddr; socklen_t newaddrlen; mssock():msock() { createsock(); addr.sin_addr.s_addr=htonl(INADDR_ANY); newaddrlen=sizeof(newaddr);//hehe } int mbind() { return bind(sock,(sockaddr *)&addr,sizeof(addr)); } int mlisten(int num=20) { return listen(sock,num); } msock maccept() { SOCKET newsock=accept(sock,(sockaddr *)&newaddr,&newaddrlen); msock newmsock; newmsock.setsock(newsock); return newmsock; }};
The above msock and mssock classes contain socket handles, which can be forcibly converted to socket handles directly.
Before encapsulating epoll, another step is to define a Data Structure to store data with an indefinite length so that it can be mounted to epoll events.
struct mdata{ int fd; unsigned int len; char buf[2048]; mdata(){} mdata(char *s,const int length) { for(int i=0;i<length;i++) { buf[i]=s[i]; } }};
Epoll encapsulation can be started and edge-triggered. My idea is to record the epoll handle and parameters in the class, and maintain an events Data for the corresponding event. Externally, you only need to obtain the return value through the class method based on the temporary number of the returned event.
Class mepoll {public: int epfd; // epoll's own handle epoll_event ev, * events; // temporary event and the int maxevents array used for storing each wait event; // Maximum Event count int timeout; // wait timeout // The default maximum event count of the constructor is 20 mepoll (unsigned short eventsnum = 20) {epfd = epoll_create (0 xfff ); maxevents = eventsnum; events = new epoll_event [maxevents]; timeout =-1;} // add a new socket handle to epoll int add (SOCKET fd) {fcntl (fd, f_SETFL, O_NONBLOCK); // set fd to non-blocking ev. events = EPOLLIN | EPOLLET; ev. data. fd = fd; return epoll_ctl (epfd, EPOLL_CTL_ADD, fd, & ev) ;}// set the handle event of the corresponding number to readable void ctl_in (int index) {ev. data. fd = * (int *) events [index]. data. ptr; ev. events = EPOLLIN | EPOLLET; epoll_ctl (epfd, EPOLL_CTL_MOD, * (int *) events [index]. data. ptr, & ev) ;}// modify writable data and bind the data to be written to the event corresponding to the handle void ctl_out (int index, mdata * data) {data-> fd = events [index]. data. fd; ev. data. ptr = data; ev. events = EPOLLOUT | EPOLLET; epoll_ctl (epfd, EPOLL_CTL_MOD, events [index]. data. fd, & ev);} int wait () {return epoll_wait (epfd, events, maxevents, timeout);} unsigned int geteventtype (int index) {return events [index]. events;} // obtain msock getsock (int index) {msock sk; sk. setsock (events [index]. data. fd); return sk;} // obtain msock getsock (mdata * data) {msock sk; sk from mdata. setsock (data-> fd); return sk;} // obtain the event mdata * getdata (int index) {return (mdata *) events [index]. data. ptr ;}};
Now there is a better epoll class. So we can start to implement a simple and complete server program.
In the implementation process, you need to distinguish between the handles used for listen and the handles used for sending and receiving data. Because the edge-triggered method is used, it is likely that the listen of a colleague may be connected to multiple connections. However, epoll_wait will only notify you once. If we find that there is an accept event, but we haven't completed all the accept processing, many links won't be connected. You can solve this problem as follows: when the listen occurs, keep accept until the accept fails.
Below I will use my game logic interface and epoll class to implement a basic server program:
The game logic interface is very simple. You only need to call gamemain to create an instance of the game class. And use the received data to callMdata * gamemain: dealdata (mdata * data)The function can get the mdata processed by the game logic and send the processed mdata back. The processed mdata * is automatically allocated by the game instance and called after sending.Gamemain: freedatainpool (Mdata * data)Release ). (Haha, I didn't expect my first time writing game server logic to do so low coupling)
# Include "ssock. h "# include" game. h "int main () {gamemain game; // create a game instance mepoll ep; // The epoll mssock ssock class; // The sock msock csock used by the listen server; // temporary sock mdata rdata; // temporary rdata ssock. setport (5000); // use the port 5000 if (SOCKET_ERROR = ssock. mbind () {puts ("bind error"); return-1;} if (SOCKET_ERROR = ssock. mlisten () {puts ("listen error"); return-1;} // start listen // Add the listen handle to epoll ep. add (ssock. getsock (); puts ("server start"); int ionum; while (1) {ionum = ep. wait (); // get events // traverse and process all events for (int I = 0; I <ionum; I ++) {printf ("some data come: "); csock = ep. getsock (I); if (ep. geteventtype (I) & EPOLLERR) {printf ("sock % u error \ n", csock. sock); csock. mclose ();} else if (ssock = csock) // process the listen event {while (1) // accept until no new connection exists {csock = ssock. maccept (); if (csock. getsock () = SOCKET_ERROR) {break;} // Add the new connection to epoll ep. add (csock. getsock (); puts ("a newsock comed:") ;}} else if (ep. geteventtype (I) & EPOLLIN) // process the received event {// obtain the corresponding sock Based on the temporary number and receive the data csock = ep. getsock (I); printf ("sock % u in \ n", csock. sock); int rlen; bool isrecv = false; rdata. len = 0; while (1) {rlen = csock. mrecv (rdata. buf + rdata. len); if (rlen <0) {if (errno = EAGAIN) {isrecv = true; break;} else if (errno = EINTR) {continue ;} else {break ;}}if (isrecv) {// call the game logic to process data and modify the sock event to send the ep. ctl_out (I, game. dealdata (& rdata) ;}} else if (ep. geteventtype (I) & EPOLLOUT) // process the send event {mdata * data = ep. getdata (I); csock = ep. getsock (data); printf ("sock % u out type: % u \ n", csock. sock, data-> buf [4]); int slen, cnt = 0; bool issend = false; while (1) {slen = csock. msend (data); if (slen <0) {if (errno = EAGAIN) {// For nonblocking sockets, it indicates that all messages have been sent successfully. issend = true; break;} else if (errno = EINTR) {// signal interrupted continue;} else {// other error break;} if (slen = 0) {break ;} /* cnt + = slen; if (cnt> = data-> len) */{issend = true; break;} game. freedatainpool (data); // you must change it to writable regardless of the sending situation to fault-tolerant ep. ctl_in (I) ;}} puts ("server ended"); return 0 ;}
After each read operation of this program is completed, the game logic is processed by a single thread in the next step. If the game logic is highly efficient and does not involve database waits, this method is desirable. Otherwise, you can start another thread to process the game logic and achieve real high concurrency.
The entire content of this article has been completed, and epoll has more knowledge than that. It will be necessary to gradually accumulate in future practices.