Linux 网络编程之select函数

本文详细介绍了I/O多路转接技术,特别是select函数的使用。通过创建文件描述符列表,利用select函数监听文件描述符的I/O操作,实现对多个连接的并发处理。select函数参数包括监听的文件描述符集合、读写异常描述符集合及超时时间。在循环中,select会返回就绪的文件描述符数量,供程序进一步处理。然而,select存在文件描述符数量限制(默认1024个)和效率问题。文章还提供了select的伪代码和实际代码示例,展示了如何在服务器端接收并响应客户端请求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、什么是I/O多路转接技术:

  • 先构造一张有关文件描述符的列表, 将要监听的文 件描述符添加到该表中
  • 然后调用一个函数,监听该表中的文件描述符,直到 这些描述符表中的一个进行I/O操作时,该函数才 返回
    1.该函数为阻塞函数
    2.函数对文件描述符的检测操作是由内核完成的
  • 在返回时,它告诉进程有多少(哪些)描述符要进行
  • 不再由应用程序自己监视客户端连接,取而代之由内核替应用程序监视文件。

2、 IO多路转接 - select

在这里插入图片描述
在这里插入图片描述
select返回的是发生I/O操作文件描述符的总数量,传出来的文件描述符表是一张表示进行I/O操作的表,如果是1,表示所代表的文件描述符进行了I/O操作。

  • 头文件

include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

  • 函数原型

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval timeout);
nfds: 监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态
readfds: 监控有读数据到达文件描述符集合,传入传出参数
writefds: 监控写数据到达文件描述符集合,传入传出参数
exceptfds: 监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
timeout: 定时阻塞监控时间,3种情况 //相对时间
1.NULL,永远等下去
2.设置timeval,等待固定时间
3.设置timeval里时间均为0,检查描述字后立即返回,轮询
struct timeval {
long tv_sec; /
seconds /
long tv_usec; /
microseconds */
};

  • 文件描述符处理函数

void FD_CLR(int fd, fd_set *set); //把文件描述符集合里fd清0
int FD_ISSET(int fd, fd_set *set); //测试文件描述符集合里fd是否置1
void FD_SET(int fd, fd_set *set); //把文件描述符集合里fd位置1
void FD_ZERO(fd_set *set); //把文件描述符集合里所有位清0

  • 返回值
    成功:所监听的所有的监听集合中,满足条件的总数,失败:返回-1,设置errno

注意:

  • select能监听的文件描述符个数受限
  • select同时监听的文件描述符最多为1024个

使用select函的优缺点:

  • 优点:
    ○ 跨平台
  • 缺点
    1.每次调用select,都需要把fd集合从用户态拷贝
    到内核态,这个开销在fd很多时会很大
    2.同时每次调用select都需要在内核遍历传递进来
    的所有fd,这个开销在fd很多时也很大
    3.select支持的文件描述符数量太小了,默认是
    1024

3、select伪代码

