简述:
多路I/O复用是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
这个机制能够通过select/poll/epoll等来使用。这些函数都可以同时监视多个描述符的读写就绪状况,这样,多个描述符的 I/O 操作都能在一个线程内并发交替地顺序完成。
不是不阻塞,是集中阻塞;
Select
select仅仅知道了有I/O事件发生了,却并不知道是哪那几个文件描述符(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据
1)int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
形参:
nfds:轮询的最大文件描述符+1
readfds:监控集合中读变化,例如监控服务器的套接字放到读集合中
writefds:监控集合中描述符的写变化 NULL
exceptfds:监控集合中描述符的异常变化 NULL
timeout: 具体填写一下结构体中的成员,表明在指定时间内监控变化,超时直接返回;
赋值0;表示非阻塞(函数运行时,有描述符变化,得到对应信息,如果没有描述符改变,函数直接返回)
赋值NULL:阻塞,select函数运行开始,等待直到描述符发生改变
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
返回值:
成功返回发生变化的描述符的个数;超时返回0;失败返回-1
注意:调用一次select 只能监控一次描述符的变化,集合中的描述符发生改变后,会从集合中自动消除;
2)操作集合相关函数:
void FD_CLR(int fd, fd_set *set); //将fd从集合中去除
int FD_ISSET(int fd, fd_set *set);//判断集合中fd是否发生变化,是返回真
void FD_SET(int fd, fd_set *set);//将fd添加到集合中
void FD_ZERO(fd_set *set);//清空集合
例题:利用多路io实现多人聊天
//----------------ser
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#define PORT 12234
#define IP "127.0.0.1"
int max=0;
int cfd[20]={0};
int count=0;
int main()
{
//1.创建套接字
int sfd=socket(AF_INET,SOCK_STREAM,0);
if(sfd==-1)
{
perror("socket");
return -1;
}
//2.绑定
struct sockaddr_in s_addr;
s_addr.sin_family=AF_INET;
s_addr.sin_port=htons(PORT);
s_addr.sin_addr.s_addr=inet_addr(IP);
int res=bind(sfd,(struct sockaddr *)&s_addr,sizeof(s_addr));
if(res==-1)
{
perror("bind");
return -1;
}
//3.监听
res=listen(sfd,20);
if(res==-1)
{
perror("listen");
return -1;
}
struct sockaddr_in c_addr;//用于连接时所需绑定的端口和ip
socklen_t len=sizeof(c_addr);
max=sfd;
fd_set table;//创建操作表
fd_set bf_table;//备份
FD_ZERO(&bf_table);//清空操作表
FD_SET(sfd,&bf_table);//将sfd添加到操作表中
char r_buf[128]={0};
char w_buf[128]={0};
while(1)
{
table=bf_table;//将备份表里的内容给操作表
res = select(max+1,&table,NULL,NULL,NULL);
if(FD_ISSET(sfd,&table))//如果判断为真成立说明要连接
{
cfd[count]=accept(sfd,(struct sockaddr *)&c_addr,&len);
max=(max>cfd[count])?max:cfd[count];
//时刻保存select第一个参数是套接字中最大的
FD_SET(cfd[count],&bf_table);
count++;
//把新连接的套接字放入备份表中
if(res==1)//说明操作表内只有一个套接字 sfd
{
continue;
}
}
//到此处说明操作表内不只一个套接字
//有人要发消息情况
int i=0;
for(i=0;i<count;i++)//判断是谁发消息
{
if(FD_ISSET(cfd[i],&table))
{
memset(r_buf,0,sizeof(r_buf));
int a=read(cfd[i],r_buf,sizeof(r_buf));
if(a>0)
{
strcpy(w_buf,r_buf);
//把读到的信息放入到写端里
for(int k=0;k<count;k++)
{
if(k!=i)//除发的本人外都发送
{
write(cfd[k],w_buf,sizeof(w_buf));
}
}
}
else if(a==0)
{
FD_CLR(cfd[i],&bf_table);
sprintf(w_buf,"%d下线",cfd[i]);
//通知别人自己已下线
for(int k=0;k<count;k++)//除发的本人外都发送
{
if(k!=i)
{
write(cfd[k],w_buf,sizeof(w_buf));
}
}
//去除已下线的
for(int j=i;j<count;j++)
{
cfd[j]=cfd[j+1];
}
count--;
}
}
}
}
return 0;
}
//-----------------cli.c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#define IP "127.0.0.1"
#define PORT 12234
int fd=0;
void *W_FUN(void *p);
void *R_FUN(void *p);
int main()
{
//1.创建套接字
fd=socket(AF_INET,SOCK_STREAM,0);
printf("fd = %d\n",fd);
if(fd==-1)
{
perror("socket");
return -1;
}
//2.连接
struct sockaddr_in ser_addr;
ser_addr.sin_family=AF_INET;
ser_addr.sin_addr.s_addr=inet_addr(IP);
ser_addr.sin_port=htons(PORT);
int res=connect(fd,(struct sockaddr *)&ser_addr,sizeof(ser_addr));
if(res==-1)
{
perror("connect");
return -1;
}
printf("连接成功\n");
//创建读写进程
pthread_t r_id,w_id;
pthread_create(&r_id,NULL,R_FUN,NULL);
pthread_create(&w_id,NULL,W_FUN,NULL);
pthread_join(r_id,NULL);
pthread_join(w_id,NULL);
return 0;
}
void *R_FUN(void *p)
{
char buf[128]={0};
int a=0;
while(1)
{
memset(buf,0,sizeof(buf));
a=read(fd,buf,sizeof(buf));
printf("%s\n",buf);
if(a==0)
{
close(fd);
exit(0);
}
}
}
void *W_FUN(void *p)
{
char buf[128]={0};
while(1)
{
memset(buf,0,sizeof(buf));
scanf("%s",buf);
getchar();
write(fd,buf,sizeof(buf));
if(strcmp(buf,"bye")==0)
{
close(fd);
exit(0);
}
}
}