System Programming Linux - Network Programming Socket I / O multiplexer (select) Server Programming

I / O multiplexing model

A call to select or poll, blocked in both system call on one, instead of blocking on the real I / O system calls. To select call blocking, waiting for a datagram socket readable. When the select returns socket readable condition, copying call recevfrom datagram to the application buffer.
Here Insert Picture Description

This is one of the five I / O model, it is also the model select function,
compared to multi-threaded and multi-process, I / O multiplexing in the context of a single process, when there are multiple concurrent connection requests, multi-threaded or multi-process model need to create a thread or process for each connection, while most of these processes or threads are blocked up. Since the number of CPU cores is generally not large, such as four core threads run 1000, then the time slot of each thread is very short, but very frequent thread switching. This is a problem. While the I / O multiplexer, connected to a plurality of processing threads need only monitor a ready state, ready to open for each connection a threading (supported by the thread pool) can be, such processes require a line A number greatly reduced, reducing the CPU overhead and memory overhead of context switching.

Other models will not go into details, here go into detail select server programming;

select function

This function allows a process to instruct the kernel to wait for one or more of the plurality of events, and wake it only after a specified time. It may not quite understand, here is the definition of the function:

#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);
返回: 若有就绪描述符则为其数目,超时为 0 ,出错为 -1

nfds: parameter specifies the number of descriptors to be tested, which is the maximum value of the descriptor to be tested + 1,

Three intermediate parameters readfds, writefds and exceptfds specified kernel testing we want to read, write, and abnormal condition descriptor, if certain conditions we are not interested in a condition which can be set to a null pointer

Any ready to spend how much time the number of seconds and microseconds, it tells the kernel to wait for the specified file descriptor: timeout.

