Three implementations of I/O multiplexing

 

1.select implementation

(1) selection process

The basic process is:

1. First construct a table of file descriptors; fd_set readfds

2. Clear the table FD_ZERO()

3. Add the file descriptors you care about to this table; FD_SET()

4. Call the select function. selset()

5. Determine which file descriptor or file descriptors generated the event (IO operation); FD_ISSET()

6. Perform corresponding logical processing;       

(2)selset function

Header file: #include<sys/select.h> #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);

Function: Monitor which file descriptors generate events, and block and wait for them to occur.

Parameters: nfds: The maximum number of file descriptions monitored (file descriptors start from 0, here is the number, remember +1)

          readfds: Read event collection; // Keyboard and mouse input, client connection are all read events

          writefds: Write event collection; //NULL means don't care

          exceptfds: exception event collection; //NULL means don't care

          timeout:    set to NULL, wait until a file descriptor changes;

                              Set to a value greater than 0 and will not be returned until the descriptor changes or the timeout expires.

        Timeout detection: If the function function is not completed within the specified time, a timeout information will be returned, and we can set corresponding requirements based on this information;

Return value: <0 error >0 indicates that an event occurred;

                If the timeout detection time is set: &tv

                <0 error >0 means an event occurred; ==0 means the timeout has expired;        

The structure is as follows:                     

            struct timeval {

               long tv_sec; specifies the waiting time in seconds

               long tv_usec; ​​specifies the waiting time in milliseconds

           };

void FD_CLR(int fd, fd_set *set); //Clear fd in the set collection 

int FD_ISSET(int fd, fd_set *set); //Determine whether fd has generated an event in the set collection

void FD_SET(int fd, fd_set *set); //Add fd to the set

void FD_ZERO(fd_set *set); //Clear the set

(3) Select features:

Select Features:

1. A process can only listen to a maximum of 1024 file descriptors  (32-bit) [2048 for 64-bit]

2.  After select is awakened, the driver must be polled again (0-1023), which is inefficient (consuming CPU resources)

3.  Select will clear unresponsive file descriptors each time, and it will need to copy user space tables to kernel space each time, which is inefficient and expensive.

   (0~3G is user mode, 3G~4G is kernel mode, switching back and forth between the two states is very time-consuming and resource-consuming)

 (4) select mechanism: 

1. The header file detects 1024 file descriptors 0-1023

2. Store standard input, standard output, and standard error in select 0~2    

3. The maximum number of file descriptions monitored is fd+1 (if fd = 3, the maximum is 4): //Because it starts from 0    

4. Select is only interested in file descriptors set to 1. If an event occurs, during select detection, the generated file descriptor will remain 1, and if no event occurs, it will be set to 0; 

5. Select will clear the table every time it polls (clear it by setting it to zero) //You need to back up the temporary table before selecting

Exercise 1:

How to respond to mouse events and keyboard events through select?

Code:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>

int main(int argc, char const *argv[])
{
    int fd = open("/dev/input/mouse0", O_RDONLY);
    if (fd < 0)
    {
        perror("open is err:");
        return -1;
    }
    //1.创建表
    fd_set readfds;
    //2/清空表
    FD_ZERO(&readfds);
    //3.设置表
    FD_SET(0, &readfds);
    FD_SET(fd, &readfds);
    fd_set readfdcp = readfds;
    int maxfd = fd;
    char buf[128] = {0};
    while (1)
    {
        //4.检测是否有相应
        select(maxfd + 1, &readfds, NULL, NULL, NULL);
        //5.检测哪一个文件描述符
        if (FD_ISSET(0, &readfds))
        {

            fgets(buf, sizeof(buf), stdin);
            if (buf[strlen(buf) - 1] == '\n')
                buf[strlen(buf) - 1] = '\0';
            printf("key: %s\n", buf);
        }
        if (FD_ISSET(fd, &readfds))
        {

            int ret = read(fd, buf, sizeof(buf));
            buf[ret] = '\0';
            printf("mouse: %s\n", buf);
        }
        readfds = readfdcp;
    }
    return 0;
}

 Exercise 2:

select is a one-to-one correspondence between file descriptors and subscripts, and 0 can only correspond to file descriptor No. 0. Therefore only the largest file descriptor is closed, --len. Note that when adding and deleting, it is for the actual table, not the temporary table.

