网络IO模型

切记:IO特性不是由接口决定,而是由描述符(fd)的属性决定

本文内容目录:

一:网络IO模型的分类,各个模型的定义和特点

二:每个模型的原理和比较

三:每个模型的编程步骤、编程实例,以及注意细节。

一:网络IO模型的分类,各个模型的定义和特点

在网络IO模型中,有五种模型:

* blocking IO              阻塞IO

* nonblocking IO        非阻塞IO

* IO multiplexing         IO多路复用

* signal driven IO       信号驱动IO

* asynchronous IO      异步IO

二:每个模型的原理

对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,它会经历两个阶段:

1)等待数据准备 (Waiting for the data to be ready)

2)将数据从内核拷贝到进程中(Copying the data from the kernel to the process)

 (1)、阻塞IO(blocking
IO)

                                               
 

                                                                                                            阻塞模型

       当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network
io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来

我们注意到,大部分的socket接口都是阻塞型的。所谓阻塞型接口是指系统调用(一般是IO接口)不返回调用结果并让当前线程一直阻塞,只有当该系统调用获得结果或者超时出错时才返回。

实际上,除非特别指定,几乎所有的IO接口 ( 包括socket接口 ) 都是阻塞型的。这给网络编程带来了一个很大的问题,如在调用send()的同时,线程将被阻塞,在此期间,线程将无法执行任何运算或响应任何的网络请求。

(2)、非阻塞IO(non-blocking
IO)

 
                               

 
                                                                               非阻塞模型

从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。

  所以,在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。

    非阻塞的接口相比于阻塞型接口的显著差异在于,在被调用之后立即返回。使用如下的函数可以将某句柄fd设为非阻塞状态

 (3)、多路复用IO(IO multiplexing)

多路复用IO的模型

当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。

这个图和blocking IO的图其实并没有太大的不同,事实上还更差一些。因为这里需要使用两个系统调用(select和recvfrom),而blocking IO只调用了一个系统调用(recvfrom)。但是,用select的优势在于它可以同时处理多个connection。(多说一句:所以,如果处理的连接数不是很高的话,使用select/epoll的web
server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)

  在多路复用模型中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket
IO给block。因此select()与非阻塞IO类似。

   多路复用模型的一个执行周期

实际上,Linux内核从2.6开始,也引入了支持异步响应的IO操作,如aio_read,
aio_write,这就是异步IO。

(4)、信号驱动IO
(ignal
driven IO)  

 
                                  

 首先我们允许套接口进行信号驱动I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据

(5)、 异步IO (asynchronous
IO     )

 
                        

   调用aio_read函数,告诉内核描述字,缓冲区指针,缓冲区大小,文件偏移以及通知的方式,然后立即返回。当内核将数据拷贝到缓冲区后,再通知应用程序。这个操作和信号驱动的区别就是:异步模式等操作完毕后才通知用户程序而信号驱动模式在数据到来时就通知用户程序。

五种IO模型的比较:

前四种模型的区别是第一阶段,第二阶段基本相同,都是将数据从内核拷贝到调用者的缓冲区。而异步I/O的两个阶段都不同于前四个模型。

以上部分内容的参考文献:

http://blog.csdn.net/blueboy2000/article/details/4485874

http://blog.csdn.net/zhoudaxia/article/details/8974779

三:每个模型的编程步骤、编程实例,以及注意细节。

头文件:head.h(下面几种IO的公用头文件)

<span style="font-size:14px;">#ifndef _HEAD_H_
#define _HEAD_H_ 

#include <stdio.h>
#include <sys/types.h>	       /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include<errno.h>
#include<error.h>
#define BUFF_SIZE 128
#define SER_IP  "127.0.0.1"
#define SER_PORT 50001

#define ECHO_PORT 7 

#endif </span>

非阻塞的server.c