struct timeval {
  long   tv_sec;      /* seconds */
  long   tv_usec;     /* microseconds */  

This parameter has three possibilities:
1. wait forever: a file descriptor in ready only good I / O when returned, for the parameter to null pointer;
2. Wait for a period of time: in a descriptor when ready to return I / O, but not more than timeval structure pointed to by this parameter specified by adding the number of seconds and microseconds;
3 do not wait: check the return immediately after the descriptor, which is called polling (polling). For this purpose, the function must point to a timeval structure, and wherein a timer (seconds, microseconds) must be 0.

Descriptor set operator

selcet use descriptor set, typically an array of integers, where each integer corresponding to each bit in a descriptor.

void FD_ZERO(fd_set *fd_set);
void FD_SET(int fd,fd_set *fdset);
void FD_CLR(int fd,fd_set *fdset);
int FD_ISSET(int fd,fd_set *fdset);

But we use the definition of a fd_set descriptor set, required FD_ZERO () to initialize;
the FD_SET () of interest to the collection of file descriptors;
FD_CLR macros () is removed;
FD_ISSET () determines which set of the designated file descriptor is ready, in the server code, these collections may be new client requests a connection (listenfd), may also be connected to the client data sent through this function, you can let the server know that now is the time to receive a new The client (accept) or read (read);

select function returns

Because, after selcet function returns, the descriptor will not ready bit is cleared concentration corresponding to the file descriptor (in response does not occur), and then FD_ISSET () query descriptor is ready (new client requests a connection, or connected client to request data);
because every return will be cleared, so we need to use an array before saving those already established connections and descriptors for listening listenfd, in a call to select it blocked , the values in the array must be assigned one by one to the character set;

Diagram

Server initial state

After creating the server uses select, the client has not connected to the server Status:
Here Insert Picture Description
this black point is created socket listenfd;
server only maintains a set of read descriptor, then the descriptor 0,1,2 positions are set to be standard input, standard output and standard error; listening descriptor immediately after
Here Insert Picture Description
this time, only the descriptors created for monitoring, we have the descriptor stored in the array is generally 3, recycled FD_SET function, present in the file descriptor array, added to the character set, character set on the realization that this description character size corresponding bit is 1, the program will select the blocked;

The first client connection

When the first client to establish a connection with the server, the listener becomes readable descriptors, among other SELECT bit is cleared to return to the open standard, the program calls the accept function returns a new file descriptor, and the descriptor is added to the array, a first client connection is typically 4;
Here Insert Picture Description
At this time, the array and descriptor set is this:
Here Insert Picture Description
from now on, a new descriptor for each program must be connected by an array of records, and added description Fu focus is FD4 .

Connecting a second client

Note that the client is connected, the array with the character set:
Here Insert Picture Description
the program must be newly connected client side records, i.e. the first size item descriptors fill at -1, then he added to the character set, FD5 , i.e., the corresponding bit will be set to 1;

Suppose this time, the first client connected disconnected after data is sent to the terminal, select blocking function will return, and process incoming data, then the descriptor array has exited is set to -1 ( clean up the room, waiting for other guests);
when selcet return:
Here Insert Picture Description
At this point, calling FD_ISSET () to find out which one descriptor is ready, then you can find related reading and writing! Of course, multiple servers may also be sent to the data, it may be connected to the new client. Similarly, in response to a position, in response to the position 0, because we each descriptor stored in the array, so not worried about set to zero before the next can be blocked again one assignment;
array after the client exits:
Here Insert Picture Description
previous select the next obstruction, it will be the contents of the array in rset assigned;
select will continue to monitor descriptor set more than a descriptor, soldiers to be blocked, the water to soil cover;

select the server code

/*********************************************************************************
 *      Copyright:  (C) 2020 Xiao yang System Studio
 *                  All rights reserved.
 *
 *       Filename:  select_server.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(03/09/2020)
 *         Author:  Lu Xiaoyang <[email protected]>
 *      ChangeLog:  1, Release initial version on "03/09/2020 06:29:03 PM"
 *                 
 ********************************************************************************/
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>
#include <libgen.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <unistd.h>

#define ARRAY_SIZE(x)   (sizeof(x)/sizeof(x[0]))   //用来计算数组大小(多少个项)

static inline void print_usage(char *progname);   //打印帮助信息
int socket_Be_ready(char *listen_ip,int listen_port);   //将服务器的socket(),bind(),listen() 包装成一个函数;

int main(int argc,char *argv[])
{
    char              buf[1024];
    char              *progname;
    int               listenfd,clifd;
    int               maxfd = 0;
    int               serv_port;
    int               rv;
    int               daemon_run = 0;   //与程序后台运行有关
    fd_set            allset;          //添加感兴趣的文件描述符的集合位;
    int               fds_array[1024];   //用来存放文件描述符的数组
    int               found = 0;
    int               i;
    int               opt = 0;
    struct option     opts[] = {
        {"port",required_argument,NULL,'p'},
        {"daemon",no_argument,NULL,'d'},
        {"help",no_argument,NULL,'h'},
        {NULL,0,NULL,0}
    };                                  //上下这一块用来接收命令行参数执行对应的赋值或者打印相关信息
    
    progname = basename(argv[0]);
    while((opt = getopt_long(argc,argv,"p:dh",opts,NULL)) != -1)
    {
        switch(opt)
            {
                case 'p':
                    serv_port = atoi(optarg);
                    break;

                case 'h':
                    print_usage(progname);
                    break;

                case 'd':
                    daemon_run = 1;
                    break;

                default:
                    break;
            }
    }

    if(!serv_port)   //未接收到用户传的端点;
    {
        print_usage(progname);
        return -1;
    }

    if((listenfd = socket_Be_ready(NULL,serv_port)) < 0)   //创建服务器;
    {
        perror("Socket create failure");
        return -2;
    }

    printf(" server start listen on port[%d]\n",serv_port);

    if(daemon_run)
    {
        daemon(0,0);   //用户加入了 -d 选项后,服务器将会在后台运行,不会占用窗口;
    }

    for(i = 0;i < ARRAY_SIZE(fds_array);i++)
    {
        fds_array[i] = -1;   //最小的文件描述符为0;将数组全部置-1,相当于把房间清空,等待人进来居住;可使用循环判断 fds_arry[i] < 0 来判断该位置是否空;
    }

    fds_array[0] = listenfd;   //将用来监听的文件描述符存放在数组的第一个位置;
   
    for( ; ; )
    {
        FD_ZERO(&allset);
        for(i = 0;i < ARRAY_SIZE(fds_array);i++)
        {
            if(fds_array[i] < 0)
                continue;
            maxfd = fds_array[i] > maxfd ? fds_array[i] : maxfd;
            FD_SET(fds_array[i],&allset);
        }                                        //selcet在返回后会清零未就绪的描述符对应位,所以每一次使用都要赋值,找到最大位;

        rv = select(maxfd+1,&allset,NULL,NULL,NULL);   //程序阻塞在此,等待字符集中的响应;
        if(rv < 0)
        {
            perror("select failure");
            break;
        }

        else if(rv == 0)
        {
            printf("Time Out\n");
            break;
        }

        if(FD_ISSET(listenfd,&allset))   //新的客户端发来连接请求,则使用 accept() 函数处理;
        {
           if((clifd = accept(listenfd,(struct sockaddr  *)NULL,NULL)) < 0)
           {
               perror("Accept new client failure");
               continue;
           }
           for(i = 1;i < ARRAY_SIZE(fds_array);i++)   //找到一个为 -1 的项,说明这个位置未被占用;
           {
               if(fds_array[i] < 0)
               {
                   printf("Put new clifd[%d] into fds_array[%d]\n",clifd,i);
                   fds_array[i] = clifd;   //将新接收的客户端的文件描述符放入文件描述符数组中;
                   found = 1;
                   break;
               }
           }

           if(!found)
           {
               printf("Put new client into array failure:full\n");
               close (clifd);
           }
        }

        else   //已连接的客户端发来数可读;
        {
            for(i = 1;i < ARRAY_SIZE(fds_array);i++)
            {
               if(fds_array[i] < 0 || !FD_ISSET(fds_array[i],&allset))   //往响应的客户端读写;
                   continue;
            
               rv = read(fds_array[i],buf,sizeof(buf));
               if(rv <= 0)
               {
                   printf("Read from client[%d] failure:%s\n",fds_array[i],strerror(errno));
                   close(fds_array[i]);
                   fds_array[i] = -1;
               }  
               else
               {
                   printf("Read %d bytes data from client[%d] : %s\n",rv,fds_array[i],buf);
                   int j = 0;
                   for(j = 0;j < rv;j++)
                   {
                       buf[j] = toupper(buf[j]);
                   }

                   rv = write(fds_array[i],buf,rv);
                   if(rv <= 0)
                   {
                       printf("Write to client[%d] failure:%s\n",fds_array[i],strerror(errno));
                       close(fds_array[i]);
                       fds_array[i] = -1;
                   }
                }
               
            }
        }
    }

    return 0;
}

void print_usage(char *progname)
{
    printf("progname usage:\n");
    printf("-p(--port) for port you will bind\n");
    printf("-d(--deamon) the programe will run at background\n");
    printf("-h(--help) print help massage\n");

    return;
}

int socket_Be_ready(char *listen_ip,int serv_port)
{
    int                    listenfd;
    int                    on = 1;
    struct sockaddr_in     servaddr;
    socklen_t              addrlen = sizeof(servaddr);

    if((listenfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
    {
        perror("socket() failure");
        return -1;
    }
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(serv_port);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    if(bind(listenfd,(struct sockaddr *)&servaddr,addrlen) < 0)
    {
        printf("Bind failure:%s\n",strerror(errno));
        return -1;
    }

    listen(listenfd,13);

    return listenfd;

}

        
    

Select the server's shortcomings

1. Each call to select, from the set of fd need to copy the user mode to kernel mode, the overhead will be large when many fd;
2. SELECT fd and all are required to traverse the kernel passed in each call, the overhead also great when many fd;
number of file descriptors 3.select support is too small, the default is 1024. But compared to the single-process version of TCP, TCP and TCP multi-threaded version of the multi-process version, select the server more efficient than three;

Published 10 original articles · won praise 20 · views 2391

Guess you like

Origin blog.csdn.net/weixin_45121946/article/details/104774086