Use select to implement full duplex of the server

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <signal.h>
#include <sys/select.h>

int acceptfp;
int main(int argc, char const *argv[])
{

    char buf[128] = {0};
    //1.创建套接字,返回建立链接的文件描述符
    int sockfp = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfp == -1)
    {
        perror("socket is err");
        exit(0);
    }
    printf("%d\n", sockfp);

    //2.绑定ip和端口号
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    socklen_t len = sizeof(struct sockaddr_in);

    if (bind(sockfp, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("bind is err");
        exit(0);
    }

    //3.listen监听
    if (listen(sockfp, 5))
    {
        perror("liste err");
        exit(0);
    }
    printf("listen ok\n");

    //1.创建表
    fd_set readfds;
    //2/清空表
    FD_ZERO(&readfds);
    //3.设置表
    FD_SET(0, &readfds);
    FD_SET(sockfp, &readfds);
    fd_set readfdcp = readfds;
    int maxfd = sockfp;
    struct timeval st;
    while (1)
    {
        readfds = readfdcp;
        //4.检测是否有响应
        st.tv_sec = 5;
        st.tv_usec = 0;
        int ret = select(maxfd + 1, &readfds, NULL, NULL, &st);
        if (ret < 0)
        {
            perror("select err");
            return -1;
        }
        else if (ret == 0)
        {
            printf("无响应\n");
        }
        //0响应,证明服务器要发送消息
        if (FD_ISSET(0, &readfds))
        {
            fgets(buf, sizeof(buf), stdin);
            if (buf[strlen(buf) - 1] == '\n')
                buf[strlen(buf) - 1] = '\0';
            for (int i = 4; i <= maxfd; ++i)
            {
                send(i, buf, sizeof(buf), 0);
            }
        }
        //sockfp,监听套接字响应证明,有客户端要链接
        if (FD_ISSET(sockfp, &readfds))
        {
            acceptfp = accept(sockfp, (struct sockaddr *)&caddr, &len);
            if (acceptfp < 0)
            {
                perror("acceptfp");
                exit(0);
            }
            printf("port:%d   ip:  %s\n", ntohs(caddr.sin_port), inet_ntoa(caddr.sin_addr));
            FD_SET(acceptfp, &readfdcp);
            if (acceptfp > maxfd)
                maxfd = acceptfp;
        }
        //检测客户端,检查是哪一个客户端发送的消息
        for (int i = 4; i <= maxfd; ++i)
        {
            if (FD_ISSET(i, &readfds))
            {
                int recvbyte = recv(i, buf, sizeof(buf), 0);
                if (recvbyte < 0)
                {
                    perror("recv err");
                    return -1;
                }
                else if (recvbyte == 0)
                {
                    printf("%d client is exit\n", i);
                    close(i);
                    FD_CLR(i, &readfdcp);
                    if (i == maxfd)
                        --maxfd;
                }
                else
                {
                    printf("%d : %s\n", i, buf);
                }
            }
        }
    }
    return 0;
}

Exercise 3:

Use select to implement full-duplex client

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>

int main(int argc, const char *argv[])
{
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd < 0)
	{
	   perror("socker is err:");
	   return -1;
	}

    struct sockaddr_in saddr;
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(atoi(argv[1]));
	saddr.sin_addr.s_addr = inet_addr(argv[2]);

    if(connect(sockfd,(struct sockaddr *)&saddr,sizeof(saddr)) < 0)
    {
        perror("connect is err:");
        return -1;
    }
   //1.创建表
   fd_set readfds,tempfds;
   //2.清空表
   FD_ZERO(&readfds);
   FD_ZERO(&tempfds);
   //3.添加文件描述符
   FD_SET(0,&readfds);
   FD_SET(sockfd,&readfds);
   
   int maxfd = sockfd;
   int ret;
   char buf[128];
   while(1)
   {
       tempfds = readfds;
      //4.调select检测
       ret = select(maxfd+1,&tempfds,NULL,NULL,NULL);
       if(ret < 0)
       {
           perror("select is err:");
           return -1;
       }
       if(FD_ISSET(0,&tempfds))
       {
           fgets(buf,sizeof(buf),stdin);
              if(buf[strlen(buf)-1] == '\n')
                 buf[strlen(buf)-1] = '\0';
        
          send(sockfd,buf,sizeof(buf),0);
       }
       if(FD_ISSET(sockfd,&tempfds))
       {
           int recvbyte = recv(sockfd,buf,sizeof(buf),0);
           if(recvbyte < 0)
           {
               perror("recv is err:");
               return -1;
           }
           printf("%s\n",buf);
       }
   }
   close(sockfd);
 return 0;
}

