For the poll model listener events and return events, we define the following macros:
#define kreadevent (Pollin | POLLPRI)#define kwriteevent (pollout | Pollwrband)#define kreadrevent (Pollin | Pollpri | Pollrdhup)#define kwriterevent (pollout)
We explained earlier why the non-blocking IO must have buffers. In fact, for server, each TCP connection should have two buffers, one for input and one for output.
sockfd–> inputbuffer–> User space –> processing data –> Get results –> outputbuffer–> SOCKFD
But this example is simply a back-up service that receives data immediately, so we only use one buffer.
For each TCP connection, we correspond to a data structure such as:
struct { // represents a TCP connection
Then we need to establish a correspondence between FD and the corresponding tcp_connection_t. I use the following array:
// provides mappings from FD to TCP connections
This array is initialized to NULL, then I use FD as subscript, that is, when I need to process an FD, I can find the corresponding tcp_connection_t pointer with the FD as subscript. This is essentially the idea of a hash table .
The logic for using tcp_connection_t is:
Whenever you accept a new connection, a new tcp_connection_t is created dynamically, and its pointer is saved to a location in which PEERFD is subscript .
Each time the connection is closed, the memory needs to be freed and the pointer is reset to NULL.
I wrote all the code in the main function, because buffer was previously encapsulated, the readability of the code was improved, and our business logic was simple, further encapsulated, but less readable.
There are several similarities with the client:
Listen for read events only if the buffer has free space
Listen for write events only when the buffer has data
So we need to reload the FD's listener event before each poll call.
The overall framework for server code is as follows:
// Initialize Poll while (1) { // reload fd array //poll system call // handle Read and write events for each FD sequentially }
So the complete code is as follows:
#define_gnu_source/* See Feature_test_macros (7) */#include<sys/socket.h>#include"sysutil.h"#include"Buffer.h"#include<assert.h>#defineEvents_size 1024typedefstruct{buffer_t buffer_;} tcp_connection_t;//represents a TCP connectiontcp_connection_t*connsets[events_size];//provides mappings from FD to TCP connectionsintMainintargcChar Const*argv[]) { //Get listener FD intLISTENFD = Tcp_server ("localhost",9981); //set the Listen FD to non-blockingActivate_nonblock (LISTENFD); //Initialize Connsets inti; for(i =0; i < events_size; ++i) {connsets[i]=NULL; } //Initialize Poll structPOLLFD Events[events_size]; for(i =0; i < events_size; ++i) events[i].fd= -1; events[0].FD =LISTENFD; events[0].events =kreadevent; intMaxi =0; while(1) { //Reload Events Array inti; for(i =1; i < events_size; ++i) {intFD =EVENTS[I].FD; Events[i].events=0;//Reset Events if(FD = =-1) Continue; ASSERT (CONNSETS[FD]!=NULL); //Listen for write events when data is readable in buffer if(Buffer_is_readable (&connsets[fd]->buffer_)) {events[i].events|=kwriteevent; } //The Read event is only monitored when there is free space in buffer if(Buffer_is_writeable (&connsets[fd]->buffer_)) {events[i].events|=kreadevent; } } //Poll Call intNready = Poll (events, Maxi +1, the); if(Nready = =-1) Err_exit ("Poll"); Else if(Nready = =0) {printf ("Poll timeout.\n"); Continue; } //Handling LISTENFD if(events[0].revents &kreadevent) { //accept a new customer FD intPEERFD = ACCEPT4 (LISTENFD, NULL, NULL, Sock_nonblock |sock_cloexec); if(PEERFD = =-1) Err_exit ("Accept4"); //New TCP connectiontcp_connection_t *pt = (tcp_connection_t*) malloc (sizeof(tcp_connection_t)); Buffer_init (&pt->buffer_); //Place the TCP connection into the ConnsetsCONNSETS[PEERFD] =pt; //put into events array inti; for(i =0; i < events_size; ++i) {if(EVENTS[I].FD = =-1) {EVENTS[I].FD= PEERFD;//There's no need to listen to FD if(I >Maxi) Maxi= i;//Update Maxi Break; } } if(i = =events_size) {fprintf (stderr,"Too many clients\n"); Exit (Exit_failure); } } //Process Customer FD//int i; for(i =1; I <= Maxi; ++i) {intSOCKFD =EVENTS[I].FD; if(SOCKFD = =-1) Continue; //Remove the pointertcp_connection_t *pt =CONNSETS[SOCKFD]; Assert (PT!=NULL); if(Events[i].revents & Kreadrevent)//reading Data { if(Buffer_read (&pt->buffer_, sockfd) = =0) { //CloseEVENTS[I].FD =-1; Close (SOCKFD); Free (PT); CONNSETS[SOCKFD]=NULL; Continue;//continue the next cycle } } if(Events[i].revents & Kwriterevent)//can send data{buffer_write (&pt->Buffer_, SOCKFD); }}} close (LISTENFD); return 0;}
It is important to note that, regarding accept, I am using ACCPET4, which is a new system call from Linux:
int accept4 (intstructint flags);
The difference from accept is that ACCEPT4 can specify a non-blocking flag bit.
Use Epoll below.
Linux non-blocking IO (vi) Implementation of non-blocking server-side using poll