1、select函数原型

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);

参数解释:

 maxfdp——传入参数,集合中所有文件描述符的范围,即最大文件描述符值+1readfds——传入传出参数,select调用时传入要监听的可读文件描述符集合,select返回时传出发生可读事件的文件描述符集合writefds——传入传出参数,select调用时传入要监听的可写文件描述符集合,select返回时传出发生可写事件的文件描述符集合errorfds——传出参数,select返回时传出发生事件(包括可读和可写)中异常事件的文件描述符集合timeout——传入参数,设置select阻塞的时间。若设置为NULL,则select一直阻塞直到有事件发生;若设置为0,则select为非阻塞模式,执行后立即返回;若设置为一个大于0的数,即select的阻塞时间,若阻塞时间内有事件发生就返回,否则时间到了立即返回

fd_set是自定义的一个数据结构,可看作一个集合,存放可读、可写或异常事件的文件描述符。fd_set集合通常有以下四个宏来操作:

void FD_ZERO(fd_set *fdset);  //清空fdset中所有文件描述符
void FD_SET(int fd,fd_set *fdset);  //添加文件描述符fd到集合fdset中
void FD_CLR(int fd,fd_set *fdset); //将文件描述符fd从集合fdset中去除
int FD_ISSET(int fd,fd_set *fdset);  //判断文件描述符fd是否在集合fdset中

select工作原理:传入要监听的文件描述符集合(可读、可写或异常)开始监听,select处于阻塞状态,当有事件发生或设置的等待时间timeout到了就会返回,返回之前自动去除集合中无事件发生的文件描述符,返回时传出有事件发生的文件描述符集合。但select传出的集合并没有告诉用户集合中包括哪几个就绪的文件描述符,需要用户后续进行遍历操作。

2、select优缺点

优点:

(1)select的可移植性较好,可以跨平台;
(2)select可设置的监听时间timeout精度更好,可精确到微秒,而poll为毫秒。

缺点:

(1)select支持的文件描述符数量上限为1024,不能根据用户需求进行更改;
(2)select每次调用时都要将文件描述符集合从用户态拷贝到内核态,开销较大;
(3)select返回的就绪文件描述符集合,需要用户循环遍历所监听的所有文件描述符是否在该集合中,当监听描述符数量很大时效率较低。

3、select使用经典案例

用select函数编写一个简单的高并发服务器,且假设服务器启动时处于无连接状态,满足以下功能:
a)可处理来自一个新客户端的连接请求;
b)监听可读事件,若已连接客户端的已连接描述符发生可读事件,服务器从客户端读取数据并处理;

