网络编程 select模型

select()机制中提供一fd_set的数据结构实际上是一long类型的数组,每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fe_set的内容,由此来通知执行了select()的进程哪一socket或文件可读

    多端口复用函数select在调用前要首先设置监听的端口数目,FD_ZERO是清空端口集,FD_SET是设置端口集。

    select()函数常常用在用一个进程监听多个服务器端socket

    有时,select()也被当作延时函数使用。sleep()延时会释放CPU,select()的话,可以在占用CPU的情况下延时。

    select()函数主要是建立在fd_set类型的基础上的。fd_set(它比较重要所以先介绍一下)是一组文件描述字(fd)的集合,它用一位来表示一个fd(下面会仔细介绍),对于fd_set类型通过下面四个来操作:

    fd_set  set;

    FD_ZERO(&set);        /*将set清零使集合中不含任何fd*/

    FD_SET(fd, &set);      /*将fd加入set集合*/

    FD_CLR(fd, &set);      /*将fd从set集合中清除*/

    FD_ISSET(fd, &set);   /*测试fd是否在set集合中*/

    过去,一个fd_set通常只能包含<32的fd(文件描述字),因为fd_set其实只用了一个32位矢量来表示fd; 现在,UNIX系统通常会在头文件<sys/select.h>中定义常量FD_SETSIZE,它是数据类型fd_set的描述字数量,其值通常是1024,这样就能表示<1024的fd。根据fd_set的位矢量实现,我们可以重新理解操作fd_set的四个宏:

    fd_set  set;

    FD_ZERO(&set);        /*将set的所有位置0,如set在内存中占8位则将set置为00000000*/

    FD_SET(0, &set);       /*将set的第0位置1,如set原来是00000000,则现在变为100000000,这样fd==1的文件描述字就被加进set中了*/

    FD_CLR(4, &set);       /*将set的第4位置0,如set原来是10001000,则现在变为10000000,这样fd==4的文件描述字就被从set中清除了*/

    FD_ISSET(5, &set);     /*测试set的第5位是否为1,如果原来set是10000100,则返回非零,表明fd==5的文件描述符在set中,否则返回0*/

    注意:fd的最大值必须<FD_SETSIZE。

    select函数的接口比较简单:

    int select(int nfds,  fd_set* readset,  fd_set* writeset,  fe_set* exceptset,  struct timeval* timeout);

    功能:

    测试指定的fd可读?可写?有异常条件待处理?

    参数:

    nfds: 需要检查的文件描述字个数(即检查到fd_set的第几位),数值应该比三组fd_set中所含的最大fd值更大,一般设为三组fd_set中所含的最大fd值加1(如在readset, writeset, exceptset中所含最大的fd为5,则nfds=6,因为fd是从0开始的 )。设这个值是为了提高效率,使函数不必检查fd_set的所有1024位。

    readset: 用来检查可读性的一组文件描述字。

    writeset: 用来检查可写性的一组文件描述字。

    exceptset: 用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内)

    timeout: 有三种可能:

    1.  timeout = NULL (阻塞:直到有一个fd位被置为1函数才返回)

    2.  timeout所指向的结构设为非零时间(等待固定时间:有一个fd位被置为1或者时间耗尽,函数均返回)

    3.  timeout所指向的结构,时间设为0(非阻塞:函数检查完每一个fd后立即返回)

    返回值:返回对应位仍然为1的fd的总数。

    Remark:

    三组fd_set均将某些fd位置0,只有那些可读,可写以及有异常条件待处理的fd位仍然为1。

    使用select函数的过程一般是:

    先调用宏FD_ZERO将指定的fd_set清零,然后调用宏FD_SET将需要测试的fd加入fd_set,接着调用函数select测试fd_set中的所有fd,最后用宏FD_ISSET检查某个fd在函数select调用后,相应位是否仍然为1

    以下是一个测试单个文件描述字可读性的例子:

    int  isready(int  fd)

    {

        int    rc;

        fd_set    fds;

        struct timeval    tv;

        FD_ZERO(&fds);

        FD_SET(fd,  &fds);

        tv.tv_sec = tv.tv_usec = 0;

        rc = select(fd+1, &fds, NULL, NULL, &tv);

        if( rc<0 )  //error

          return -1;

        return FD_ISSET(fd, &fds)  ? 1: 0;

    }

    下面还有一个复杂一些的应用:

    //这段代码将指定测试Socket的描述字的可读可写性,因为Socket使用的也是fd

    unit32  SocketWait(TSocket* s,  bool rd,  bool wr,  unit32 timems)

    {

        fd_set  rfds, wfds;

     #ifdef _WIN32

        TIMEVAL tv;

    #else

        struct timeval   tv;

    #endif    /*_WIN32*/

        FD_ZERO(&rfds);

        FD_ZERO(&wfds);

        if(rd)    //TRUE

          FD_SET(*s, &rfds);  //添加要测试的描述字

        if(wr)

          FD_SET(*s, &wfds);

        tv.tv_sec = timems/1000;  //seconds

        tv.tv_usec = timems%1000;  //ms

        for(;;)  //如果errno==EINTR,反复测试缓冲区的可读性

          switch(select((*s)+1, &fds, &wfds, NULL, (timems==TIME_INFINITE?NULL:&tv)))  //测试在规定的时间内套接字接口接收缓冲区是否有数据可读

          {

               // 0——超时,  -1——出错

               case 0:  /*time out*/

                   return 0;

               case (-1):    /*socket error*/

                  if( SocketError()==EINTR )

                     break;

                  return 0;  //有错但不是EINTR

               default:

                  if(FD_ISSET(*s, &rfds))  //如果s是fds中的一员返回非0,否则返回0

                     return 1;

                  if(FD_ISSET(*s, &wfds))

                     rerun  2;

                  return 0;

          };

    }


