Heim >Betrieb und Instandhaltung >Betrieb und Wartung von Linux >Einführung in die Vorteile des Auswahlmechanismus

Einführung in die Vorteile des Auswahlmechanismus

零下一度
零下一度Original
2017-06-27 10:05:263406Durchsuche

Der Zweck des Select-Systemaufrufs besteht darin, lesbare, beschreibbare und Ausnahmeereignisse auf dem Dateideskriptor, an dem der Benutzer interessiert ist, innerhalb eines bestimmten Zeitraums zu überwachen.

Vorteile des Select-Mechanismus

Warum erscheint das Select-Modell?

Sehen Sie sich zunächst den folgenden Code an:

int iResult = recv(s, buffer,1024);

Dies wird zum Empfangen von Daten im Socket im Standardblockierungsmodus verwendet. Recv wird dort erst blockiert Es liegen Daten zum Lesen auf der Socket-Verbindung vor und die Daten werden in den Puffer gelesen, andernfalls bleiben sie dort blockiert. Wenn dies in einem Single-Thread-Programm passiert, wird der Haupt-Thread (es gibt nur einen Standard-Haupt-Thread in einem Single-Thread-Programm) blockiert, sodass das gesamte Programm hier gesperrt ist wird für immer gesperrt. Dieses Problem kann durch Multithreading gelöst werden, bei mehreren Socket-Verbindungen ist dies jedoch keine gute Option und weist eine schlechte Skalierbarkeit auf.

Sehen Sie sich den Code noch einmal an:

int iResult = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);
iResult = recv(s, buffer,1024);

Dieses Mal kehrt der Recv-Aufruf sofort zurück, unabhängig davon, ob auf der Socket-Verbindung Daten zu empfangen sind. Der Grund dafür ist, dass wir ioctlsocket verwenden, um den Socket in den nicht blockierenden Modus zu versetzen. Wenn Sie ihm jedoch folgen, werden Sie feststellen, dass recv zwar sofort zurückkehrt, wenn keine Daten vorhanden sind, aber auch einen Fehler zurückgibt: WSAEWOULDBLOCK, was bedeutet, dass der angeforderte Vorgang nicht erfolgreich abgeschlossen wurde.

Nachdem sie das gesehen haben, sagen viele Leute vielleicht: Rufen Sie recv wiederholt auf und überprüfen Sie den Rückgabewert, bis es erfolgreich ist, aber die Effizienz ist sehr problematisch und der Aufwand ist zu groß.

Die Entstehung des ausgewählten Modells dient der Lösung der oben genannten Probleme.
Der Schlüssel zum Select-Modell besteht darin, mehrere Sockets auf geordnete Weise einheitlich zu verwalten und zu planen.

Einführung in die Vorteile des Auswahlmechanismus

Wie oben gezeigt, fügt der Benutzer zuerst den Socket, der E/A-Vorgänge erfordert, zur Auswahl hinzu, blockiert dann und wartet auf die Rückkehr des ausgewählten Systemaufrufs. Wenn Daten eintreffen, wird der Socket aktiviert und die Auswahlfunktion kehrt zurück. Der Benutzerthread initiiert offiziell eine Leseanforderung, liest die Daten und setzt die Ausführung fort.

Aus Prozesssicht gibt es keinen großen Unterschied zwischen der Verwendung der Select-Funktion für E/A-Anfragen und dem synchronen Blockierungsmodell. Es gibt sogar zusätzliche Vorgänge zum Hinzufügen eines Überwachungs-Sockets und zum Aufrufen der Select-Funktion, was die Effizienz erhöht noch schlimmer. Der größte Vorteil der Verwendung von select besteht jedoch darin, dass Benutzer mehrere Socket-IO-Anfragen gleichzeitig in einem Thread verarbeiten können. Benutzer können mehrere Sockets registrieren und dann kontinuierlich select aufrufen, um die aktivierten Sockets zu lesen und so den Zweck zu erreichen, mehrere E/A-Anfragen gleichzeitig im selben Thread zu verarbeiten. Im synchronen Blockierungsmodell muss dieser Zweck durch Multithreading erreicht werden.

Der Pseudocode des Auswahlprozesses lautet wie folgt:

{
    select(socket);
    while(1) 
    {
        sockets = select();
        for(socket in sockets) 
        {
            if(can_read(socket)) 
            {
                read(socket, buffer);
                process(buffer);
            }
        }
    }
}

Einführung und Verwendung ausgewählter verwandter APIs

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);