服务器端代码:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include<ctype.h>#define SERV_PORT 6666int main()
{int i,j,n,maxi;int maxfd,listenfd,connfd,sockfd;int nready,client[FD_SETSIZE-1];  //FD_SETSIZE=1024,定义数组client来储存已连接描述符,最多1023个char buf[BUFSIZ], str;struct sockaddr_in clie_addr,serv_addr;socklen_t clie_addr_len;fd_set allset,readset;  //定义监听描述符集合allset和发生事件描述符集合readsetbzero(&serv_addr,sizeof(serv_addr));serv_addr.sin_family=AF_INET;serv_addr.sin_port=htons(SERV_PORT):  //端口号,将无符号短整型转换为网络字节顺序serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);  //一个主机可能有多个网卡,所以是本机的任意IP地址listenfd=socket(AF_INET,SOCK_STREAM,0); //AF_INET表示使用32位IP地址,SOCK_STREAM表示使用TCP连接bind(listenfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));  //将服务器套接字地址与套接字描述符联系起来listen(listenfd,1024); //设置可监听的连接数量为1024maxfd=listenfd; //初始化最大文件描述符为监听描述符listenfd//初始化client数组,将数组所有元素置为-1int maxi=-1;  //数组client储存的文件描述符的个数,初始化为-1for (i=0;i<FD_SETSIZE;i++)client[i]=-1//初始化select监听文件描述符的集合FD_ZERO(&allset);     //初始化监听集合FD_SET(listenfd,&allset);  //将监听描述符listenfd添加到集合中while(1){readset=allset;nready=select(maxfd+1,&readset,NULL,NULL,NULL); //select只监听可读事件,且为永久阻塞直到有事件发生if (nready<0)perr_exit("select error");//判断listenfd是否发生事件,若发生,则处理新客户端连接请求if (FD_ISSET(listenfd,&readset)){clie_addr_len=sizeof(clie_addr);connfd=accept(listenfd,(struct sockaddr *)&clie_addr,&clie_addr_len);//与请求客户端建立连接printf(“received from %s at port %d\n”,inet_ntop(AF_INET,&clie_addr.sin_addr.s_addr,&str,sizeof(str)),ntohs(clie_add.sin_port));  //打印该客户端的IP地址和端口号//将connfd赋值给client数组中第一个为-1的元素位置for (i=0;i<FD_SETSIZE;i++){if (client[i]<0){client[i]=connfd;break;}}//判断select监听的文件描述符的个数是否超过上限if (i == FD_SIZE-1)   //减1的原因是要考虑监听描述符listenfd也属于select监控{fputs("too many clients\n",stderr);exit(1);}FD_SET(connfd,&allset);  //向监控的文件描述符集合allset中添加新的描述符connfdif (connfd>maxfd)maxfd=connfd;   //更新最大文件描述符值//保证maxi永远是client数组中最后一个非-1的元素的位置if(i>maxi)maxi=i;//如果nready=1,即只有一个发生事件的描述符,在此条件下必为listenfd,则返回循环位置,继续调用select监控;否则继续向下执行--nready;if (nready==0)continue;}//找到client数组中发生事件的已连接描述符,并读取、处理数据for (i=0;i<=maxi;i++){sockfd=client[i];if (sockfd<0)  //已连接描述符失效,重新开始循环continue;  if (FD_ISSET(sockfd,&readset)){n=read(sockfd,buf,sizeof(buf));if (n==0) //当客户端关闭连接,服务端也关闭连接{colse(sockfd);FD_CLR(sockfd,&allset);  //解除select对该已连接文件描述符的监控client[i]=-1;}else if (n>0){for (j=0;j<n;j++)buf[j]=toupper(buf[j]);sleep(2);write(sockfd,buf,n);}--nready;if (nready==0)break;  //跳出for循环,还在while中}}}close(listenfd);return 0;
}

客户端代码

#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>#define SERV_IP "127.0.0.1"  //客户端、服务端都在一台主机上,所以直接用本机IP地址
#define SERV_PORT 6666int main()
{int cfd;struct sockaddr_in serv_addr;char buf[BUFSIZ];int n;cfd=socket(AF_INET,SOCK_STREAM,0);memset(&serv_addr,0,sizeof(serv_addr));serv_addr.sin_family=AF_INET;serv_addr.sin_port=htons(SERV_PORT);inet_pton(AF_INET,SERV_IP,&serv_addr.sin_addr.s_addr);  //将点十进制字节串转换为网络字节序connect(cfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));while(1){fgets(buf,sizeof(buf),stdin);write(cfd,buf,strlen(buf));n=Read(cfd,buf,sizeof(buf));write(STDOUT_FILENO,buf,n);}close(cfd);return 0;
}

将服务端、客户端生成可执行文件之后,先启动服务器,再启动客户端与服务器建立连接,用户输入字符,服务器将读取到的字符转换为大写,再写回客户端的屏幕上,测试结果如下所示:
服务器会显示客户端的IP地址以及端口号,可发现客户端与服务器来自同一台主机,这是没有问题的

客户端会显示用户输入的字符串以及转为大写后写回的字符串,如图:

注意:数据传输完毕后,一定要客户端先断开连接,避免服务器出现TIME_WAIT状态,从而占用服务器资源。

