>  기사  >  운영 및 유지보수  >  셀렉트 메커니즘의 장점 소개

셀렉트 메커니즘의 장점 소개

零下一度
零下一度원래의
2017-06-27 10:05:263355검색

select 시스템 호출의 목적은 지정된 기간 내에 사용자가 관심을 갖는 파일 설명자에 대한 읽기 가능, 쓰기 가능 및 예외 이벤트를 모니터링하는 것입니다.

셀렉트 메커니즘의 장점

셀렉트 모델이 나타나는 이유는 무엇인가요?

먼저 다음 코드를 살펴보세요:

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

이것은 데이터를 수신하는 데 사용됩니다. 기본 차단 모드의 소켓에서 recv는 소켓 연결에서 읽을 수 있는 데이터가 있을 때까지 차단합니다. 데이터가 버퍼로 읽어질 때까지 반환됩니다. 그렇지 않으면 버퍼에서 항상 차단됩니다. 단일 스레드 프로그램에서 이런 일이 발생하면 메인 스레드(단일 스레드 프로그램에는 기본 메인 스레드가 하나만 있음)가 차단되므로 데이터가 전송되지 않으면 프로그램 전체가 여기에서 잠깁니다. 영원히 차단됩니다. 이 문제는 멀티스레딩으로 해결할 수 있지만, 멀티 소켓 연결의 경우 이는 좋은 옵션이 아니며 확장성이 좋지 않습니다.

코드를 다시 보세요:

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

이번에는 소켓 연결에 수신할 데이터가 있는지 여부에 관계없이 Recv 호출이 즉시 반환됩니다. 그 이유는 소켓을 비차단 모드로 설정하기 위해 ioctlsocket을 사용하기 때문입니다. 그러나 이를 따라가면 데이터가 없을 때 recv가 즉시 반환되지만 요청한 작업이 성공적으로 완료되지 않았음을 의미하는 WSAEWOULDBLOCK 오류도 반환한다는 것을 알 수 있습니다.

이 내용을 보고 많은 사람들은 성공할 때까지 그냥 recv를 반복적으로 호출하고 반환 값을 확인하면 된다고 말할 수 있지만, 이 방법의 효율성은 매우 문제가 되고 오버헤드가 너무 많습니다.

셀렉트 모델의 등장은 위의 문제점을 해결하기 위함입니다.
셀렉트 모델의 핵심은 질서정연한 방법을 사용하여 여러 소켓을 균일하게 관리하고 예약하는 것입니다.

셀렉트 메커니즘의 장점 소개

위와 같이 사용자는 먼저 IO 작업이 필요한 소켓을 select에 추가한 다음 select 시스템 호출이 반환될 때까지 차단하고 기다립니다. 데이터가 도착하면 소켓이 활성화되고 선택 기능이 반환됩니다. 사용자 스레드는 공식적으로 읽기 요청을 시작하고 데이터를 읽고 실행을 계속합니다.

프로세스 관점에서 볼 때 IO 요청에 선택 기능을 사용하는 것과 동기식 차단 모델을 사용하는 것에는 큰 차이가 없습니다. 모니터링 소켓을 추가하고 선택 기능을 호출하는 추가 작업도 있어 효율성이 더욱 저하됩니다. 그러나 select를 사용하는 가장 큰 장점은 사용자가 하나의 스레드에서 동시에 여러 소켓 IO 요청을 처리할 수 있다는 것입니다. 사용자는 여러 소켓을 등록한 다음 계속해서 select를 호출하여 활성화된 소켓을 읽어 동일한 스레드에서 여러 IO 요청을 동시에 처리하려는 목적을 달성할 수 있습니다. 동기식 차단 모델에서는 멀티스레딩을 통해 이 목적을 달성해야 합니다.

선택 프로세스 의사 코드는 다음과 같습니다.

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

관련 API 소개를 선택하여 사용합니다.

#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);

매개변수 설명:

maxfdp: 모니터링되는 총 파일 설명자 수로, 전체 파일의 최대 파일 설명자 수보다 큽니다. 설명자 세트 파일 설명자는 0부터 계산되므로 값이 1 더 큽니다.

readfds, writefds, Exceptset: 각각 읽기 가능, 쓰기 가능, 예외 및 기타 이벤트에 해당하는 설명자 세트를 가리킵니다.

timeout: 선택 기능의 시간 초과를 설정하는 데 사용됩니다. 즉, 포기하기 전에 기다려야 하는 시간을 커널에 알려줍니다. timeout == NULL은 무한한 시간을 기다리는 것을 의미합니다.

timeval 구조는 다음과 같이 정의됩니다.

struct timeval
{      
    long tv_sec;   /*秒 */
    long tv_usec;  /*微秒 */   
};

반환 값: 타임아웃 시 0이 반환되고, 성공 시 0보다 큰 정수가 반환됩니다. 정수는 준비된 설명자의 수를 나타냅니다.

다음은 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); //测试某个位是否被置位

Select 사용 예:
파일 설명자 세트가 선언되면 모든 위치는 FD_ZERO를 사용하여 0으로 설정되어야 합니다. 그런 다음 관심 있는 설명자에 해당하는 비트를 설정합니다. 작업은 다음과 같습니다.

fd_set rset;   
int fd;   
FD_ZERO(&rset);   
FD_SET(fd, &rset);   
FD_SET(stdin, &rset);

그런 다음 선택 함수를 호출하고 혼잡 시 파일 설명자 이벤트가 도착할 때까지 기다립니다. 설정된 시간이 초과되면 더 이상 기다리지 않습니다. 실행을 계속합니다.

select(fd+1, &rset, NULL, NULL,NULL);

select가 반환된 후 FD_ISSET을 사용하여 해당 비트가 설정되었는지 테스트합니다.

if(FD_ISSET(fd, &rset)   
{ 
    ... 
    //do something  
}

다음은 select를 사용하는 가장 간단한 예입니다.

#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;
}

프로그램을 실행하고 일부 데이터를 자연스럽게 입력하면 프로그램에서 다음을 묻는 메시지를 표시합니다. 데이터가 수신되었습니다.
셀렉트 메커니즘의 장점 소개

선택 모델에 대한 심층적인 이해:

선택 모델을 이해하는 열쇠는 fd_set을 이해하는 것입니다. 설명의 편의를 위해 fd_set의 각 비트는 파일에 해당할 수 있습니다. 설명자 fd. 그러면 1바이트 길이의 fd_set은 최대 8개의 fd에 해당할 수 있습니다.

(1) fd_set set을 실행하면 FD_ZERO(&set); 그러면 set의 비트 표현은 0000,0000입니다.

(2) fd=5이면 FD_SET(fd,&set)을 실행하고 set은 0001,0000이 됩니다(5번째 위치는 1입니다)

(3) fd=2와 fd=1을 더하면 set은 0001이 됩니다. ,0011

(4) select(6,&set,0,0,0)를 실행하여 차단하고 대기합니다

(5) fd=1과 fd=2 모두에서 읽을 수 있는 이벤트가 발생하면 select가 반환됩니다. 0000,0011. 참고: 이벤트가 없는 fd=5는 지워집니다.

위 논의를 바탕으로 선택 모델의 특징을 쉽게 그릴 수 있습니다.

(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过去

测试:我们先运行服务器,再运行客户端
셀렉트 메커니즘의 장점 소개

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的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。

위 내용은 셀렉트 메커니즘의 장점 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.