(5) Select timeout detection:

The necessity of timeout detection:

1. Avoid unlimited blocking of the process when there is no data;

2. If the function of the statement is not completed within the specified time, the relevant function will be executed;

The structure is as follows:                     

            struct timeval {

               long tv_sec; specifies the waiting time in seconds

               long tv_usec; ​​specifies the waiting time in milliseconds

           };

2.poll implementation

 (1) poll process

Usage: 1. First create the structure array struct pollfd fds[100];

          2. Add the file descriptor of the structure member and the trigger method fds[0].fd =? ;fds[0].events = POLLIN 

          3. Save the subscript of the last valid element in the array       

          4. Call the function poll ret = poll(fds,nfds+1,-1);

          5. Determine whether the file descriptor in the structure triggers an event fds[i].revents == POLLIN

          6. Trigger different events based on different file descriptors 

(2) poll function

声明:int poll(struct pollfd *fds, nfds_t nfds, int timeout);

Header file: #include<poll.h>

Function: Monitor and wait for attribute changes of multiple file descriptors

parameter:

  1.struct pollfd *fds: the file descriptor array you care about, the size is defined by yourself

   If you want to detect more file descriptors, create a structure array struct pollfd fds[N]; 

           struct pollfd

           {

                  int fd; //File descriptor

             short events; //Waiting event trigger conditions----POLLIN read time trigger (most)

             short revents; //Actually occurred events (no events occurred: 0 ))

            }

    2. nfds: maximum number of file descriptors

    3. timeout: timeout detection (millisecond level): 1000 == 1s      

                          If -1, block if 0, do not block

Return value: <0 error >0 indicates that an event occurred;

              If the timeout detection time is set: &tv

                <0 error >0 means an event occurred; ==0 means the timeout has expired;

(3) Poll characteristics

1. Optimize the limit on the number of file descriptors;

(Determined based on the parameters of the first function of the poll function. If the number of events to be monitored is 1, the capacity of the structure array is 1. If you want to monitor 100 events, then the capacity of the structure array is 100. How many file descriptors are there ? It’s up to the programmer to decide )

2. After poll is awakened, the driver needs to be polled again, which is relatively inefficient (consuming CPU)

3. Poll does not need to reconstruct the file descriptor table (nor clear the table), it only needs to copy data from user space to kernel space (relatively high efficiency)

practise: 

Use poll to implement full duplex of the server

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <signal.h>
#include <sys/select.h>
#include <poll.h>

