Multiplex IO transfer server

Posted by ROCKINDANO on Mon, 17 Jan 2022 08:43:18 +0100

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

  1. 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.
  2. 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:

  1. poll can break the upper limit of 1024 open file descriptors.
  2. Listening and returning sets are separated.
  3. 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;

}

Topics: socket Concurrent Programming epoll