- Libevent is a simple encapsulation of reactor mode. If you use libevent, you don't need to pay attention to platform features. Whether it is linux, window or mac platform, you can convert network io processing into event processing
1, Event and event manager event_base introduction
- event time (similar to related events handled by epoll)
Events include: (coupling is too high during network programming)
1. Establish the connection and shake hands 3 times
2. Disconnect and wave 4 times
3. Message arrival (read)
4. After sending the message, write()
- event_base event manager (similar to epoll\poll\selcet)
2, Introduction to libevent process (Registration - > detection - > dispatch)
1. Register events of interest - event_ Add (what we need to write)
2. The event manager detects the type of event-
3. Synchronous dispatch asynchronous request processing - callback (we need to write)
3, Benefits of libevent
1. Platform independent
2. Convert network io into event processing
3. Ignore specific parameter details, io function details, return value of errno, etc
4. Encapsulation of specific events (e.g. network io events, timing events, signal events)
4, Code comparison
4.1 original reactor code
- In the original network programming, the connection was disconnected and the connection transceiver data was coupled together
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <netinet/tcp.h> #include <arpa/inet.h> #include <pthread.h> #include <errno.h> #include <sys/epoll.h> #define BUFFER_LENGTH 1024 #define LISTEN_PORT 100 struct sockitem { // int sockfd; int (*callback)(int fd, int events, void *arg); char recvbuffer[BUFFER_LENGTH]; // char sendbuffer[BUFFER_LENGTH]; int rlength; int slength; }; // mainloop / eventloop --> epoll --> struct reactor { int epfd; struct epoll_event events[512]; }; struct reactor *eventloop = NULL; int recv_cb(int fd, int events, void *arg); int send_cb(int fd, int events, void *arg) { struct sockitem *si = (struct sockitem*)arg; send(fd, si->sendbuffer, si->slength, 0); // struct epoll_event ev; ev.events = EPOLLIN | EPOLLET; //ev.data.fd = clientfd; si->sockfd = fd; si->callback = recv_cb; ev.data.ptr = si; epoll_ctl(eventloop->epfd, EPOLL_CTL_MOD, fd, &ev); } // ./epoll 8080 //Accept data int recv_cb(int fd, int events, void *arg) { //int clientfd = events[i].data.fd; struct sockitem *si = (struct sockitem*)arg; struct epoll_event ev; //char buffer[1024] = {0}; int ret = recv(fd, si->recvbuffer, BUFFER_LENGTH, 0); if (ret < 0) { //If readbuffer is full, EAGAIN will be returned if (errno == EAGAIN || errno == EWOULDBLOCK) { // return -1; } else { } ev.events = EPOLLIN; //ev.data.fd = fd; epoll_ctl(eventloop->epfd, EPOLL_CTL_DEL, fd, &ev); close(fd); free(si); } else if (ret == 0) { //If equal to 0, the connection is disconnected // printf("disconnect %d\n", fd); ev.events = EPOLLIN; //ev.data.fd = fd; epoll_ctl(eventloop->epfd, EPOLL_CTL_DEL, fd, &ev); close(fd); free(si); } else { printf("Recv: %s, %d Bytes\n", si->recvbuffer, ret); si->rlength = ret; memcpy(si->sendbuffer, si->recvbuffer, si->rlength); si->slength = si->rlength; struct epoll_event ev; ev.events = EPOLLOUT | EPOLLET; //ev.data.fd = clientfd; si->sockfd = fd; si->callback = send_cb; ev.data.ptr = si; epoll_ctl(eventloop->epfd, EPOLL_CTL_MOD, fd, &ev); } } int accept_cb(int fd, int events, void *arg) { struct sockaddr_in client_addr; memset(&client_addr, 0, sizeof(struct sockaddr_in)); socklen_t client_len = sizeof(client_addr); int clientfd = accept(fd, (struct sockaddr*)&client_addr, &client_len); if (clientfd <= 0) return -1; char str[INET_ADDRSTRLEN] = {0}; printf("recv from %s at port %d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)), ntohs(client_addr.sin_port)); struct epoll_event ev; ev.events = EPOLLIN | EPOLLET;//After the connection is established, the response read event is opened //ev.data.fd = clientfd; struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem)); si->sockfd = clientfd; si->callback = recv_cb;//If the client sends data to the server, we call this callback function ev.data.ptr = si; epoll_ctl(eventloop->epfd, EPOLL_CTL_ADD, clientfd, &ev); return clientfd; } int init_sock(short port) { //1. Create socket int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { return -1; } //2. Bind specific ports struct sockaddr_in addr; memset(&addr, 0, sizeof(struct sockaddr_in)); addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = INADDR_ANY; if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) { return -2; } //3. Listening socket if (listen(sockfd, 5) < 0) { return -3; } printf ("listen port : %d\n", port); return sockfd; } //EPOLLIN read event, EPOLLOUT write event, EPOLLERR network error event, epollbackup: both the write end and the read end are closed, that is, the connection is closed int main(int argc, char *argv[]) { //1. Obtain the port parameters, create an eventloop, and create a listening socket if (argc < 2) { return -1; } int port = atoi(argv[1]); eventloop = (struct reactor*)malloc(sizeof(struct reactor)); // epoll opera eventloop->epfd = epoll_create(1); int i = 0; for (i = 0;i < LISTEN_PORT;i ++) { int sockfd = init_sock(port+i); struct epoll_event ev; ev.events = EPOLLIN; struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem)); si->sockfd = sockfd; si->callback = accept_cb;//The callback function of listening socket is accept_cb, listen to the socket and call accept when a message comes_ CB function ev.data.ptr = si; //Leave listenfd to epoll epoll_ctl(eventloop->epfd, EPOLL_CTL_ADD, sockfd, &ev); } while (1) { int nready = epoll_wait(eventloop->epfd, eventloop->events, 512, -1); if (nready < -1) { break; } int i = 0; for (i = 0;i < nready;i ++) { if (eventloop->events[i].events & EPOLLIN) { //printf("sockitem\n"); struct sockitem *si = (struct sockitem*)eventloop->events[i].data.ptr; si->callback(si->sockfd, eventloop->events[i].events, si); } if (eventloop->events[i].events & EPOLLOUT) { struct sockitem *si = (struct sockitem*)eventloop->events[i].data.ptr; si->callback(si->sockfd, eventloop->events[i].events, si); } } } }
4.2 code of libevent encapsulating reactor
- Now decouple the specific events with different callback functions
4.2. 1. Packaging of single reactor
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <netinet/tcp.h> #include <arpa/inet.h> #include <pthread.h> #include <errno.h> #include <sys/epoll.h> #include <event.h> #include <time.h> #include <event2/listener.h> #include <event2/bufferevent.h> void socket_read_callback(struct bufferevent *bev,void* arg) { char msg[4096]; //After decoupling, the disconnection of the connection is not handled here size_t len = bufferevent_read(bev,msg,sizeof(msg)-1); msg[len] = '\0'; printf("server read the data %s\n",msg); char reply[4096] = "recvieced msg: "; strcat(reply + strlen(reply),msg); bufferevent_write(bev,reply,strlen(reply)); } void socket_event_callback(struct bufferevent *bev,short events,void* arg) { if(events & BEV_EVENT_EOF) //BEV_EVENT_EOF: 1.read=0,2. write=-1&errno=EPIPE,3. EPOLLHUP of epoll printf("connection closed\n"); else if(events & BEV_EVENT_ERROR) //network error printf("some other error\n"); bufferevent_free(bev); //The operation is equivalent to fd corresponding to close } //Connection establishment void listener_callback(struct evconnlistener *listener,evutil_socket_t fd, struct sockaddr sock,int socklen,void* arg ) { printf("accept a client fd:%d\n",fd); struct event_base *base = (struct event_base*)arg;//The context has taken out the corresponding event //Create a user state read / write buffer object Bev (meaning: it may not be read at one time) struct bufferevent *bev = bufferevent_socket_new(base,fd,BEV_OPT_CLOSE_ON_FREE); //Set the response read event callback function, socket_read_callback //socket_event_callback is a disconnected callback function bufferevent_setcb(bev,socket_read_callback,NULL,socket_event_callback,NULL); bufferevent_enable(bev,EV_READ|EV_PERSIST); //Function: register read events and follow epoll_ctl has the same effect; EV_PERSIST means that it will not be removed after registration. It will be removed without registration once } //Callback function for timed events static void do_timer(int fd,short events,void* arg) { struct event* timer = (struct event*)arg; timer_t now = time(NULL); printf("do_timer %s",(char*)ctime(&now)); struct timeval tv = {1,0}; event_add(timer,&tv); } //Compile instruction //gcc evmain.c -o evmain -levent int main(int argc,char*argv[]) { //1. Create structure struct sockaddr_in sin; memset(&sin,0,sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = htons(8989); //2. Initialize the event manager struct event_bash *base = event_bash_new(); //First initialize an event manager //3. The establishment of connection and the processing of accept process are encapsulated into an object //Parameters: First: select the event manager; Second: provide a callback function, which is equivalent to accept_cb handles connection establishment; //Third: lev_ OPT_ Reusable: if the port is not set, the port cannot be connected (the set port can be reused after the server is restarted); LEV_OPT_CLOSE_ON_FREE: when the client is disconnected, the program automatically closes fd for us struct evconnlistener *listener = evconnlistener_new_bind(base,listener_callback,base, LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, 10,(struct sockaddr*)&sin, sizeof(struct sockaddr_in)); // //For the server, there are three events: 1 Network io events, 2 Timed events (for example, some events need to be delayed or executed every two seconds), 3 Signal event (e.g. KILL -9) //Signal example: log system, the log file cannot be written in, but the debugging fd is available. Reason: the log is redirected. Solution principle: because the log is redirected, when an exception occurs, the kernel notifies the application in the form of a signal. The application needs to capture this signal, and fd reopens it according to the redirection struct event evtimer; //Timing event struct timeval tv = {1,0}; //Parameters: the first is seconds, the second is microseconds; The overall meaning is that the function is executed once per second event_set(&evtimer,-1,0,do_timer,&evtimer); //Set the task as a scheduled event, do_timer is the callback function of timed events, and the fourth parameter is the context parameter event_bash_set(base,&evtimer); //Then bind the specific event to the event handler base event_add(&evtimer,&tv); //Register event manager //Summary: after 1 second, the event manager will send it out synchronously and call the callback function //event loop event_base_dispatch(base); //Detect event + event dispatch, that is, the callback of the call response evconnlistener_free(listener); event_bash_free(base); return 0; }
- Display effect: libevent runs timer every second
- telnet open connection effect
- After the connection is established, the client prints hello
4.2. 2. Encapsulation of memcached multiple reactor s (processed in multiple network threads)
//slightly //memcached.c //thread.c
- General idea: the main thread cooperates with the worker thread created according to the number of CPU cores. After accept ing the new fd, the fd is handed over to the worker thread. After that, the client communicates with the worker thread to complete the establishment, disconnection, arrival and sending of messages
- The accept ed fd is put into a queue of the corresponding thread by using load balancing and routing. The main thread writes data to the management of the corresponding working thread. When the working thread receives the data of the pipeline, it takes the connection from the queue, and registers the events of connection disconnection, message arrival and sending to the event manager event_base
- Multiple reactors: multiple threads have one reactor