int acceptfp;
int main(int argc, char const *argv[])
{

    char buf[128] = {0};
    //1.创建套接字,返回建立链接的文件描述符
    int sockfp = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfp == -1)
    {
        perror("socket is err");
        exit(0);
    }
    printf("%d\n", sockfp);

    //2.绑定ip和端口号
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    socklen_t len = sizeof(struct sockaddr_in);

    if (bind(sockfp, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("bind is err");
        exit(0);
    }

    //3.listen监听
    if (listen(sockfp, 5))
    {
        perror("liste err");
        exit(0);
    }
    printf("listen ok\n");
    //1.创建结构体数组
    struct pollfd fds[100];
    //2.添加文件描述符和触发方式
    fds[0].fd = 0;
    fds[0].events = POLLIN;

    fds[1].fd = sockfp;
    fds[1].events = POLLIN;
    int nfds = 1;
    int ret;
    while (1)
    {
        //3.poll轮循检测
        ret = poll(fds, nfds + 1, 2000);
        if (ret < 0)
        {
            perror("poll is err");
            return -1;
        }
        else if (ret == 0)
        {
            printf("qeqweqe\n");
            continue;
        }
        //4. 判断哪一个文件描述符产生响应,并发布任务
        for (int i = 0; i <= nfds; ++i)
        {
            if (fds[i].revents == POLLIN)
            {
                if (fds[i].fd == 0)
                {
                    fgets(buf, sizeof(buf), stdin);
                    if (buf[strlen(buf) - 1] == '\n')
                        buf[strlen(buf) - 1] = '\0';
                    //printf("发送信息:\n");
                    for (int j = 2; j <= nfds; ++j)
                    {
                        send(fds[j].fd, buf, sizeof(buf), 0);
                    }
                }
                else if (fds[i].fd == sockfp)
                {
                    acceptfp = accept(sockfp, (struct sockaddr *)&caddr, &len);
                    if (acceptfp < 0)
                    {
                        perror("acceptfp");
                        exit(0);
                    }
                    printf("port:%d   ip:  %s\n", ntohs(caddr.sin_port), inet_ntoa(caddr.sin_addr));
                    fds[++nfds].fd = acceptfp;
                    fds[nfds].events = POLLIN;
                }
                else
                {
                    int recvbyte = recv(fds[i].fd, buf, sizeof(buf), 0);
                    if (recvbyte < 0)
                    {
                        perror("recv err");
                        return -1;
                    }
                    else if (recvbyte == 0)
                    {
                        printf("%d client is exit\n", i);
                        close(fds[i].fd);
                        //覆盖
                        fds[i] = fds[nfds];
                        //--i,--nfds后,最后一个循环不到
                        --nfds, --i;
                    }
                    else
                    {
                        printf("%d : %s\n", i, buf);
                    }
                }
            }
        }
    }
    return 0;
}

(4) Poll timeout detection

 timeout: timeout detection (millisecond level): 1000 == 1s      

                  If -1, block if 0, do not block

3. epoll implementation

(1) epoll process:

How to use Epoll:

1. Create red-black tree and ready linked list                                       int epfd = epoll_create(1);

2. Add file descriptors and event information to the tree

    event.events = EPOLLIN|EPOLLET;

    event.data.fd = 0;

    epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event

3. Block and wait for the event to occur. Once the event occurs, process it 

     int ret = epoll_wait(epfd,events,32,-1);

4. Process according to the file descriptor prepared to be processed in the chain

(2) epoll function family 

epoll uses a set of functions: epoll_create creates a red-black tree and an ordered linked list

                                          epoll_ctl adds file descriptors and events to/removes from the tree

                                          epoll_wait waits for an event to occur

epoll_create 

Create red-black trees and linked lists

Header file: #include <sys/epoll.h>

声明:int epoll_create(int size);

Function: Create a red-black tree root node (create an epoll instance), and also create a ready linked list

Return value: Returns an instance (binary tree handle) on success, returns -1 on failure.

epoll_ctl

Control the epoll attribute

声明: int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

Function: Control epoll attributes, such as adding nodes to red-black trees

Parameters: 1. epfd: the return handle of the epoll_create function. //an identifier

          2. op: indicates the action type, and has three macros:         

                EPOLL_CTL_ADD: Register new fd into epfd

                EPOLL_CTL_MOD: Modify the listening events of registered fd

                EPOLL_CTL_DEL: delete an fd from epfd

3. The file descriptor to be operated on

4. Structure information:

        typedef union epoll_data {

                int fd; //The file descriptor to be added, only use this

                uint32_t u32; typedef unsigned int

                uint64_t u64; typedef unsigned long int

        } epoll_data_t;

        struct epoll_event {

                uint32_t events; events

                epoll_data_t data; //Union (see above)

        };

           About events:

            EPOLLIN: Indicates that the corresponding file descriptor is readable

            EPOLLOUT: writable

            EPOLLPRI: Emergency data is available for reading;

            EPOLLERR: error;

            EPOLLHUP: hung up;

            EPOLLET: trigger mode, edge trigger; (edge ​​trigger is used by default)

            ET mode: indicates changes in status;

           NULL: Delete a file descriptor, no event

Return value: success: 0, failure: -1

epoll_wait

Wait for an event to occur

声明: int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

Function: Wait for events to occur

   The kernel will look for file descriptors in the red-black tree that respond to events and put these file descriptors into the ready list

    The contents of the ready list will be copied to the second parameter events at the same time when epoll_wait is executed.