转至:http://blog.youkuaiyun.com/bailyzheng/article/details/7477446

在C语言网络编程里,select模型是实现IO多路复用的方式之一,可实现多客户端连接通信。 select函数是该模型的核心,在Linux下,它也是一个系统函数,其原型如下: ```c #include <sys/select.h> int select(int ndfs, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout); ``` 该函数的工作流程为:把已连接的socket都放到一个句柄集合(fd_set)中,接着调用select函数将句柄集合拷贝到内核,由内核检查是否有网络事件产生,此检查通过遍历集合的方式进行。当检查到有事件产生后,将对应的socket标记为可读或可写,随后把整个句柄集合拷贝回用户态,用户态还需再次遍历集合,找到就绪的句柄并做后续处理 [^1]。 使用select模型时,select会遍历socket数组,取出有响应的socket,若没有则持续遍历,虽然select()函数的执行是阻塞的,但每次都只处理有响应的socket,所以能实现多客户端连接通信 [^2]。 以下是一个简单的使用示例步骤及代码框架: 1. 初始化socket,绑定地址,监听连接等基本操作。 2. 创建fd_set集合,将监听socket加入集合。 3. 调用select函数进行监听。 4. 遍历集合,处理就绪的socket。 示例代码框架如下: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/select.h> #include <unistd.h> #define PORT 8888 #define MAX_CLIENTS 10 int main() { int server_fd, new_socket; struct sockaddr_in address; int opt = 1; int addrlen = sizeof(address); char buffer[1024] = {0}; fd_set readfds; int max_sd; // 创建socket if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } // 设置socket选项 if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) { perror("setsockopt"); exit(EXIT_FAILURE); } address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); // 绑定地址 if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { perror("bind failed"); exit(EXIT_FAILURE); } // 监听连接 if (listen(server_fd, 3) < 0) { perror("listen"); exit(EXIT_FAILURE); } while (1) { FD_ZERO(&readfds); FD_SET(server_fd, &readfds); max_sd = server_fd; // 这里可以添加其他客户端socket到readfds集合 // 调用select函数 int activity = select(max_sd + 1, &readfds, NULL, NULL, NULL); if ((activity < 0) && (errno != EINTR)) { perror("select error"); } // 处理监听socket上的新连接 if (FD_ISSET(server_fd, &readfds)) { if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) { perror("accept"); exit(EXIT_FAILURE); } // 可以将新的客户端socket加入集合等操作 } // 遍历其他socket,处理读写事件 // ... } return 0; } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值