<span style="font-size:14px;">#include "head.h"
int main(int argc, const char *argv[])
{
	int sockfd,n ;
	char buf[BUFF_SIZE];
	struct  sockaddr_in  peer_addr;
	socklen_t addrlen = sizeof(peer_addr);
	if(-1 == (sockfd = socket(AF_INET,SOCK_DGRAM,0)))
	{
		perror("socket");
		return -1;
	}

	//填充服务端地址结构体
	struct sockaddr_in ser_addr;
	bzero(&ser_addr,sizeof(ser_addr)); 

	ser_addr.sin_family = AF_INET;
	ser_addr.sin_port =  htons(SER_PORT);
	ser_addr.sin_addr.s_addr = inet_addr(SER_IP);

	//必须绑定
	if(-1 == bind(sockfd, (struct sockaddr *)&ser_addr,sizeof(ser_addr)) )
	{
		perror("bind");
		return -1;
	}

	//1. 获取文件状态标志位
	int flag = fcntl(0,F_GETFL);
	printf("set before,stdin flag = %d\n ",flag);

	int flag1=fcntl(3,F_GETFL);
	printf("set before,socket flag1=%d\n",flag1);
	//2.添加非阻塞特性,首先获取器描述符的属性,然后进行或运算,这样不会覆盖以前的描述符属性
	fcntl(0,F_SETFL, flag | O_NONBLOCK);//将标准输入IO(stdin)设置为非阻塞状态 ,
  fcntl(3,F_SETFL,flag1 | O_NONBLOCK);//将套接字设置为非阻塞状态, 也可以写成fcntl(sockfd,F_SETFL,flag1 | O_NONBLOCK);
  //为什么是3呢?  因为0是标准输入 stdin  1是标准输出stdout  2是errno
  //这些在默认的情况下,系统是将其打开的,套接字自然就是3了,如果继续打开一个IO,其值是4,以此类推

	flag = fcntl(0,F_GETFL);
	flag1=fcntl(3,F_GETFL);
	printf("set after,stdin flag = %#o \n ",flag);
	printf("set after,socket flag1=%#o \n",flag1);

     int num;
	//非阻塞:接收客户端数据并且还要读取当前终端的输入
	//实现终端输入和接受数据并发执行
	while(1)
	{
	   if(NULL!=fgets(buf,BUFF_SIZE,stdin))
	   {
	    	printf("buf = %s\n",buf);
	   }

		if( -1 == (n = recvfrom(sockfd,buf,BUFF_SIZE,0,(struct sockaddr *)&peer_addr,&addrlen)))
		{
		  if(errno!=EAGAIN)
		  {
			  perror("recvfrom");
		    	return -1;
		  }
		}
		if(n>0)
		printf("peer_addr IP :  %s port: %d \n",inet_ntoa(peer_addr.sin_addr),ntohs(peer_addr.sin_port));
	}
	return 0;
}
</span>

信号驱动IO的server.c

#include "head.h"
int sockfd,n ;
struct  sockaddr_in  peer_addr;
socklen_t addrlen = sizeof(peer_addr);

void handler(int signum)
{
	char buf[BUFF_SIZE];
	bzero(buf,sizeof(buf));

		if( -1 == (n = recvfrom(sockfd,buf,BUFF_SIZE,0,(struct sockaddr *)&peer_addr,&addrlen)))
		{
            perror("recvfrom ");
					  return ;
		}

		printf("------%s-------\n",buf);
}

int main(int argc, const char *argv[])
{
	char buf[BUFF_SIZE];	

	if(-1 == (sockfd = socket(AF_INET,SOCK_DGRAM,0)))
	{
		perror("socket");
		return -1;
	}

	//填充服务端地址结构体
	struct sockaddr_in ser_addr;
	bzero(&ser_addr,sizeof(ser_addr)); 

	ser_addr.sin_family = AF_INET;
	ser_addr.sin_port =  htons(SER_PORT);
	ser_addr.sin_addr.s_addr = inet_addr(SER_IP);

	//必须绑定 (在tcp或udp中,服务器端必须调用bind,进行和IP地址的绑定)
	if(-1 == bind(sockfd, (struct sockaddr *)&ser_addr,sizeof(ser_addr)) )
	{
		perror("bind");
		return -1;
	}

  int flag=	fcntl(sockfd,F_GETFL);
  fcntl(sockfd,F_SETFL,flag|O_ASYNC);  //设置套接字允许异步接收

  fcntl(sockfd,F_SETOWN,getpid());   //将该信号和本进程绑定,即只允许该进程来相应对应的信号
  /*
  在 fcntl(sockfd,F_SETOWN,getpid()); 中指定sockfd的原因?
  假如在本程序中,有两个套接字,到底由哪个进行和本进程一起与外部程序进行通信呢?所以要指定sockfd
  */

	signal(SIGIO,handler);//注册信号处理函数

	while(1)
	{
	   if(NULL!=fgets(buf,BUFF_SIZE,stdin))
	   {
	    	printf("buf = %s\n",buf);
	   }
	}
	return 0;
}

