linux下的四种IO模型

一、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模型进行了简单的介绍。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值