Linux 网络编程之select函数

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;
    
  }
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux网络编程(总共41集) 讲解Linux网络编程知识,分以下四个篇章。 Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输单元(MTU)/路径MTU 以太网帧格式 ICMP ARP RARP 03TCPIP基础(三) IP数据报格式 网际校验和 路由 04TCPIP基础(四) TCP特点 TCP报文格式 连接建立三次握手 连接终止四次握手 TCP如何保证可靠性 05TCPIP基础(五) 滑动窗口协议 UDP特点 UDP报文格式 Linux网络编程之socket编程篇 06socket编程(一) 什么是socket IPv4套接口地址结构 网络字节序 字节序转换函数 地址转换函数 套接字类型 07socket编程(二) TCP客户/服务器模型 回射客户 /服务器 socket、bind、listen、accept、connect 08socket编程(三) SO_REUSEADDR 处理多客户连接(process-per-conection) 点对点聊天程序实现 09socket编程(四) 流协议与粘包 粘包产生的原因 粘包处理方案 readn writen 回射客户/服务器 10socket编程(五) read、write与recv、send readline实现 用readline实现回射客户/服务器 getsockname、getpeername gethostname、gethostbyname、gethostbyaddr 11socket编程(六) TCP回射客户/服务器 TCP是个流协议 僵进程与SIGCHLD信号 12socket编程(七) TCP 11种状态 连接建立三次握手、连接终止四次握手 TIME_WAIT与SO_REUSEADDR SIGPIPE 13socket编程(八) 五种I/O模型 selectselect改进回射客户端程序 14socket编程(九) select 读、写、异常事件发生条件 用select改进回射服务器程序。 15socket编程(十) 用select改进第八章点对点聊天程序 16socket编程(十一) 套接字I/O超时设置方法 用select实现超时 read_timeout函数封装 write_timeout函数封装 accept_timeout函数封装 connect_timeout函数封装 17socket编程(十二) select限制 poll 18socket编程(十三) epoll使用 epoll与select、poll区别 epoll LT/ET模式 19socket编程(十四) UDP特点 UDP客户/服务基本模型 UDP回射客户/服务器 UDP注意点 20socket编程(十五) udp聊天室实现 21socket编程(十六) UNIX域协议特点 UNIX域地址结构 UNIX域字节流回射客户/服务 UNIX域套接字编程注意点 22socket编程(十七) socketpair sendmsg/recvmsg UNIX域套接字传递描述符字 Linux网络编程之进程间通信篇 23进程间通信介绍(一) 进程同步与进程互斥 进程间通信目的 进程间通信发展 进程间通信分类 进程间共享信息的三种方式 IPC对象的持续性 24进程间通信介绍(二) 死锁 信号量 PV原语 用PV原语解决司机与售票员问题 用PV原语解决民航售票问题 用PV原语解决汽车租赁问题 25System V消息队列(一) 消息队列 IPC对象数据结构 消息队列结构 消息队列在内核中的表示 消息队列函数 26System V消息队列(二) msgsnd函数 msgrcv函数 27System V消息队列(三) 消息队列实现回射客户/服务器 28共享内存介绍 共享内存 共享内存示意图 管道、消息队列与共享内存传递数据对比 mmap函数 munmap函数 msync函数 29System V共享内存 共享内存数据结构 共享内存函数 共享内存示例 30System V信号量(一) 信号量 信号量集结构 信号量集函数 信号量示例 31System V信号量(二) 用信号量实现进程互斥示例 32System V信号量(三) 用信号集解决哲学家就餐问题 33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相关函数 POSIX消息队列示例 35POSIX共享内存 POSIX共享内存相关函数 POSIX共享内存示例 Linux网络编程之线程篇 36线程介绍 什么是线程 进程与线程 线程优缺点 线程模型 N:1用户线程模型 1:1核心线程模型 N:M混合线程模型 37POSIX线程(一) POSIX线程库相关函数 用线程实现回射客户/服务器 38POSIX线程(二) 线程属性 线程特定数据 39POSIX信号量与互斥锁 POSIX信号量相关函数 POSIX互斥锁相关函数 生产者消费者问题 自旋锁与读写锁介绍 40POSIX条件变量 条件变量 条件变量函数 条件变量使用规范 使用条件变量解决生产者消费者问题 41一个简单的线程池实现 线程池性能分析 线程池实现 网络编程, Linux

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值