一、linux下的四种IO模型包含哪些?
Linux下的四种IO模型包含:阻塞模型、非阻塞模型、异步驱动模型、多路复用模型。四种模型原理图如图1-1所示。
图1-1 模型原理图
Linux中常见的命令名如图1-2所示。
图1-2 文件命令名描述图
二、Linux下四种模型简介
1..阻塞模型
一般情况下,文件描述符都是阻塞模型,与阻塞相关的函数有: accept,recv,recvfrom,read。
阻塞其实就是将进程由执行态调度到了睡眠态
代码示例1:
/*客户端*/
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
int main()
{
//(1)创建数据流套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
perror(" socket tcp failed");
return -1;
}
//(2)初始化网络通信结构体
//(3)把网络通信结构体和套接字绑定在一起
//(2)和(3)可以省略,此时发送方的端口号随机,如果要固定,就不能省略
//(4)向服务器发起连接请求
struct sockaddr_in destaddr;
destaddr.sin_family = AF_INET;
destaddr.sin_port = htons(40000);
destaddr.sin_addr.s_addr = inet_addr("192.168.12.16");//服务器的IP地址
socklen_t len = sizeof(struct sockaddr_in);
int ret = connect(sockfd,(struct sockaddr*)&destaddr,len);
if(ret == -1)
{
perror("connect failed");
return -1;
}
//数据的收发
char buf[32];
while(1)
{
scanf("%s",buf);
send(sockfd,buf,32,0);
}
return 0;
}
/*服务器端*/
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
int main()
{
//(1)创建数据流套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
perror(" socket tcp failed");
return -1;
}
//(2)初始化网络通信结构体
struct sockaddr_in myaddr;
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(40000);
myaddr.sin_addr.s_addr = INADDR_ANY;
//(3)把网络通信结构体和套接字绑定在一起
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(sockfd,(struct sockaddr*)&myaddr,len);
if(ret == -1)
{
perror("bind failed");
return -1;
}
//(4)监听是否有客户端连接到服务器
if( listen(sockfd,5) != 0)
{
perror("listen failed");
return -1;
}
//(5)等待客户端向服务器发起连接请求。
struct sockaddr_in clientaddr;
int server_sock = accept(sockfd,(struct sockaddr*)&clientaddr,&len);
if(server_sock == -1)
{
perror("accept failed");
return -1;
}
printf("accept new_client connect\n");
printf("clien.sin_port = %hu\n",ntohs(clientaddr.sin_port));
printf("clien.sin_addr = %s\n",inet_ntoa(clientaddr.sin_addr));
//数据的收发
char buf[32];
while(1)
{
recv(server_sock,buf,32,0);
printf("recv:%s\n",buf);
}
return 0;
}
2..非阻塞模型
非阻塞模型与阻塞模型相反。修改文件属性函数:
int fcntl(int fd, int cmd, ... /* arg */ );
参数一: 要操作的文件的文件描述符
参数二: 要操作的命令
参数三: 根据参数二需要加入的参数,也可以根据情况省略
非阻塞模型下,原本可以作为阻塞使用的函数,都无法再将进程调度到睡眠态。
将server_sock设置成非阻塞模型:
long stu = fcntl(server_sock,F_GETFL); //先获取原文件的属性
stu |= O_NONBLOCK; //位或的原因:是为了不改变原有的文件属性,加上非阻塞属性
fcntl(server_sock,F_SETFL,stu); //把改变后的属性放回原文件中
代码示例2:
客户端代码与示例代码1客户端一样。
/*服务器端*/
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
int main()
{
//(1)创建数据流套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
perror(" socket tcp failed");
return -1;
}
//(2)初始化网络通信结构体
struct sockaddr_in myaddr;
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(40000);
myaddr.sin_addr.s_addr = INADDR_ANY;
//(3)把网络通信结构体和套接字绑定在一起
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(sockfd,(struct sockaddr*)&myaddr,len);
if(ret == -1)
{
perror("bind failed");
return -1;
}
//(4)监听是否有客户端连接到服务器
if( listen(sockfd,5) != 0)
{
perror("listen failed");
return -1;
}
//(5)等待客户端向服务器发起连接请求。
struct sockaddr_in clientaddr;
int server_sock = accept(sockfd,(struct sockaddr*)&clientaddr,&len);
if(server_sock == -1)
{
perror("accept failed");
return -1;
}
printf("accept new_client connect\n");
printf("clien.sin_port = %hu\n",ntohs(clientaddr.sin_port));
printf("clien.sin_addr = %s\n",inet_ntoa(clientaddr.sin_addr));
//将server_sock设置成非阻塞模型
long stu = fcntl(server_sock,F_GETFL);//先获取原文件的属性
stu |= O_NONBLOCK;//位或的原因:是为了不改变原有的文件属性,加上非阻塞属性
fcntl(server_sock,F_SETFL,stu);//把改变后的属性放回原文件中
//数据的收发
char buf[32];
while(1)
{
recv(server_sock,buf,32,0);
printf("recv:%s\n",buf);
}
return 0;
}
3..异步驱动模型
异步驱动模型是由文件产生input或者output数据流时,引发操作系统产生SIGIO或者SIGURG信号,然后进入到信号注册函数中处理。
signal(SIGIO,catch);//注册信号
fcntl(server_sock,F_SETOWN,getpid());//接收SIGIO信号的是当前进程
fcntl(server_sock,F_SETFL,O_ASYNC);//将文件属性设置为异步属性
示例代码3:
客户端代码与示例代码1客户端一样。
/*服务器端*/
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
int server_sock=0;
void catch(int sig)
{
//数据的收发
char buf[32];
recv(server_sock,buf,32,0);
printf("recv:%s\n",buf);
}
int main()
{
//(1)创建数据流套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
perror(" socket tcp failed");
return -1;
}
//(2)初始化网络通信结构体
struct sockaddr_in myaddr;
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(40000);
myaddr.sin_addr.s_addr = INADDR_ANY;
//(3)把网络通信结构体和套接字绑定在一起
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(sockfd,(struct sockaddr*)&myaddr,len);
if(ret == -1)
{
perror("bind failed");
return -1;
}
//(4)监听是否有客户端连接到服务器
if( listen(sockfd,5) != 0)
{
perror("listen failed");
return -1;
}
//(5)等待客户端向服务器发起连接请求。
struct sockaddr_in clientaddr;
server_sock = accept(sockfd,(struct sockaddr*)&clientaddr,&len);
if(server_sock == -1)
{
perror("accept failed");
return -1;
}
printf("accept new_client connect\n");
printf("clien.sin_port = %hu\n",ntohs(clientaddr.sin_port));
printf("clien.sin_addr = %s\n",inet_ntoa(clientaddr.sin_addr));
signal(SIGIO,catch);//注册信号
fcntl(server_sock,F_SETOWN,getpid());//接收当前信号的是当前进程
fcntl(server_sock,F_SETFL,O_ASYNC);//将文件属性设置为异步属性
int i=0;
while(1)
{
printf("sec:%d\n",i++);
sleep(1);
}
return 0;
}
4..多路复用模型
多路复用模型是不停的监控一个文件集合。如果文件集合中的文件描述符有变化,那么立即退出监控,检测变化的原因。如果没有变化,那么也只是监控一段时间,自动退出。原理图如下图所示。
#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);
参数一: 文件描述符的最大值+1
参数二: 需要监控读操作的文件集合
参数三: 需要监控写操作的文件集合
参数四: 需要监控执行操作的文件集合
参数五: 超时时间
返回值: 如果有文件集合产生动作,返回值>0
如果没有文件集合产生动作,是超时结束,返回值==0
如果失败出错,返回-1.
//把fd文件描述符从set集合中删除
void FD_CLR(int fd, fd_set *set);
//判断fd文件描述符是否在set集合中
int FD_ISSET(int fd, fd_set *set);
//把fd文件描述符加入到set集合中
void FD_SET(int fd, fd_set *set);
//清空set文件集合
void FD_ZERO(fd_set *set);
//超时时间结构体
struct timeval {
long tv_sec; 秒
long tv_usec; 微秒
};
多路复用模型的优点: 在同时处理多个文件描述符的时候,可以不开线程。
示例代码4:
客户端与示例代码1一样。
/*服务器端*/
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
int main()
{
//(1)创建数据流套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
perror(" socket tcp failed");
return -1;
}
int on=1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
//(2)初始化网络通信结构体
struct sockaddr_in myaddr;
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(40000);
myaddr.sin_addr.s_addr = INADDR_ANY;
//(3)把网络通信结构体和套接字绑定在一起
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(sockfd,(struct sockaddr*)&myaddr,len);
if(ret == -1)
{
perror("bind failed");
return -1;
}
//(4)监听是否有客户端连接到服务器
if( listen(sockfd,5) != 0)
{
perror("listen failed");
return -1;
}
//开始设置文件描述符监控 select
int maxfd = sockfd; //用来保存最大的文件描述符
fd_set rfd; //读文件集合
int server_fd[20];//用来保存客户端连接后产生的通信用桃姐
int i=0; //用来表示server_fd下表
int num=0; //用来表示客户端的个数
struct timeval t;//超时时间结构体
while(1)
{
FD_ZERO(&rfd); //清空文件集合
FD_SET(sockfd,&rfd); //把套接字加入到读文件集合
for(i=0;i<num;i++)
FD_SET( server_fd[i],&rfd); //将客户端文件描述符加入到读文件集合
t.tv_sec = 2;//超时时间为2秒
printf("准备监听\n");
//监控文件描述符集合
ret = select(maxfd+1,&rfd,NULL,NULL,&t);
if(ret == -1)
{
perror("select failed");
return -1;
}
else if(ret == 0)
{
printf("时间超时\n");
continue;
}
else if(ret > 0)//文件集合中的文件描述符有动作
{
//判断 sockfd 是否还在集合中
if( FD_ISSET(sockfd,&rfd) )
{
//(5)等待客户端向服务器发起连接请求。
struct sockaddr_in clientaddr;
server_fd[num] = accept(sockfd,(struct sockaddr*)&clientaddr,&len);
if(server_fd[num] == -1)
{
perror("accept failed");
return -1;
}
printf("accept new_client connect\n");
printf("clien.sin_port = %hu\n",ntohs(clientaddr.sin_port));
printf("clien.sin_addr = %s\n",inet_ntoa(clientaddr.sin_addr));
if(maxfd < server_fd[num])
maxfd = server_fd[num];//最大的文件描述符是最新创建的这个
num++;//客户端连接个数+1
}
//客户端发过来数据---每次遍历客户端文件描述符集合
for(i=0;i<num;i++)
{
if( FD_ISSET(server_fd[i],&rfd) )
{
//数据的收发
char buf[32];
recv(server_fd[i],buf,32,0);
printf("server_fd[%d]=%d,recv:%s\n",i,server_fd[i],buf);
if(strcmp("88",buf)==0)
{
close(server_fd[i]);
int j;
for(j=i;j<num;j++)
server_fd[j] = server_fd[j+1];
num--;
}
}
}
}
}
return 0;
}
总结
本文仅仅对Linux下的阻塞模型、非阻塞模型、异步驱动模型、多路复用模型四种IO模型进行了简单的介绍。