Parameterbeschreibung:

maxfdp: überwacht Die Gesamtzahl der Dateideskriptoren, die um 1 größer ist als der Maximalwert der Dateideskriptoren in allen Dateideskriptorsätzen, da Dateideskriptoren ab 0 gezählt werden; Die verfügbare Sammlung von Deskriptoren, die Ereignissen wie „Lesen“, „Schreibbar“ und „Ausnahme“ entsprechen.

Timeout: Wird verwendet, um das Timeout der Auswahlfunktion festzulegen, dh um dem Kernel mitzuteilen, wie lange er warten soll, bevor er aufgibt. timeout == NULL bedeutet unendlich langes Warten

Die Zeitwertstruktur ist wie folgt definiert:

Rückgabewert: 0 wird bei Zeitüberschreitung zurückgegeben; -1 wird bei einem Fehler zurückgegeben Bei Erfolg wird eine Ganzzahl größer als 0 zurückgegeben. Diese Ganzzahl stellt die Anzahl der bereiten Deskriptoren dar.
struct timeval
{      
    long tv_sec;   /*秒 */
    long tv_usec;  /*微秒 */   
};

Im Folgenden werden mehrere gängige Makros im Zusammenhang mit der Select-Funktion vorgestellt:

Beispiel für die Verwendung von Select:
#include <sys/select.h>   
int FD_ZERO(int fd, fd_set *fdset);   //一个 fd_set类型变量的所有位都设为 0
int FD_CLR(int fd, fd_set *fdset);  //清除某个位时可以使用
int FD_SET(int fd, fd_set *fd_set);   //设置变量的某个位置位
int FD_ISSET(int fd, fd_set *fdset); //测试某个位是否被置位
Wenn ein Dateideskriptorsatz deklariert wird, müssen alle mit FD_ZERO gelöscht werden Position Null. Setzen Sie dann das Bit, das dem Deskriptor entspricht, an dem wir interessiert sind. Die Operation ist wie folgt:


Rufen Sie dann die Auswahlfunktion auf und warten Sie auf das Eintreffen des Dateideskriptorereignisses im Überlastungsfall überschritten wird, nicht mehr. Warten und mit der Ausführung fortfahren.
fd_set rset;   
int fd;   
FD_ZERO(&rset);   
FD_SET(fd, &rset);   
FD_SET(stdin, &rset);

Nachdem „select“ zurückgegeben wurde, verwenden Sie FD_ISSET, um zu testen, ob das angegebene Bit gesetzt ist:
select(fd+1, &rset, NULL, NULL,NULL);

