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 == 0)
continue;
}
//客户端有向服务器端发数据
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;
}
}