一、IO模型 --- 多路复用。
1、什么是多路复用?
就是先将需要监听的文件描述符/套接字加入到一个集合中,然后在规定时间/无限时间去监听这个集合,如果在规定时间/无限时间内有数据到达,则其余没有数据到达的文件描述符就会被自动剔除到集合之外,我们用户只需要观察集合中有哪些文件描述符/套接字剩下就可以了。
2、如何实现多路复用?
1)定义一个集合。 数据类型: fd_set fd_set set;
2)删除、添加、清空、判断文件描述符在集合中? --> man 2 select 就可以查询到以下的函数接口。
void FD_CLR(int fd, fd_set *set); ---> 从集合中删除一个文件描述符
int FD_ISSET(int fd, fd_set *set); ---> 测试一个文件描述符是否在集合中
void FD_SET(int fd, fd_set *set); ---> 往集合中添加一个文件描述符
void FD_ZERO(fd_set *set); ---> 清空集合
参数: fd: 文件描述符/套接字 set: 集合的地址
注意: FD_ISSET()一般在select函数之后使用,因为我们最关心的就是谁还在集合中。
例题: 写一个程序,定义一个集合,先清空集合,然后将键盘的文件描述符添加到集合中,然后再判断一下该键盘的文件描述符在不在集合中。
#include "head.h"
int main(int argc,char *argv[]) { //1. 定义一个集合 fd_set set;
//2. 清空集合
FD_ZERO(&set);
//3. 将键盘的文件描述符添加到集合中。
//FD_SET(STDIN_FILENO,&set);
//4. 判断一下该文件描述符在不在集合中。
int ret = FD_ISSET(STDIN_FILENO,&set);
printf("ret = %d\n",ret);
return 0;
}
运行结果: 在集合中,返回1 不在集合中,返回0
3)如何监听一个集合? --> select() -> man 2 select
#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: 一般设置为NULL exceptfds:一般设置为NULL timeout: 如果设置为NULL,则是无限时间监听这个集合。
返回值: 成功:就绪的文件描述符的个数(集合中剩余的文件描述符的个数) 失败:-1
eg:服务器
#include "head.h"
int main(int argc,char *argv[]) // ./Rose 50001
{
//1. 创建TCP套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
//2. 绑定IP地址
struct sockaddr_in srvaddr;
socklen_t len = sizeof(srvaddr);
bzero(&srvaddr,len);
srvaddr.sin_family = AF_INET;
srvaddr.sin_port = htons(atoi(argv[1]));
srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd,(struct sockaddr *)&srvaddr,len);
//3. 设置监听套接字
listen(sockfd,5);
//4. 坐等客户端的连接。
struct sockaddr_in cliaddr;
bzero(&cliaddr,len);
int connfd;
connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len);
if(connfd > 0)
{
printf("new connection:%s\n",inet_ntoa(cliaddr.sin_addr));
}
//5. 将套接字加入到集合中。
fd_set set;
int maxfd = connfd > STDIN_FILENO ? connfd : STDIN_FILENO;
char buf[100];
//6. 不断地监听这个集合中。
while(1)
{
FD_ZERO(&set);
FD_SET(connfd,&set);
FD_SET(STDIN_FILENO,&set);
//无限等待,直到有数据,这个函数才会返回。
select(maxfd+1,&set,NULL,NULL,NULL);
//select函数返回了,究竟是谁还在集合中呢?
if( FD_ISSET(connfd,&set) ) //如果connfd还在集合中,那么说明客户端想我发送数据。
{
bzero(buf,sizeof(buf));
recv(connfd,buf,sizeof(buf),0);
printf("from client:%s",buf);
if(strncmp(buf,"quit",4) == 0)
{
break;
}
}
if( FD_ISSET(STDIN_FILENO,&set) ) //如果STDIN_FILENO还在集合中,那么说明我想发送数据给客户端。
{
bzero(buf,sizeof(buf));
fgets(buf,sizeof(buf),stdin);
send(connfd,buf,strlen(buf),0);
if(strncmp(buf,"quit",4) == 0)
{
break;
}
}
}
//7. 回收资源。
close(sockfd);
close(connfd);
return 0;
}
客户端:
#include "head.h"
int main(int argc,char *argv[]) // ./Jack 192.168.19.3 50001
{
//1. 创建TCP协议套接字。
int sockfd;
sockfd = socket(AF_INET,SOCK_STREAM,0);
//2. 打电话。
struct sockaddr_in srvaddr;
socklen_t len = sizeof(srvaddr);
bzero(&srvaddr,len);
srvaddr.sin_family = AF_INET;
srvaddr.sin_port = htons(atoi(argv[2]));
inet_pton(AF_INET,argv[1],&srvaddr.sin_addr);
int ret = connect(sockfd,(struct sockaddr *)&srvaddr,len);
if(ret == -1)
{
printf("connect error!\n");
}
//3. 将套接字加入到集合中。
fd_set set;
int maxfd = sockfd > STDIN_FILENO ? sockfd : STDIN_FILENO;
char buf[100];
//4. 不断地监听这个集合中。
while(1)
{
FD_ZERO(&set);
FD_SET(sockfd,&set);
FD_SET(STDIN_FILENO,&set);
//无限等待,直到有数据,这个函数才会返回。
select(maxfd+1,&set,NULL,NULL,NULL);
//select函数返回了,究竟是谁还在集合中呢?
if( FD_ISSET(sockfd,&set) ) //如果connfd还在集合中,那么说明客户端想我发送数据。
{
bzero(buf,sizeof(buf));
recv(sockfd,buf,sizeof(buf),0);
printf("from server:%s",buf);
if(strncmp(buf,"quit",4) == 0)
{
break;
}
}
if( FD_ISSET(STDIN_FILENO,&set) ) //如果STDIN_FILENO还在集合中,那么说明我想发送数据给客户端。
{
bzero(buf,sizeof(buf));
fgets(buf,sizeof(buf),stdin);
send(sockfd,buf,strlen(buf),0);
if(strncmp(buf,"quit",4) == 0)
{
break;
}
}
}
//5. 回收资源。
close(sockfd);
return 0;
}