多路复用IO的server.c

#include "head.h"
/*
多路复用IO,类似与火车站
其中火车站的多个进站口就是检测表
有动车可以就绪,有普通列车可以就绪
站长在站口进行监听,查看,然后让已经到达的火车视为就绪状态(就绪出发)
不出站的火车永远都是就绪状态,因此要判断哪个火车到达后,就视为就绪,然后及时让其出站。

在这里,检测表就是火车站的多个站口。各种火车类型就是不同的描述符类型
列车长得监听和查看就是select
火车的进站就是描述符就绪
火车出站就是对应的描述符数据被取走
*/
int main(int argc, const char *argv[])
{
	int sockfd,n ;
	char buf[BUFF_SIZE];

	struct  sockaddr_in  peer_addr;
	socklen_t addrlen = sizeof(peer_addr);

	if(-1 == (sockfd = socket(AF_INET,SOCK_DGRAM,0)))
	{
		perror("socket");
		return -1;
	}

	//填充服务端地址结构体
	struct sockaddr_in ser_addr;
	bzero(&ser_addr,sizeof(ser_addr)); 

	ser_addr.sin_family = AF_INET;
	ser_addr.sin_port =  htons(SER_PORT);
	ser_addr.sin_addr.s_addr = inet_addr(SER_IP);

	//必须绑定
	if(-1 == bind(sockfd, (struct sockaddr *)&ser_addr,sizeof(ser_addr)) )
	{
		perror("bind");
		return -1;
	}

     /*
	  *多路复用服务器的创建步骤:
	  *1建立检测表
	  *2将需要监听的描述符加入该表
	  *3监听该表
	  *4判断该表中的就绪描述符,进行对应的读数据
      *
	  * */

	// 建立检测表
       fd_set readfds;
	   bzero(&readfds,sizeof(readfds));
   //将描述符加入该表
    FD_SET(0,&readfds);
	  FD_SET(sockfd,&readfds);//此时检测表的相应位被置1

	  //sleep(10);  可以在此加入睡眠,让多个描述符同时输入就绪状态,查看t会发生变化,相应就绪的会被置为1

	  int t=select(sockfd+1,&readfds,NULL,NULL,NULL); //select之后,只有就绪的相应为设置为1,其他没有就绪的描述符新、相应位为0了

	  printf("t=%d------------------\n",t);

  char rbuf[BUFF_SIZE];
	while(1)
	{
          bzero(rbuf,sizeof(rbuf));
         printf("stdin FD_ISSET()===%d\n",FD_ISSET(0,&readfds));
         printf("sockfd FD_ISSET()===%d\n",FD_ISSET(sockfd,&readfds));
		 if(FD_ISSET(0,&readfds))
		 fgets(rbuf,sizeof(rbuf),stdin);
	}

	return 0;
}

未经允许,禁止转载

网络IO模型,布布扣,bubuko.com

时间: 2024-12-26 18:04:20

网络IO模型的相关文章

linux网络IO模型——阻塞、非阻塞和同步、异步

最近几天在学习nginx的时候了解了一下linux网络IO模型,在此谈谈我自己的理解,如有错误请多多指教.本文参考书籍Richard Stevens的“UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking ”,6.2节“I/O Models ”. Linux网络IO请求数据分为两段: 1.数据准备 2.将数据从内核拷贝到进程空间 其实,阻塞.非阻塞和同步.异步的不同就在于这两个阶段的不同. 同步和异步关

