linux socket 编程之 —— select函数详解

来源:互联网 发布:java intellij i18n 编辑:程序博客网 时间:2024/06/01 18:23

函数作用:

系统提供select函数来实现多路复用输入/输出模型。select系统调用是用来让我们的程序监视多个文件句柄的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变。关于文件句柄,其实就是一个整数,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE *结构的表示就是stdin、stdout、stderr。

    int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
函数参数列表:

1intmaxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错。

2fd_set*readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以 从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读;如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

3fd_set*writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可 以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。

4fd_set*errorfds同上面两个参数的意图,用来监视文件错误异常文件。

5structtimeval* timeoutselect的超时时间,这个参数至关重要,它可以使select处于三种状态,

第一,若将NULL以形参传入,即不传入时间结构,就将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;

第二,若将时间值设为00毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;

第三,timeout的值大于0,这就是等待的超时时间,即 selecttimeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

参数timeout为结构timeval,用来设置select()的等待时间,其结构定义如下:
<span style="white-space:pre"></span>struct timeval  <span style="white-space:pre"></span>{      <span style="white-space:pre"></span>time_t tv_sec;//second      <span style="white-space:pre"></span>time_t tv_usec;//minisecond  <span style="white-space:pre"></span>};
函数返回值
执行成功则返回文件描述词状态已改变的个数,如果返回0代表在描述词状态改变前已超过timeout时间,没有返回;当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。错误值可能为:
EBADF 文件描述词为无效的或该文件已关闭
EINTR 此调用被信号所中断
EINVAL 参数n 为负值。
ENOMEM 核心内存不足
头文件:
select 位于:#include <sys/select.h>
struct timeval 位于: #include <sys/time.h>
以下的宏提供了处理这三种描述词组的方式:
FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set);用来清除描述词组set的全部位
select模型的特点:

  (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前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。

基本原理
常用代码块:
<span style="font-size:14px;">fd_set  rdfds;struct timeval tv;int ret;FD_ZERO(&rdfds);FD_SET(socket, &rdfds);tv.tv_sec = 1;tv.tv_uses = 500;ret = select (socket + 1, %rdfds, NULL, NULL, &tv);if(ret < 0) perror (“select”);else if (ret = = 0) printf(“time out”);else {       printf(“ret = %d/n”,ret);       if(FD_ISSET(socket, &rdfds)){    /* 读取socket句柄里的数据 */recv( );}}</span>


Linux socket编程下 select函数的应用:
    linux 的socket函数分为阻塞和非阻塞两种方式,比如accept函数,在阻塞模式下,它会一直等待有客户连接。而在非阻塞情况下,会立刻返回。我们一般都希望程序能够运行在非阻塞模式下。一种方法就是做一个死循环,不断去查询各个socket的状态,但是这样会浪费大量的cpu时间。解决这个问题的一个方法就是使用select函数。使用select函数可以以非阻塞的方式和多个socket通信。当有socket需要处理时,select函数立刻返回,期间并不会占用cpu时间。
例程分析
<span style="font-size:14px;">#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <errno.h>#include <string.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#define MYPORT 1234    // 侦听端口#define BACKLOG 5     // 最大可连接客户端数量#define BUF_SIZE 200int fd_A[BACKLOG];    // 连接的FD数组int conn_amount;    // 当前连接的数量void showclient(){    int i;    printf("client amount: %d\n", conn_amount);    for (i = 0; i < BACKLOG; i++)    {        printf("[%d]:%d  ", i, fd_A[i]);    }    printf("\n\n");}int main(void){    int sock_fd, new_fd;               // 侦听sock_fd, 新连接new_fd    struct sockaddr_in server_addr;    // server address information    struct sockaddr_in client_addr;    // connector's address information    socklen_t sin_size;    int yes = 1;    char buf[BUF_SIZE];    int ret;    int i;    //创建侦听Socket    if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)    {        perror("Create listening socket error!");        exit(1);    }    //配置侦听Socket    //SO_REUSEADDR BOOL 允许套接口和一个已在使用中的地址捆绑。    if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)    {        perror("setsockopt error!");        exit(1);    }       server_addr.sin_family = AF_INET;         // host byte order    server_addr.sin_port = htons(MYPORT);     // short, network byte order    server_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP    memset(server_addr.sin_zero, '\0', sizeof(server_addr.sin_zero));    //绑定新创建的Socket到指定的IP和端口    if (bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)    {        perror("bind error!");        exit(1);    }    //开始侦听,最大连接数为BACKLOG    if (listen(sock_fd, BACKLOG) == -1)    {        perror("listen error!");        exit(1);    }    printf("listen port %d\n", MYPORT);       //监控文件描述符集合    fd_set fdsr;    //监控文件描述符集合中最大的文件号    int maxsock;    //Select超时返回的时间。    struct tim tv;    conn_amount = 0;    sin_size = sizeof(client_addr);    maxsock = sock_fd;    while (1)    {        // 初始化文件描述符集合 initialize file descriptor set        FD_ZERO(&fdsr);        // 把Sock_fd加入到文件描述符集合        FD_SET(sock_fd, &fdsr);        // 超时设置30秒        tv.tv_sec = 30;        tv.tv_usec = 0;        // 把活动的socket的句柄加入到文件描述符集合中        for (i = 0; i < BACKLOG; i++)        {            if (fd_A[i] != 0)            {                FD_SET(fd_A[i], &fdsr);            }        }        //Select 函数原型        //int select(nfds, readfds, writefds, exceptfds, timeout)        //nfds: select监视的文件句柄数,视进程中打开的文件数而定,一般设为呢要监视各文件中的        //最大文件号加一        //readfds:select监视的可读文件句柄集合        //writefds:select监视的可写文件句柄集合。        //exceptfds:select监视的异常文件句柄集合。        //timeout:本次select的超时结束时间。        ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv);        if (ret < 0)        {            perror("select error!");            break;        }        else if (ret == 0)        {            printf("timeout\n");            continue;        }        // 轮询各个文件描述符(socket)        for (i = 0; i < conn_amount; i++)        {             //FD_ISSET(int fd, fdset *fdset):检查fdset联系的文件句柄fd是否可读写,             // >0表示可读写。            if (FD_ISSET(fd_A[i], &fdsr))            {                //接收数据                ret = recv(fd_A[i], buf, sizeof(buf), 0);                if (ret <= 0) //接收数据出错                {                           printf("client[%d] close\n", i);                    close(fd_A[i]);                    FD_CLR(fd_A[i], &fdsr);                    fd_A[i] = 0;                }                else  // 数据接收成功                {                      //将接收数据的最后一位补0                    if (ret < BUF_SIZE)                         memset(&buf[ret], '\0', 1);                    printf("client[%d] send:%s\n", i, buf);                 }            }        }        // 检查是否有新连接进来,如果有新连接进来,接收连接,生成新socket,        //并加入到监控文件描述符集合中。        if (FD_ISSET(sock_fd, &fdsr))        {            //接受连接            new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &sin_size);            if (new_fd <= 0)            {                perror("accept socket error!");                continue;            }            // 将新的连接加入到监控文件描述符集合            if (conn_amount < BACKLOG)            {                fd_A[conn_amount++] = new_fd;                printf("new connection client[%d] %s:%d\n", conn_amount,inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));                if (new_fd > maxsock)                    maxsock = new_fd;            }            else            {                printf("max connections arrive, exit\n");                send(new_fd, "bye", 4, 0);                close(new_fd);                break;            }        }        showclient();    }    // 关闭所有连接    for (i = 0; i < BACKLOG; i++)    {        if (fd_A[i] != 0)        {            close(fd_A[i]);        }    }    exit(0);}</span>



0 0
原创粉丝点击