1.8.2 Linux socket 编程 关于 select函数详解

函数作用:

系统提供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);
 
函数参数列表: 
 

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

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

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

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

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

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

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

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

	参数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 200 int 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值