Das Folgende ist das einfachste Beispiel für die Verwendung von „select“:
if(FD_ISSET(fd, &rset)   
{ 
    ... 
    //do something  
}

Wir führen das Programm aus und geben einige Daten ein, und das Programm meldet, dass die Daten empfangen wurden.
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main()
{
    fd_set rd;
    struct timeval tv;
    int err;
    

    FD_ZERO(&rd);
    FD_SET(0,&rd);
    
    tv.tv_sec = 5;
    tv.tv_usec = 0;
    err = select(1,&rd,NULL,NULL,&tv);
    
    if(err == 0) //超时
    {
        printf("select time out!\n");
    }
    else if(err == -1)  //失败
    {
        printf("fail to select!\n");
    }
    else  //成功
    {
        printf("data is available!\n");
    }

    
    return 0;
}


Einführung in die Vorteile des AuswahlmechanismusDetailliertes Verständnis des ausgewählten Modells:

Der Schlüssel zum Verständnis des ausgewählten Modells besteht darin, fd_set zu verstehen. Der Einfachheit halber beträgt die Länge von fd_set 1 Byte und jedes Bit in fd_set Kann einem Dateideskriptor fd entsprechen. Dann kann ein 1 Byte langes fd_set maximal 8 fds entsprechen.

(1) Führen Sie fd_set set aus; FD_ZERO(&set); dann ist die Bitdarstellung von set 0000.0000.

(2) Wenn fd=5, führen Sie FD_SET(fd,&set) aus und set wird zu 0001,0000 (die fünfte Position ist 1)

(3) Wenn fd= erneut hinzugefügt wird 2. fd=1, dann wird set zu 0001,0011

(4) Select(6,&set,0,0,0) ausführen und warten auf

blockieren (5) Wenn fd= 1 , lesbare Ereignisse treten auf beiden fd=2 auf, wählen Sie dann Returns aus und setzen Sie die Änderungen auf 0000,0011. Hinweis: fd=5 ohne Ereignisse wird gelöscht.

Basierend auf der obigen Diskussion können die Eigenschaften des ausgewählten Modells leicht abgeleitet werden:

(1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。据说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。

(2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。

(3)可见select模型必须在select前循环加fd,取maxfd,select返回后利用FD_ISSET判断是否有事件发生。

用select处理带外数据

网络程序中,select能处理的异常情况只有一种:socket上接收到带外数据。

什么是带外数据?

带外数据(out—of—band data),有时也称为加速数据(expedited data),
是指连接双方中的一方发生重要事情,想要迅速地通知对方。
这种通知在已经排队等待发送的任何“普通”(有时称为“带内”)数据之前发送。
带外数据设计为比普通数据有更高的优先级。
带外数据是映射到现有的连接中的,而不是在客户机和服务器间再用一个连接。

我们写的select程序经常都是用于接收普通数据的,当我们的服务器需要同时接收普通数据和带外数据,我们如何使用select进行处理二者呢?

下面给出一个小demo:

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>


int main(int argc, char* argv[])
{
    if(argc <= 2)
    {
        printf("usage: ip address + port numbers\n");
        return -1;
    }
    
    const char* ip = argv[1];
    int port = atoi(argv[2]);

        printf("ip: %s\n",ip);
        printf("port: %d\n",port);
    
    int ret = 0;
    struct sockaddr_in address;
    bzero(&address,sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET,ip,&address.sin_addr);
    address.sin_port = htons(port);
    
    int listenfd = socket(PF_INET,SOCK_STREAM,0);
    if(listenfd < 0)
    {
        printf("Fail to create listen socket!\n");
        return -1;
    }
    
    ret = bind(listenfd,(struct sockaddr*)&address,sizeof(address));
    if(ret == -1)
    {
        printf("Fail to bind socket!\n");
        return -1;
    }
    
    ret = listen(listenfd,5); //监听队列最大排队数设置为5
    if(ret == -1)
    {
        printf("Fail to listen socket!\n");
        return -1;
    }
    
    struct sockaddr_in client_address;  //记录进行连接的客户端的地址
    socklen_t client_addrlength = sizeof(client_address);
    int connfd = accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength);
    if(connfd < 0)
    {
        printf("Fail to accept!\n");
        close(listenfd);
    }
    
    char buff[1024]; //数据接收缓冲区
    fd_set read_fds;  //读文件操作符
    fd_set exception_fds; //异常文件操作符
    FD_ZERO(&read_fds);
    FD_ZERO(&exception_fds);
    
    while(1)
    {
        memset(buff,0,sizeof(buff));
        /*每次调用select之前都要重新在read_fds和exception_fds中设置文件描述符connfd,因为事件发生以后,文件描述符集合将被内核修改*/
        FD_SET(connfd,&read_fds);
        FD_SET(connfd,&exception_fds);
        
        ret = select(connfd+1,&read_fds,NULL,&exception_fds,NULL);
        if(ret < 0)
        {
            printf("Fail to select!\n");
            return -1;
        }
        
        
        if(FD_ISSET(connfd, &read_fds))
        {
            ret = recv(connfd,buff,sizeof(buff)-1,0);
            if(ret <= 0)
            {
                break;
            }
            
            printf("get %d bytes of normal data: %s \n",ret,buff);
            
        }
        else if(FD_ISSET(connfd,&exception_fds)) //异常事件
        {
            ret = recv(connfd,buff,sizeof(buff)-1,MSG_OOB);
            if(ret <= 0)
            {
                break;
            }
            
            printf("get %d bytes of exception data: %s \n",ret,buff);
        }
        
    }
    
    close(connfd);
    close(listenfd);
    
    
    return 0;
}

用select来解决socket中的多客户问题

上面提到过,,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。在网络编程中,当涉及到多客户访问服务器的情况,我们首先想到的办法就是fork出多个进程来处理每个客户连接。现在,我们同样可以使用select来处理多客户问题,而不用fork。

服务器端

#include <sys/types.h> 
#include <sys/socket.h> 
#include <stdio.h> 
#include <netinet/in.h> 
#include <sys/time.h> 
#include <sys/ioctl.h> 
#include <unistd.h> 
#include <stdlib.h>

int main() 
{ 
    int server_sockfd, client_sockfd; 
    int server_len, client_len; 
    struct sockaddr_in server_address; 
    struct sockaddr_in client_address; 
    int result; 
    fd_set readfds, testfds; 
    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立服务器端socket 
    server_address.sin_family = AF_INET; 
    server_address.sin_addr.s_addr = htonl(INADDR_ANY); 
    server_address.sin_port = htons(8888); 
    server_len = sizeof(server_address); 
    bind(server_sockfd, (struct sockaddr *)&server_address, server_len); 
    listen(server_sockfd, 5); //监听队列最多容纳5个 
    FD_ZERO(&readfds); 
    FD_SET(server_sockfd, &readfds);//将服务器端socket加入到集合中
    while(1) 
    {
        char ch; 
        int fd; 
        int nread; 
        testfds = readfds;//将需要监视的描述符集copy到select查询队列中,select会对其修改,所以一定要分开使用变量 
        printf("server waiting\n"); 

        /*无限期阻塞,并测试文件描述符变动 */
        result = select(FD_SETSIZE, &testfds, (fd_set *)0,(fd_set *)0, (struct timeval *) 0); //FD_SETSIZE:系统默认的最大文件描述符
        if(result < 1) 
        { 
            perror("server5"); 
            exit(1); 
        } 

        /*扫描所有的文件描述符*/
        for(fd = 0; fd < FD_SETSIZE; fd++) 
        {
            /*找到相关文件描述符*/
            if(FD_ISSET(fd,&testfds)) 
            { 
              /*判断是否为服务器套接字,是则表示为客户请求连接。*/
                if(fd == server_sockfd) 
                { 
                    client_len = sizeof(client_address); 
                    client_sockfd = accept(server_sockfd, 
                    (struct sockaddr *)&client_address, &client_len); 
                    FD_SET(client_sockfd, &readfds);//将客户端socket加入到集合中
                    printf("adding client on fd %d\n", client_sockfd); 
                } 
                /*客户端socket中有数据请求时*/
                else 
                { 
                    ioctl(fd, FIONREAD, &nread);//取得数据量交给nread
                    
                    /*客户数据请求完毕,关闭套接字,从集合中清除相应描述符 */
                    if(nread == 0) 
                    { 
                        close(fd); 
                        FD_CLR(fd, &readfds); //去掉关闭的fd
                        printf("removing client on fd %d\n", fd); 
                    } 
                    /*处理客户数据请求*/
                    else 
                    { 
                        read(fd, &ch, 1); 
                        sleep(5); 
                        printf("serving client on fd %d\n", fd); 
                        ch++; 
                        write(fd, &ch, 1); 
                    } 
                } 
            } 
        } 
    } 

    return 0;
}

客户端

//客户端
#include <sys/types.h> 
#include <sys/socket.h> 
#include <stdio.h> 
#include <netinet/in.h> 
#include <arpa/inet.h> 
#include <unistd.h> 
#include <stdlib.h>
#include <sys/time.h>

int main() 
{ 
    int client_sockfd; 
    int len; 
    struct sockaddr_in address;//服务器端网络地址结构体 
     int result; 
    char ch = 'A'; 
    client_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立客户端socket 
    address.sin_family = AF_INET; 
    address.sin_addr.s_addr = inet_addr("127.0.0.1");
    address.sin_port = htons(8888); 
    len = sizeof(address); 
    result = connect(client_sockfd, (struct sockaddr *)&address, len); 
    if(result == -1) 
    { 
         perror("oops: client2"); 
         exit(1); 
    } 
    //第一次读写
    write(client_sockfd, &ch, 1); 
    read(client_sockfd, &ch, 1); 
    printf("the first time: char from server = %c\n", ch); 
    sleep(5);
    
    //第二次读写
    write(client_sockfd, &ch, 1); 
    read(client_sockfd, &ch, 1); 
    printf("the second time: char from server = %c\n", ch);
    
    close(client_sockfd); 
   
    return 0; 
}

运行流程:

客户端:启动->连接服务器->发送A->等待服务器回复->收到B->再发B给服务器->收到C->结束

服务器:启动->select->收到A->发A+1回去->收到B->发B+1过去

测试:我们先运行服务器,再运行客户端
Einführung in die Vorteile des Auswahlmechanismus

select总结:

select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是:

1、单个进程可监视的fd数量被限制,即能监听端口的大小有限。一般来说这个数目和系统内存关系很大,具体数目可以cat/proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.

2、 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低:当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。

3、需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。

Das obige ist der detaillierte Inhalt vonEinführung in die Vorteile des Auswahlmechanismus. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn