New page
- epoll
- Popular Understanding:
In a community, at first, people in the community asked couriers to pick up the pieces at home or to send them to the express store. The former took a little labor of couriers, and the latter could waste customers' time. Later, the express company came up with a way to establish an unmanned express receiving cabinet (honeycomb) in the community, Customers put everything they want to send in that cabinet. The courier will bring a big bag at a certain time and take away the things to be sent. In this way, the courier will not have to collect express from door to door and improve the efficiency
The people in the community are the files io, and the things in the cabinet are the io with event response, so you don't have to traverse one by one
- how is the bottom layer implemented?
I've been asked in the interview. I usually say that the red black tree is implemented with a two-way linked list, and then I can't go on. Now I'll make a big summary of epoll
The use of epoll is not enough. Now let's talk about why epoll time complexity can achieve O (1)?
We generally say that the bottom layer puts the io with events in a queue, which is a two-way linked list, and the insertion time is complex as O (1),
Yes, when we traverse the queue in the user state, we can ensure that every io traversed has an event, and the efficiency must be improved. Then, in the kernel state, how does the bottom layer put the io with an event into the queue? We already know how to put it. Now the problem is how to pick out the io with events in so many IOS? Is it all traversed once in the kernel state? I was puzzled at the beginning. After consulting a lot of data, I knew that there was a mechanism at the lower level. A soft terminal would be generated when an io occurred. Then I thought of the signal. The type of signal was SIGIO (I'm not sure whether the soft interrupt here is an io interrupt)
sigaction(SIGIO, &sigio_action, NULL);
When an IO event occurs, the corresponding callback function will be called immediately. In this way, I don't need each IO to ask whether an event has occurred. I set a callback function for each io. If an IO event occurs, call the callback function immediately. Then I'm ready to put the IO in the queue, and the role of the red black tree is management These IOS and set their callback functions, just like this api
**epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);**
Add the new io to the red black tree, and then register a corresponding callback function. We all know that the red black tree is characterized by addition, deletion, modification and query, and the time complexity is stable
logn, exit with io, cancel the corresponding callback function, and then delete the io
The above is the current preliminary understanding of the underlying implementation of epoll. I'll look at the source code and update it later
The specific process is as follows (see in Zhihu)
Finally, put an epoll to use. Here, the reactor mode is used (you can search this), and a more popular comment is added to facilitate understanding
#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 Buffersize 1024 enum WS_STATUS { WS_INIt, WS_HANDSHAKE, WS_DATAFORM, WS_DATAEND, }; int handshake(struct sockitem *si, struct reactor *mainloop) { } struct sockitem { // int sockfd; int (*callback)(int fd, int events, void *arg); char recvbuffer[Buffersize]; // char sendbuffer[Buffersize]; int rlength; int stauts; }; // 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; memcpy(si->sendbuffer, si->recvbuffer, si->rlength); send(fd, si->sendbuffer, si->rlength + 1, 0); // send(fd, "\n", 1, 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 int recv_cb(int fd, int events, void *arg) { //Here is the processing scheme for ordinary users //int clientfd = events[i].data.fd; struct sockitem *si = (struct sockitem *)arg; struct epoll_event ev; char buffer[1024] = {0}; int ret = recv(fd, buffer, 1024, 0); if (ret < 0) { 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) { // // 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(" Yeah! Recv: %s, %d Bytes\n", buffer, ret); memcpy(si->recvbuffer, buffer, ret); //Not surprisingly, I finally finished everything //Update your information in epoll family struct epoll_event ev; ev.events = EPOLLOUT | EPOLLET; //ev.data.fd = clientfd; si->rlength = ret; si->sockfd = fd; si->callback = send_cb; ev.data.ptr = si; //Report to the housekeeper that he wants to modify his personal information epoll_ctl(eventloop->epfd, EPOLL_CTL_MOD, fd, &ev); } } int accept_cb(int fd, int events, void *arg) { //This is the first user to enter the epoll family. He has special responsibilities. He is regarded as the little assistant of the housekeeper. His responsibility is to receive new users struct sockaddr_in client_addr; memset(&client_addr, 0, sizeof(struct sockaddr_in)); socklen_t client_len = sizeof(client_addr); //The above is to make room for new users to register information, and give them an id, which is the only sign to identify new users in this place 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)); //Then prepare to join the epoll family. The process is the same as that of the first one struct epoll_event ev; ev.events = EPOLLIN | EPOLLET; //ev.data.fd = clientfd; struct sockitem *si = (struct sockitem *)malloc(sizeof(struct sockitem)); si->sockfd = clientfd; //The difference here is that when an event occurs, its corresponding solution is different (that is, its callback function is different) si->callback = recv_cb; si->stauts = WS_HANDSHAKE; ev.data.ptr = si; //The information is well prepared. Finally, I told the housekeeper that there are new members to join epoll_ctl(eventloop->epfd, EPOLL_CTL_ADD, clientfd, &ev); return clientfd; } int main(int argc, char *argv[]) { if (argc < 2) { return -1; } int port = atoi(argv[1]); int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { return -1; } 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; //Bind an identity to the socket if (bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0) { return -2; } //maximum connection if (listen(sockfd, 5) < 0) { return -3; } // struct reactor // { // int epfd; // struct epoll_event events[512]; // }; // reactor allocates space, and eventloop is the largest housekeeper eventloop = (struct reactor *)malloc(sizeof(struct reactor)); // epoll opera // Assign a permission to epoll - > EPFD to manage io eventloop->epfd = epoll_create(1); //Each IO (the file descriptor should have an identity before entering epoll, so that the housekeeper EventLoop - > EPFD can be managed easily) //The structure epoll_event contains the identity of io struct epoll_event ev; // Event type ev.events = EPOLLIN; //ev.data.fd = sockfd; //int idx = 2000; //Here, for convenience, you can reference a structure and record each io information in detail. There is a null pointer in the ev structure, which is why you are referencing a structure struct sockitem *si = (struct sockitem *)malloc(sizeof(struct sockitem)); //The structure sockitem records the io status, what type of io it is, what events have occurred, and which function to call si->sockfd = sockfd; si->stauts = WS_INIt; si->callback = accept_cb; // The null pointer in ev points to the si space. This ev can contain a lot of information. You only need to turn it strongly when using it ev.data.ptr = si; // Bring the new io to the housekeeper EventLoop - > EPFD and tell the housekeeper how to get out. This is EPOLL_CTL_ADD, which means to join the epoll family // Tell yourself the id and its identity ev epoll_ctl(eventloop->epfd, EPOLL_CTL_ADD, sockfd, &ev); //The first thing to join is to monitor whether new users access the socket. Its responsibility is to tell the housekeeper when a user comes, and then pull the user into the epoll family //pthread_cond_waittime(); while (1) { //This is the duty of the housekeeper. In the epoll family, whoever has something to do, pull him down to a small room, count how many people have something to do, and then deal with the number of people who have something to do int nready = epoll_wait(eventloop->epfd, eventloop->events, 512, -1); if (nready < -1) { break; } int i = 0; //Pull down a room and deal with it one by one for (i = 0; i < nready; i++) { //event driven //Process according to the event type of each io to improve efficiency (the event type here is readable and writable) if (eventloop->events[i].events & EPOLLIN) { //printf("sockitem\n"); //Before each user comes in, they have registered their personal information and what to do if something happens to me (corresponding processing function), so the housekeeper doesn't have to worry //First extract the user's information and look at the processing scheme written by the user himself (here is the function callback) struct sockitem *si = (struct sockitem *)eventloop->events[i].data.ptr; si->callback(si->sockfd, eventloop->events[i].events, si); } //As above, here are only different event types 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); } } } }