int main(0
{
int lfd = socket();
bind();
listen();
//创建一张文件描述符表
fd_set reads,temp;
//初始化
fd_zero(&reads);
//将监听的文件描述符加入集合
fd_set(lfd,&reads);
int maxfd = lfd;
while(1)
{
//委托检测
int ret = select(maxfd+1,&reads;NUILL,NULL,NULL);
//判断是不是监听的
if(fd_issed(lfd,&temp))
{
//接受新连接
int cfd = accept();
//cfd加入读集合
fd_set(cfd,&reads);
//更新maxfd
maxfd = maxfd < cfd?cfd:maxfd;
}
//客户端发送数据
for(int i = lfd+1; i <= maxfd; i++)
{
if(fd_isset(i &temp))
{
int len =read()
if(len == 0)
{
//cfd从读集合中删掉
fd_clr(i,&reads);
}
write();
}
}

}
}

4、select 代码

sever.c


#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include<ctype.h>

#include<wrap.h>
#definr SERRV_PORT 6666

int main(int argv,char * argv[])
{

  int i,j,n,maxi;
int nready,client[ED_SETSIZE] ;//自定义数组,防止遍历1024个文件描述符
int maxfd,listenfd,connfd,sockfd;
char buf[MAXSIZE],str[INET)ADDRSTRLEN];

struct sockaddr_in cle_addr,serv_addr;
socklen_t client_addr_len;
fd_set rset,allset; //rset:读事件集合,allset 用来暂存

listenfd = sock(AF_INET,SOCK_STREAM,0);

bzero(&serv_addr,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(SERV_PORT);

Bind(listenfd,(struct sockaddr*)serv_addr,sizeof(serv_addr));
Listen(listenfd,128);

maxfd = listenfd; //起初listenfd为最大的文件描述符

maxi  = -1; //用作client[] 下标,初始值指向0 个元素之前下标的位置

maxi = -1;

for(i = 0; i < FD_SIZE; i++)
client[i] = -1;     //用-1 初始化client[]

FD_ZERO(&allset);
FD_SET(listened,&allset); //构造select监听的文件描述符集

while(1)
{

  rset  = allset;   //每次循环都重新设置select 监控信号集
  nready = select(maxfd + 1,&rset,NULL,NULL,NULL);
 
 if(FD_ISSET(listenfd,&rset)){ //说明有新的连接
 
 client_addr_len = sizeof(client_addr);
 connfd= Accept(listenfd,(struct sock_addr*)clie_addr,&clien_addr_len);//accept不会阻塞
 printf("received from %s at port %d\n",inet_ntop(AF_INET,&clie_addr.sin_addr,str,sizeof(str)),ntops(clie_addr_sinport));
for(i = 0; i <FD_SETSIZE; i++)
{
  if(client[i] < 0)   //找到client[]没有用的位置
  {
    client[i] = connfd;  //保存accept返回的文件描述符,
    break;
  }
}

if(i == FD_SETSIZE) //达到select 能监控的文件描述符的上限
{
  fputs("too many clients\n",stdeer); 
  exit(1);
}

FD_SET(connfd,&allset);//向监控文件描述符集合allset添加新的文件描述符connfd
if(connfd > maxfd)
maxfd = connfd;    //select 第一个参数需要

if(i > maxi) //保证maxi 存的总是client{} 最后一个元素下标
maxi = i;

if-- nready == 0continue;
}

//客户端有向服务器端发数据
for(i = 0; i <= maxi; i ++) //检测哪个clients有数据就绪
{
  if((sockfd = client[i]) < 0)
  continue;
  if(FD_ISSET(sockfd,&rset))
  {
     if((n =Read(sockfd,buf,sizeof(buf))) == 0) //当client关闭连接时,服务器也关闭来连接
     {
         Close(sockfd);
         FD_CLR(sockfd,buf,sizeof9buf) == 0) /解除select对此文件描述符的监控
         client[i] = -1;
    }else if(n > 0)
    {
       for( j = 0; j < n; j ++)
       buf[j] = toupper(buf[j]);
       sleep(10);
       Write(sockfd,buf,n);
    }
    if(-- nready == 0) ///跳出佛如,但还在while中
     break;
  }
}
}
Close(listenfd);
return 0;

}

client.c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include<ctype.h>

#include<wrap.h>
#definr SERRV_PORT 6666

int main()
{
   struct sockaddr_in servvaddr;
   socklen_t servaddr_len;
   chat buf[MAXSIZE];
   bzero(&sockaddr,sizeof(servvaddr));
   sockaddr.sin_family = AF_INET;
   inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr);
   servaddr.sin_port = htons(SERRV_PORT )
  int lfd = sock(AF_INET,SOCKSTREAM,0);
  connect(lfd,(struct sockaddr*)servaddr,&servaddr_len);
  while(fgets(buf, MAXLINE, stdin) != NULL)
  {
     Write(cfd,buf,sizeof(buf));
     n = Read(sockfd, buf, MAXLINE);
		if (n == 0)
			printf("the other side has been closed.\n");
		else
			Write(STDOUT_FILENO, buf, n);
	}
	Close(sockfd);
	return 0;
    
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值