TCP/IP network programming -- better than select epoll
We have discussed the select method for implementing I/O multiplexing in concurrent servers. However, the select method has low performance and is not suitable for the mainstream modern development environment with Web server development. Therefore, epoll in Linux, kqueue in BSD,/dev/poll in Solaris, and IOCP in Windows are available. This chapter describes epoll Technology in Linux.
Epoll understanding and application
The reason why select-based I/O multiplexing is slow:
1. A common loop statement for all file descriptors after the select function is called. Every time an event occurs, it needs to traverse all file descriptors to find the file descriptors that have changed. (Loop is not added in the previous example)
2. Each time you call the select function, you must pass the monitoring object information to the function. That is, when the select function is called each time, the monitoring object information is transmitted to the operating system. Why? It is because of the socket change function we monitor, and the socket is managed by the operating system. (This is the most efficient)
Note: the reason for this does not mean that the select statement is useless. In this case, select: 1 is suitable, and there are two fewer access persons on the server. The program should be compatible.
How does epoll optimize the select statement:
1. Every time an event occurs, it does not need to traverse all file descriptors cyclically. It aggregates the changed file descriptors separately.
2. Only the information of the Monitored object is transmitted to the operating system. Only the changed items are notified when the monitoring scope or content changes.
Functions and struct necessary for epoll implementation
- Function:
-
- Epoll_create: creates a space for saving the epoll file descriptor. This function also returns the file descriptor. Therefore, you must call the close function when terminating the operation. (Create memory space)
-
- Epoll_ctl: register with the space, add or modify the file descriptor. (Register a listener event)
-
- Epoll_wait: similar to the select function, it waits for the file descriptor to change. (Listener Event Callback)
-
- Struct:
-
- Struct epoll_event
- {
- _ Uint32_t events;
- Epoll_data_t data;
- }
- Typedef union epoll_data
- {
- Void * ptr;
- Int fd;
- _ Uinit32_t u32;
- _ Uint64_t u64;
- } Epoll_data_t;
Epoll-based echo server
- //
-
- // Main. cpp
-
- // Hello_server
-
- //
-
- // Created by app05 on 15-10-19.
-
- // Copyright (c) 2015 app05. All rights reserved.
-
- //
-
- # Include
-
- # Include
-
- # Include
-
- # Include
-
- # Include
-
- # Include
-
- # Include
-
- # Define BUF_SIZE 100
-
- # Define EPOLL_SIZE 50
-
- Void error_handling (char * buf );
-
- Int main (int argc, const char * argv []) {
-
- Int serv_sock, clnt_sock;
-
- Struct sockaddr_in serv_adr, clnt_adr;
-
- Socklen_t adr_sz;
-
- Int str_len, I;
-
- Char buf [BUF_SIZE];
-
- // Similar to the select fd_set variable, you can view the status changes of the Monitored object. The epoll_event struct aggregates the changed file descriptors separately.
-
- Struct epoll_event * ep_events;
-
- Struct epoll_event event;
-
- Int epfd, event_cnt;
-
- If (argc! = 2)
-
- {
-
- Printf ("Usage: % s \ n", argv [0]);
-
- Exit (1 );
-
- }
-
- Serv_sock = socket (PF_INET, SOCK_STREAM, 0 );
-
- If (serv_sock =-1)
-
- Error_handling ("socket () error ");
-
- Memset (& serv_adr, 0, sizeof (serv_adr ));
-
- Serv_adr.sin_family = AF_INET;
-
- Serv_adr.sin_addr.s_addr = htonl (INADDR_ANY );
-
- Serv_adr.sin_port = htons (atoi (argv [1]);
-
- If (bind (serv_sock, (struct sockaddr *) & serv_adr, sizeof (serv_adr) =-1)
-
- Error_handling ("bind () error ");
-
- If (listen (serv_sock, 5) =-1)
-
- Error_handling ("listen () error ");
-
- // The bucket for creating a file descriptor is called the "epoll routine"
-
- Epfd = epoll_create (EPOLL_SIZE );
-
- Ep_events = malloc (sizeof (struct epoll_event) * EPOLL_SIZE );
-
- // Add monitoring for read events (register events)
-
- Event. events = EPOLLIN; // read Data events
-
- Event. data. fd = serv_sock;
-
- Epoll_ctl (epdf, EPOLL_CTL_ADD, serv_sock, & event );
-
- While (1)
-
- {
-
- // Responds to the event and returns the number of file descriptors of the event.
-
- Event_cnt = epoll_wait (epfd, ep_events, EPOLL_SIZE,-1); // when-1 is passed, wait until the event occurs
-
- If (event_cnt =-1)
-
- {
-
- Puts ("epoll_wait () error ");
-
- Break;
-
- }
-
- // Server socket and client socket
-
- For (I = 0; I <event_cnt; I ++ ){
-
- If (ep_events [I]. data. fd = serv_sock) // connect the server to the client.
-
- {
-
- Adr_sz = sizeof (clnt_adr );
-
- Clnt_sock = accept (serv_sock, (struct sockaddr *) & clnt_adr, & adr_sz );
-
- Event. events = EPOLLIN;
-
- Event. data. fd = clnt_sock;
-
- Epoll_ctl (epfd, EPOLL_CTL_ADD, clnt_sock, & event );
-
- Printf ("connected client: % d \ n", clnt_sock );
-
- }
-
- Else // transmit data after connection
-
- {
-
- Str_len = read (ep_events [I]. data. fd, buf, BUF_SIZE );
-
- If (str_len = 0)
-
- {
-
- // Delete the event
-
- Epoll_ctl (epfd, EPOLL_CTL_DEL, ep_events [I]. data. fd, NULL );
-
- Close (ep_events [I]. data. fd );
-
- Printf ("closed client: % d \ n", ep_events [I]. data. fd );
-
- }
-
- Else
-
- {
-
- Write (ep_events [I]. data. fd, buf, str_len );
-
- }
-
- }
-
- }
-
- }
-
- Close (serv_sock );
-
- Close (epfd );
-
- Return 0;
-
- }
-
- Void error_handling (char * message)
-
- {
-
- Fputs (message, stderr );
-
- Fputc ('\ n', stderr );
-
- Exit (1 );
-
- }
Conditional trigger and edge trigger
What are conditional and edge triggers? They are the event response methods, and epoll is the conditional trigger method by default. Conditional trigger means that the event is always notified as long as there is data in the input buffer, and the epoll_wait is returned cyclically. Edge trigger means that only one event is registered when the input buffer receives the data. even if there is still data in the input buffer, it will not be registered and only respond once.
Edge trigger has the advantages of relative conditional trigger: It can separate the time points for receiving and processing data. In terms of model implementation, edge trigger is more likely to bring high performance.
Change the above epoll instance to edge trigger:
1. First rewrite event. events = EPOLLIN | EPOLLET; (EPOLLIN: Read data event EPOLLET: edge Triggering Method)
2. Edge trigger only responds to receive data events once. to read all the data in the input buffer at a time, you need to determine when the data is read? Linux declares a global variable: int errno; (in error. h), which records the additional information provided when an error occurs. Here we can use it to determine whether to read the data:
- Str_len = read (...);
-
- If (str_len <0)
-
- {
-
- If (errno = EAGAIN) // mark for reading all data in the input buffer
-
- Break;
-
- }
3. In the edge trigger mode, the read and write operations that work in blocking mode may cause the server to pause for a long time. Therefore, edge triggering must adopt non-blocking Socket data transmission. So how can we change the data transmission mode of the socket to non-blocking mode?
// Fd socket file descriptor, modifying the socket data transmission mode to non-blocking
- Void setnonblockingmode (int fd)
-
- {
-
- Int flag = fcntl (fd, F_GETFL, 0); // obtain the original properties of the socket.
-
- Fcntl (fd, F_SETFL, flag | O_NONBLOCK); // you can add a non-blocking mode based on the original attribute.
-
- }