python学习四十三天(网络IO模型)

阅读目录 一 IO模型介绍 二 阻塞IO(blocking IO) 三 非阻塞IO(non-blocking IO) 四 多路复用IO(IO multiplexing) 五 异步IO(Asynchronous I/O) 六 IO模型比较分析 七 selectors模块 IO模型介绍 为了更好地了解IO模型,我们需要事先回顾下:同步.异步.阻塞.非阻塞 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)I

五种网络IO模型以及多路复用IO中select/epoll对比

下面都是以网络读数据为例 [2阶段网络IO] 第一阶段:等待数据 wait for data 第二阶段:从内核复制数据到用户 copy data from kernel to user 下面是5种网络IO模型 [阻塞blocking IO] 两阶段全程阻塞 recvfrom -> [syscall -> wait -> copy ->] return OK [非阻塞nonblocking IO] 第一阶段是非阻塞的不断检查是否数据准备好,第二阶段阻塞读取数据 recvfrom -&

五种网络IO模型

一:概述 在网络编程中,阻塞.非阻塞.同步.异步经常被提到,下面谈一下I/O在生活中的钓鱼场景(纯属虚构,如有雷同,纯属巧合). 1.阻塞式I/O:开始钓鱼,眼睛一直盯着,鱼儿上钩拉杆. 2.非阻塞式I/O:开始钓鱼,你一直怀疑鱼儿在偷吃鱼饵,一直拉杆,没有鱼,然后重复放杆拉杆,直到有钓上鱼. 3.I/O复用:开始钓鱼,但是你同时放了多条鱼竿,然后开始眼睛不断查看多条鱼竿,直到叼上鱼. 4.信号驱动:开始钓鱼,但是你的鱼竿很特殊,鱼儿上钩会发出声音通知你,你可以同时干别的事. 5.异步I/O:姜

通过实例理解Java网络IO模型

网络IO模型及分类 网络IO模型是一个经常被提到的问题,不同的书或者博客说法可能都不一样,所以没必要死抠字眼,关键在于理解. Socket连接 不管是什么模型,所使用的socket连接都是一样的.以下是一个典型的应用服务器上的连接情况.客户的各种设备通过Http协议与Tomcat进程交互,Tomcat需要访问Redis服务器,它与Redis服务器也建了好几个连接.虽然客户端与Tomcat建的是短连接,很快就会断开,Tomcat与Redis是长连接,但是它们本质上都是一样的.建立一个Socket后

5种网络IO模型(有图,很清楚)

同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?这个问题其实不同的人给出的答案都可能不同,比如wiki,就认为asynchronous IO和non-blocking IO是一个东西.这其实是因为不同的人的知识背景不同,并且在讨论这个问题的时候上下文(context)也不相同.所以,为了更好的回答这个问题,我先限定一下本文的上下文. 本文讨论的背景是Linux环境下的n

Linux 环境下 网络IO模型

本文讨论的背景是Linux环境下的network IO. IO发生时涉及的对象和步骤: 对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel).当一个read操作发生时,它会等待内核经历两个阶段: 1  内核数据准备 (Waiting for the data to be ready) 2  内核把数据从内核空间,拷贝到用户空间中 (Copying the data from

[转载] 网络IO模型

转载自http://blog.csdn.net/zhoudaxia/article/details/8974779 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?这个问题其实不同的人给出的答案都可能不同,比如wiki,就认为asynchronous IO和non-blocking IO是一个东西.这其实是因为不同的人的知识背景不同,并且在讨论这个问题的时候上下文(co

Java 网络 IO 模型

在进入主题之前先看个 Java 网络编程的一个简单例子:代码很简单,客户端和服务端进行通信,对于客户端的每次输入,服务端回复 get.注意,服务端可以同时允许多个客户端连接. 服务端端代码: // 创建服务端 socket ServerSocket serverSocket = new ServerSocket(20000); client = serverSocket.accept(); // 客户端连接成功,输出提示 System.out.println("客户端连接成功"); /