Parameters: epfd:handle;

           events: Used to save a collection of events responded to from the ready list; (outgoing parameters, defining structure array)

           maxevents: indicates the number of response events fetched from the linked list each time;

           timeout: timeout, milliseconds, 0 returns immediately, -1 blocks

Return value: Success: The actual number taken out from the linked list. Returns -1 on failure.

(4) epoll features

1. There is no limit on the maximum number of file descriptors monitored (depending on your own system, 1GB - about 100,000)

2. Asynchronous I/O, epoll. When an event occurs and is awakened, the file descriptor actively calls the callback (callback function) function to directly obtain the awakened file descriptor, without polling, and is highly efficient.

3.epoll does not need to reconstruct the file descriptor table, it only needs to copy the data from user space to kernel space.

(5) epoll mechanism

Select and poll both belong to the synchronous IO mechanism (polling)

epoll is an asynchronous IO mechanism (not polling): 

Epoll handles high concurrency, millions of levels

  1. Red-black tree: It is a special binary tree (each node has attributes). How can Epoll monitor many of them? First create the root node of the tree. Each node is an fd stored in the form of a structure (the node contains some attributes and callback functions)
  2. Ready linked list: When an event occurs on a file descriptor, the callback function is automatically called, and the event corresponding to the linked list (read time or write event) is found through the callback function.

 

 practise:

epoll implements server

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <signal.h>
#include <sys/select.h>
#include <poll.h>
#include <sys/epoll.h>

int acceptfp;
int main(int argc, char const *argv[])
{

    char buf[128] = {0};
    //1.创建套接字,返回建立链接的文件描述符
    int sockfp = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfp == -1)
    {
        perror("socket is err");
        exit(0);
    }
    printf("%d\n", sockfp);

    //2.绑定ip和端口号
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    socklen_t len = sizeof(struct sockaddr_in);

    if (bind(sockfp, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("bind is err");
        exit(0);
    }

    //3.listen监听
    if (listen(sockfp, 5))
    {
        perror("liste err");
        exit(0);
    }
    printf("listen ok\n");
    //1.创建红黑树以及链表
    //树的跟节点/树的句柄
    int epfd = epoll_create(1);
    //2.上树
    struct epoll_event event;
    struct epoll_event events[32] ;
    event.events = EPOLLET | EPOLLIN;

    event.data.fd = 0;
    epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &event);

    event.data.fd = sockfp;
    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfp, &event);

    while (1)
    {
        //3.阻塞等待文件描述符产生事件
        int ret = epoll_wait(epfd, events, 32, -1);
        printf("asdsdfgdsf\n");
        if (ret < 0)
        {
            perror("epoll err");
            return -1;
        }
        //4.根据文件描述符号,进行处理
        for (int i = 0; i < ret; ++i)
        {
            if (events[i].data.fd == 0)
            {
                fgets(buf, sizeof(buf), stdin);
                if (buf[strlen(buf) - 1] == '\n')
                    buf[strlen(buf) - 1] = '\0';
                printf("发送信息:\n");
                //send(fds[j].fd, buf, sizeof(buf), 0);
            }
            else if (events[i].data.fd == sockfp)
            {
                acceptfp = accept(sockfp, (struct sockaddr *)&caddr, &len);
                if (acceptfp < 0)
                {
                    perror("acceptfp");
                    exit(0);
                }
                printf("port:%d   ip:  %s\n", ntohs(caddr.sin_port), inet_ntoa(caddr.sin_addr));
                //上树
                event.data.fd = acceptfp;
                epoll_ctl(epfd, EPOLL_CTL_ADD, acceptfp, &event);
            }
            else
            {
                int recvbyte = recv(events[i].data.fd, buf, sizeof(buf), 0);
                if (recvbyte < 0)
                {
                    perror("recv err");
                    return -1;
                }
                else if (recvbyte == 0)
                {
                    printf("%d client is exit\n", events[i].data.fd);
                    close(events[i].data.fd);
                    //下树
                    epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
                }
                else
                {
                    printf("%d : %s\n", events[i].data.fd, buf);
                }
            }
        }
    }

    return 0;
}

Compared 

Guess you like

Origin blog.csdn.net/m0_73731708/article/details/132912805