Multiplex IO transfer server
The multi-channel IO transfer server is also called multi task IO server. The main idea of this kind of server is that the application program no longer monitors the client connection, but gives it to the kernel to replace the application program monitoring file.
There are three main methods used: select, poll and epoll.
select
- The number of file descriptors that select can listen to is limited to FD_SIZE, usually 1024. Simply changing the number of file descriptors opened by the process does not change the number of select listening files.
- It is appropriate for clients below 1024 to use select, but if too many clients are connected, select adopts polling mode, which will greatly reduce the response efficiency of the server.
/* According to POSIX.1-2001, POSIX.1-2008 */ #include <sys/select.h> /* According to earlier standards */ #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); /* Parameters: nfds: Add 1 to the maximum file descriptor in the monitored file descriptor set, because this parameter will tell the kernel how many file descriptors are in the state before detection readfds: Monitor whether the read data reaches the file descriptor set and passes in and out parameters writefds: The monitoring write data arrives at the file descriptor set and passes in and out parameters exceptfds: The monitoring exception arrives at the file descriptor set, such as the out of band data arrival exception, and the incoming and outgoing parameters timeout: Timed blocking monitoring time 1,NULL,Forever blocking waiting 2,Set timeval and wait for a fixed time 3,Set the time in timeval to 0, and return immediately after checking the description word for polling Return value: Success: returns the number of file descriptors contained in the three return descriptor sets (that is, the total number of bits set in readfds, writefds and exceptfds). If the timeout expires before anything interesting happens, the number may be zero. Failure: error returned - 1, setting errno */ // File descriptor set operation function void FD_CLR(int fd, fd_set *set); int FD_ISSET(int fd, fd_set *set); void FD_SET(int fd, fd_set *set); void FD_ZERO(fd_set *set); struct timeval { long tv_sec; /* seconds */ long tv_usec; /* microseconds */ };
Application case:
#include <stdio.h> #include <arpa/inet.h> #include <ctype.h> #include <string.h> #include <unistd.h> #include <sys/select.h> #include "wrap.h" #define MAXSIZE 8192 #define SERV_PORT 8888 int main(void) { int i, maxi, nready, n; int lfd, cfd, sfd, maxfd; int client[FD_SETSIZE]; // Custom array client to prevent traversal of 1024 file descriptors, FD_SETSIZE defaults to 1024 char buf[MAXSIZE] = {0}; struct sockaddr_in serv_addr, clie_addr; socklen_t clie_addr_len; fd_set rset, allset; // rset read event file descriptor set allset is used for temporary storage char clientip[256]; lfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(SERV_PORT); serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // Specify any local IP //inet_pton(AF_INET, "192.168.10.100", &serv_addr.sin_addr.s_addr); Bind(lfd, (const struct sockaddr *)&serv_addr, (socklen_t)sizeof(serv_addr)); Listen(lfd, 128); // Set the upper limit for connecting to the server at the same time, and the maximum number of listeners for operating system operation is 128 maxfd = lfd; // At the beginning, lfd is the maximum file descriptor maxi = -1; // Initialize client [] with - 1 for(i = 0; i < FD_SETSIZE; i++) client[i] = -1; FD_ZERO(&allset); // Construct select monitor file descriptor set FD_SET(lfd, &allset); printf("Accepting client connect ...\n"); while(1) { rset = allset; // The select monitor semaphore set is reset at each loop because the select function modifies its value nready = select(maxfd+1, &rset, NULL, NULL, NULL); if(nready < 0) { perror("select error"); exit(1); } // If there is a new client connection request if(FD_ISSET(lfd, &rset)) { clie_addr_len = sizeof(clie_addr); cfd = Accept(lfd, (struct sockaddr *)&clie_addr, &clie_addr_len); // Blocking listening client connection requests printf("rcv from %s at port %d\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clientip, sizeof(clientip)), ntohs(clie_addr.sin_port)); // Found a useless location in client [] // Save the file descriptor returned by accept to client [] for(i = 0; i < FD_SETSIZE; i++) { if(client[i] < 0) { client[i] = cfd; printf("add a new client[%d] = %d\n", i, cfd); break; } } // The maximum number of files that can be monitored by select is 1024 if(i == FD_SETSIZE) { fputs("too many clients\n", stderr); } // Add a new file descriptor cfd to the monitoring file descriptor set allset FD_SET(cfd, &allset); if(cfd > maxfd) maxfd = cfd; // Ensure that maxi always stores the subscript of the last element of client [] if(i > maxi) maxi = i; if(--nready == 0) continue; } // Detect that the client has data ready for(i = 0; i <= maxi; i++) { if((sfd = client[i]) < 0) continue; if(FD_ISSET(sfd, &rset)) { printf("Ready to read client data.\n"); // When the client closes the connection, the server also closes the corresponding connection if((n = Read(sfd, buf, sizeof(buf))) == 0) { Close(sfd); // De select monitoring of this file descriptor FD_CLR(sfd, &allset); client[i] = -1; } else if(n > 0) { for(int j = 0; j < n; j++) buf[j] = toupper(buf[j]); sleep(10); Write(sfd, buf, n); Write(STDOUT_FILENO, buf, n); } if(--nready == 0) break; // Jump out of for, but still in while } } } Close(lfd); return 0; }
poll
poll is an upgraded version of select. characteristic:
- poll can break the upper limit of 1024 open file descriptors.
- Listening and returning sets are separated.
- The search scope becomes smaller.
The ulimit - a command can view the number of file descriptors allowed to be opened.
ulimit -a core file size (blocks, -c) 0 data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 13312 max locked memory (kbytes, -l) 65536 max memory size (kbytes, -m) unlimited open files (-n) 1024 pipe size (512 bytes, -p) 8 POSIX message queues (bytes, -q) 819200 real-time priority (-r) 0 stack size (kbytes, -s) 8192 cpu time (seconds, -t) unlimited max user processes (-u) 13312 virtual memory (kbytes, -v) unlimited file locks
open files is the number of file descriptors allowed to be opened.
Use the command cat to view the socket descriptor that can be opened by a process. This is the maximum value of the file descriptor that can be opened according to the computer hardware conditions.
cat /proc/sys/fs/file-max
If necessary, you can modify the upper limit value by modifying the configuration file. After modification, restart or log off the user to take effect.
sudo vim /etc/security/limits.conf Write the following configuration at the end of the file, soft Soft limits, hard Hard limits. * soft nofile 3000 * hard nofile 10000
Application case:
#include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout); /* Parameters: fds: First address of monitoring array nfds: Number of file descriptors to be monitored in the monitoring array timeout: -1: Blocking waiting; 0: return immediately; > 0: wait for the specified length of time. Return value: Success: returns the number of file descriptors that meet the criteria Failure: Return - 1, set errno */ struct pollfd { int fd; /* file descriptor File descriptor to listen on*/ short events; /* requested events What event POLLIN\POLLOUT\POLLERR listens for this file descriptor*/ short revents; /* returned events Events returned when conditions are met*/ }; /* POLLIN Common or out of band priority data readable, i.e. POLLRDNORM | POLLRDBAND */
Application case:
#include <stdio.h> #include <arpa/inet.h> #include <ctype.h> #include <string.h> #include <unistd.h> #include <poll.h> #include <errno.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include "wrap.h" #define MAXSIZE 1024 #define SERV_PORT 8888 #define MAXLINE 80 #define OPEN_MAX 3000 int main(void) { ssize_t n; int i, maxi; int nready; // Receive the return value of poll and record the number of fd events that meet the listening requirements int lfd, cfd, sfd; char buf[MAXLINE] = {0}; struct sockaddr_in serv_addr, clie_addr; socklen_t clie_addr_len; struct pollfd client[OPEN_MAX]; // poll array char clientip[INET_ADDRSTRLEN]; // Temporary connected client ip lfd = Socket(AF_INET, SOCK_STREAM, 0); // Set port multiplexing int opt = 1; setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(SERV_PORT); serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // Specify any local IP //inet_pton(AF_INET, "192.168.10.100", &serv_addr.sin_addr.s_addr); Bind(lfd, (const struct sockaddr *)&serv_addr, (socklen_t)sizeof(serv_addr)); Listen(lfd, 128); // Set the upper limit for connecting to the server at the same time, and the maximum number of listeners for operating system operation is 128 // The first file descriptor to listen to is stored in client[0] client[0].fd = lfd; // lfd listens for normal read events client[0].events = POLLIN; //Initialize the remaining element 0 in client [] with - 1. It is also a file descriptor and cannot be used for(i = 1; i < OPEN_MAX; i++) client[i].fd = -1; // Maximum element subscript of valid elements in client [] array maxi = 0; printf("Accept to connecting...\n"); for(;;) { // Block listening for client connection requests nready = poll(client, maxi+1, -1); // lfd read event ready if(client[0].revents & POLLIN) { clie_addr_len = sizeof(clie_addr); cfd = Accept(lfd, (struct sockaddr *)&clie_addr, &clie_addr_len); // Blocking listening client connection requests printf("rcv from %s at port %d\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clientip, sizeof(clientip)), ntohs(clie_addr.sin_port)); for(i = 1; i < OPEN_MAX; i++) { if(client[i].fd < 0) { // Find the location of client [] and store the cfd returned by accept client[i].fd = cfd; break; } } // Maximum client book reached if(i == OPEN_MAX) { perror("too many clients"); exit(1); } // Set the cfd just returned and monitor the read event client[i].events = POLLIN; // Update the subscript of the largest element in client [] if(i > maxi) maxi = i; // When there are no more ready events, continue to return poll blocking if(--nready == 0) continue; } // If the previous if is not satisfied, it means that the lfd is not satisfied. Check the client [] to see which cfd is ready for(i = 0; i <= maxi; i++) { if((sfd = client[i].fd) < 0) continue; if(client[i].revents & POLLIN) { printf("Ready to read client data.\n"); // When the client closes the connection, the server also closes the corresponding connection if((n = Read(sfd, buf, sizeof(buf))) == 0) { // Description: the client closes the connection first printf("client[%d] closed connection\n", i); Close(sfd); // Unmonitored poll for this file descriptor client[i].fd = -1; } else if(n > 0) { for(int j = 0; j < n; j++) buf[j] = toupper(buf[j]); sleep(10); Write(sfd, buf, n); Write(STDOUT_FILENO, buf, n); } else { // connection reset by client // Received RST flag if(errno == ECONNRESET) { printf("client[%d] aborted connection\n", i); Close(sfd); client[i].fd = -1; } else { perror("read error"); exit(1); } } if(--nready == 0) break; // Jump out of for, but still in while } } } Close(lfd); return 0; }
epoll
epoll is an enhanced version of the multiplexed IO interface select/poll under Linux. It can significantly improve the system CPU utilization of the program when there is only a small amount of activity in a large number of concurrent connections, because it multiplexes the file descriptor set to transmit the results without forcing the developer to prepare the file descriptor set to be listened on before waiting for events every time, Another reason is that when getting events, it does not need to traverse the entire set of listened descriptors, but only those descriptors that are asynchronously awakened by kernel IO events and added to the Ready queue.
epoll is a popular and preferred model in Linux large-scale concurrent network programs.
Epoll not only provides Level Triggered IO events such as select/poll, but also provides Edge Triggered, which makes it possible for user space programs to cache IO States and reduce epoll_ wait/epoll_ Call pwait to improve application efficiency.
Basic API
1. Create an epoll handle. The parameter size is used to tell the kernel the number of file descriptors to listen for, which is related to the memory size.
Balanced binary tree: a binary tree whose height difference between the left subtree and the right subtree is less than 1.
The fastest way to find balanced binary tree: dichotomy.
#include <sys/epoll.h> int epoll_create(int size); /* size:The number of file descriptors monitored is a reference for the Linux kernel. The actual number of file descriptors monitored can be greater than it. Actually used for a red black tree. Return value: a file descriptor epfd, which points to the root of a red black tree (more efficient balanced binary tree) */
2. Controls the events on the file descriptor monitored by an epoll: registration, modification and deletion.
#include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); /* epfd: For epoll_ Handle to create op: Represents an action, which is represented by three macros: EPOLL_CTL_ADD (Register a new fd to EPFD) (add a red black tree node), EPOLL_CTL_MOD (Modify the listening event of registered fd (modify the node of a red black tree), EPOLL_CTL_DEL (Delete a FD from epfd (delete a red black tree node); fd: Which file descriptor to register, modify, or delete. event: Tell the kernel the events to listen for, and pass in the parameters */ typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; /* events: EPOLLIN\EPOLLOUT\EPOLLERR... */
3. Wait for an event to be generated on the monitored file descriptor, similar to the select() call
#include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); /* Parameters: epfd: For epoll_ Handle to create events: Used to store the collection, array and outgoing parameters of events obtained by the kernel maxevents: Tell the kernel how big the events are, and the value of maxevents cannot be greater than epoll_ size at create(), array capacity timeout: Timeout -1: block 0: Immediate return, non blocking >0: Specifies how many milliseconds to block Return value: Success: returns how many file descriptors are ready Time expired: return 0 Failed: Return - 1 */
Application case:
#include <stdio.h> #include <arpa/inet.h> #include <ctype.h> #include <string.h> #include <unistd.h> #include <sys/epoll.h> #include <errno.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include "wrap.h" #define SERV_PORT 8888 #define MAXLINE 8192 #define OPEN_MAX 3000 int main(void) { ssize_t efd, res, nready; int i, n, num; int lfd, cfd, sfd; char buf[MAXLINE] = {0}; struct sockaddr_in serv_addr, clie_addr; socklen_t clie_addr_len; struct epoll_event tep, ep[OPEN_MAX]; // tep: epoll_ctl parameters; ep[]: epoll_wait parameter char clientip[INET_ADDRSTRLEN]; // Temporary connected client ip lfd = Socket(AF_INET, SOCK_STREAM, 0); // Set port multiplexing int opt = 1; setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(SERV_PORT); serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // Specify any local IP //inet_pton(AF_INET, "192.168.10.100", &serv_addr.sin_addr.s_addr); Bind(lfd, (const struct sockaddr *)&serv_addr, (socklen_t)sizeof(serv_addr)); Listen(lfd, 128); // Set the upper limit for connecting to the server at the same time, and the maximum number of listeners for operating system operation is 128 efd = epoll_create(OPEN_MAX); // Create an epoll model, and efd points to the root node of the red black tree if(efd == -1) { perror("epoll_create error"); exit(1); } // The listening event of the specified lfd is read tep.events = EPOLLIN; tep.data.fd = lfd; // Set lfd and its corresponding structure to the tree, and efd can find the tree res = epoll_ctl(efd, EPOLL_CTL_ADD, lfd, &tep); if(res == -1) { perror("epoll_ctl error"); exit(1); } printf("Accept to connecting...\n"); for(;;) { // epoll is server blocking listening event, and ep is struct // epoll_ Array of event type, OPEN_MAX is the array capacity, and - 1 indicates blocking wait nready = epoll_wait(efd, ep, OPEN_MAX, -1); if(nready == -1) { perror("epoll_wait error"); exit(1); } for(i = 0; i < nready; i++) { // If it is not a read event, skip the following and continue the next cycle if(!(ep[i].events & EPOLLIN)) continue; // Judge whether the fd satisfying the event is lfd if(ep[i].data.fd == lfd) { clie_addr_len = sizeof(clie_addr); cfd = Accept(lfd, (struct sockaddr *)&clie_addr, &clie_addr_len); // Blocking listening client connection requests printf("rcv from %s at port %d\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clientip, sizeof(clientip)), ntohs(clie_addr.sin_port)); printf("cfd[%d] --- client[%d]\n", cfd, ++num); // Add the newly connected client to epoll's read event listener tep.events = EPOLLIN; tep.data.fd = cfd; res = epoll_ctl(efd, EPOLL_CTL_ADD, cfd, &tep); if(res == -1) { perror("epoll_ctl error"); exit(1); } } else // Not lfd { sfd = ep[i].data.fd; n = Read(sfd, buf, MAXLINE); // Reading 0 indicates that the client has closed the connection // You need to delete the file descriptor from the red black tree if(n == 0) { res = epoll_ctl(efd, EPOLL_CTL_DEL, sfd, NULL); if(res == -1) { perror("epoll_ctl error"); exit(1); } Close(sfd); // Close the connection to the client printf("client[%d] closed connection\n", sfd); } else if(n < 0) { perror("read n < 0 error:"); res = epoll_ctl(efd, EPOLL_CTL_DEL, sfd, NULL); if(res == -1) { perror("epoll_ctl error"); exit(1); } Close(sfd); // Close the connection to the client } else { for(i = 0; i < n; i++) { buf[i] = toupper(buf[i]); } Write(STDOUT_FILENO, buf, n); Write(sfd, buf, n); } } } } Close(lfd); return 0; }
Horizontal trigger (epoll LT) and edge trigger (epoll ET)
Horizontal trigger (epoll LT): as long as there is still data in the read / write buffer of the file descriptor that has not been read, it will be triggered all the time, that is, call epoll again_ When waiting, it will return immediately again until the data in the read-write buffer is completely read. Epoll defaults to this mode.
Edge trigger (epoll ET): it will be triggered only once when the data in the read / write buffer of the file descriptor comes, and epoll will be called again_ When waiting, it will not return immediately, even if the data in the buffer is not read completely. The return will not be triggered until the read-write buffer of the file descriptor comes again.
#include <stdio.h> #include <stdlib.h> #include <sys/epoll.h> #include <errno.h> #include <unistd.h> #define MAXLINE 10 int main(void) { int efd, i; int pfd[2]; pid_t pid; char buf[MAXLINE]; char ch = 'a'; pipe(pfd); pid = fork(); // Subprocess write if(pid == 0) { close(pfd[0]); while(1) { // aaaa\n for(i = 0; i < MAXLINE/2; i++) { buf[i] = ch; } buf[i - 1] = '\n'; ch++; // bbbb\n for(; i < MAXLINE; i++) { buf[i] = ch; } buf[i - 1] = '\n'; ch++; // aaaa\nbbbb\n write(pfd[1], buf, sizeof(buf)); sleep(1); write(STDOUT_FILENO, "sleep 1\n", 8); sleep(1); write(STDOUT_FILENO, "sleep 2\n", 8); sleep(1); write(STDOUT_FILENO, "sleep 3\n", 8); sleep(1); write(STDOUT_FILENO, "sleep 4\n", 8); sleep(1); write(STDOUT_FILENO, "sleep 5\n", 8); } close(pfd[1]); } // Parent process read else if(pid > 0) { struct epoll_event event; // epoll_ The last parameter set by CTL struct epoll_event revent[10]; // epoll_wait ready return array int ret, len; close(pfd[1]); efd = epoll_create(10); // Create a red black tree that can hold 10 nodes and return the root of the red black tree event.events = EPOLLIN | EPOLLET; // ET edge trigger // event.events = EPOLLIN; // LT level trigger event.data.fd = pfd[0]; ret = epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], & event); if(ret == -1) { perror("epoll_ctl error"); exit(1); } while(1) { write(STDOUT_FILENO, "epoll_wait...\n", 14); ret = epoll_wait(efd, revent, MAXLINE, -1); // -1 blocking printf("ret %d\n", ret); if(revent[0].data.fd == pfd[0]) { len = read(pfd[0], buf, MAXLINE/2); write(STDOUT_FILENO, buf, len); } } close(pfd[0]); close(efd); } else { perror("fork error"); exit(1); } return 0; }
epoll non blocking IO
This is a very typical application of epoll. It is a common mode. This mode can reduce epoll_ Call wait to improve the efficiency of the program. Because it is a non blocking IO, you need to set the cfd socket with fcntl() to be non blocking.
// client.c #include <stdio.h> #include <stdlib.h> #include <sys/epoll.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <arpa/inet.h> #include <strings.h> #define MAXLINE 10 #define SERV_PORT 8888 int main(void) { struct sockaddr_in servaddr; char buf[MAXLINE]; char ch = 'a'; int sfd, i; sfd = socket(AF_INET, SOCK_STREAM,0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); connect(sfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while(1) { // aaaa\n for(i = 0; i < MAXLINE/2; i++) { buf[i] = ch; } buf[i - 1] = '\n'; ch++; // bbbb\n for(; i < MAXLINE; i++) { buf[i] = ch; } buf[i - 1] = '\n'; ch++; // aaaa\nbbbb\n write(sfd, buf, sizeof(buf)); sleep(1); write(STDOUT_FILENO, "sleep 1\n", 8); sleep(1); write(STDOUT_FILENO, "sleep 2\n", 8); sleep(1); write(STDOUT_FILENO, "sleep 3\n", 8); sleep(1); write(STDOUT_FILENO, "sleep 4\n", 8); sleep(1); write(STDOUT_FILENO, "sleep 5\n", 8); } close(sfd); return 0; }
// server.c #include <stdio.h> #include <stdlib.h> #include <sys/epoll.h> #include <errno.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/types.h> #include <strings.h> #include <fcntl.h> #define MAXLINE 10 #define SERV_PORT 8888 int main(void) { int efd; int lfd, cfd; char buf[MAXLINE]; char ipstr[INET_ADDRSTRLEN]; struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; lfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); bind(lfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); listen(lfd, 128); struct epoll_event ev; struct epoll_event rev[10]; int res, len; efd = epoll_create(10); ev.events = EPOLLIN | EPOLLET; // ET edge trigger //ev.events = EPOLLIN; // LT level trigger printf("Accepting connections...\n"); cliaddr_len = sizeof(cliaddr); cfd = accept(lfd, (struct sockaddr *)&cliaddr, &cliaddr_len); printf("received from %s at port %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, ipstr, sizeof(ipstr)), ntohs(cliaddr.sin_port)); // Modify cfd to non blocking read int flag = fcntl(cfd, F_GETFL); flag |= O_NONBLOCK; fcntl(cfd, F_SETFL, flag); ev.data.fd = cfd; epoll_ctl(efd, EPOLL_CTL_ADD, cfd, &ev); while(1) { printf("epoll_wait begin\n"); res = epoll_wait(efd, rev, 10, -1); printf("epoll_wait end\n"); printf("res %d\n", res); if(rev[0].data.fd == cfd) { // Non blocking read, polling while((len = read(cfd, buf, MAXLINE/2)) > 0) write(STDOUT_FILENO, buf, len); } } return 0; }