select函数详解及使用案例相关推荐

  1. 利用MATLAB求解非线性优化问题---fgoalattain函数详解及应用案例

    最近在做方程组的非线性优化问题,用到了fgoalattain函数,总结一下: 意义 解决多目标的非线性优化问题 函数形式 函数表示形式如下: 上式中,weight, goal, b和beq 是向量(组 ...

  2. Linux I/O复用之select函数详解

    http://blog.csdn.net/y396397735/article/details/55004775 select函数的功能和调用顺序 使用select函数时统一监视多个文件描述符的:  ...

  3. select()函数详解

    elect()在SOCKET编程中还是比较重要的,可是对于初学SOCKET的人来说都不太爱用select()写程序,他们只是习惯写诸如 conncet().accept().recv()或recvfr ...

  4. select 函数详解

    Unix系统下解释: 头文件: #include  <sys/time.h> 函数原型:int select(int maxfdp, fd_set* readfds, fd_set* wr ...

  5. select函数详解

    select函数的功能和调用顺序 使用select函数可以完成非阻塞方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况--读写或是异常. 非阻塞方式:non-block,就是进程或线程执行此 ...

  6. TCP/IP编程之select函数详解

    前述: linux下的I/O复用模型目前很多都已经不用select函数了,而是用epoll,但是为什么还需要了解select编程呢,其实是从两个方面考虑的:一是为了通过select去理解epoll,而 ...

  7. linux select函数详解

    在Linux中,我们可以使用select函数实现I/O端口的复用,传递给 select函数的参数会告诉内核: •我们所关心的文件描述符 •对每个描述符,我们所关心的状态.(我们是要想从一个文件描述符中 ...

  8. select函数详解及实例分析

    Select函数在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect. accept.recv或recvfrom这样的阻塞 ...

  9. linux下wait函数,Linux wait函数详解

    wait和waitpid出现的原因 SIGCHLD --当子进程退出的时候,内核会向父进程SIGCHLD信号,子进程的退出是个异步事件(子进程可以在父进程运行的任何时刻终止) --子进程退出时,内核将 ...

最新文章

  1. JAVA实现查看详情功能_【JavaWeb】111:详情页面的实现
  2. OpenCV背景扣除Background Subtraction
  3. 文件服务器冷热数据划分,游戏服务器冷热数据分离方案
  4. 『後起Android开发02』对SharedPreferences和Toast的简单封装
  5. HTML自动刷新页面
  6. php代码 编码转换,php字符编码转换代码
  7. 微信小程序父子组件之间传值
  8. jdk重复安装,Error:Registry key ‘Software\JavaSoft\Java Runtime Environment\CurrentVersion(已解决)
  9. git pull 报错:Exiting because of unfinished merge.
  10. 了解Nape 2d物理引擎 第一天
  11. np.add.at和np.negative.at
  12. Excel拆分字符判断是否有汉字
  13. 电商时代的逆向思维法则
  14. 阿里云域名购买与域名解析使用教程
  15. 密码学归约证明——DH密钥交换协议在窃听者存在的情况下的安全性
  16. 游戏修改器制作教程一:键盘鼠标模拟
  17. 游戏感:虚拟感觉的游戏设计师指南——第十五章 超级马里奥64
  18. 图像比对-感知哈希算法
  19. cmatrix秀一波骚操作
  20. 51单片机静态动态数码管显示

热门文章

  1. mysql数据库 主键
  2. 钉钉PC端开发者版本下载及调试技巧
  3. [230501] 4月29日考试真题第一篇|Temporary Pools
  4. pytorch学习(1) 数据集制作
  5. [摘录]遇见未知的自己(三)
  6. python饼图显示百分比怎么设置_python使用Matplotlib绘制饼图
  7. python反弹shell
  8. Java技术常见术语及其解释(常见缩写)
  9. 后台接口接收数组,前端 ajax traditional参数必须为true
  10. Nodejs安装npm