网络IO管理

网络 IO,会涉及到两个系统对象。
1、用户空间调用 IO 的进程或者线程
2、内核空间的内核系统

比如发生 IO 操作 read 时,内核空间的内核系统 会经历两个阶段:

  1. 等待数据准备就绪
  2. 将数据从内核拷贝到进程或者线程中。从内核态拷贝到用户空间。

因为在以上两个阶段上各有不同的情况,所以出现了多种网络 IO 模型

5种网络IO模型

1、阻塞IO

在这里插入图片描述

在linux系统里,所有的socket默认都是阻塞的。

当用户进程调用了read,kernel开始了IO的第一个阶段: kernel没有准备好数据,而用户用户进程是被阻塞的。

当kernel一直等到数据准备好了。

第二阶段:它就会把数据从kernel拷贝到用户内存。这个阶段也是阻塞的。

拷贝完成后,kernel给用户进程返回结果,用户进程解除阻塞状态,继续运行起来。

阻塞IO的特点:在IO执行的两个阶段都被阻塞(等待数据和拷贝数据两个阶段)

listen()、send() 、recv()
这些接口都是阻塞型接口的,
不返回调用结果,当前线程一直阻塞,只有当系统调用获得结果或者超时出错才返回。

int accept(int s, struct sockaddr *addr, socklen_t *addrlen);

执行完bind()和 listen()后,操作系统已经开始在指定的端口处监听所有的连接请求。

如果有请求,则将该连接请求加入请求队列。

调用 accept()接口正是从 socket s 的请求队列抽取第一个连接信息,创建一个与 s 同类的新的 socket 返回句柄。

如果请求队列当前没有请求,则 accept() 将进入阻塞状态直到有请求进入队列。
在这里插入图片描述

2、非阻塞IO

linux下,可以通过设置socket为非阻塞。
对非阻塞的socket,执行读操作,如下图
在这里插入图片描述

当设置socket为非阻塞时,
当用户进程执行read操作时,如果kernel中没有数据就绪,那么就会立即返回-1,并且设置errno为EAGAIN。

如果kernel中,有数据就绪的话,就会阻塞用户进程,等待把kernel的数据拷贝到用户空间,然后返回。

在非阻塞状态下,recv() 接口在被调用后立即返回,返回值代表了不同的含义。

  • recv() 返回值大于 0,表示接受数据完毕,返回值即是接受到的字节数;

  • recv() 返回 0,表示连接已经正常断开;

  • recv() 返回 -1,且 errno 等于 EAGAIN,表示 recv 操作还没执行完成;

  • recv() 返回 -1,且 errno 不等于 EAGAIN,表示 recv 操作遇到系统错误 errno。

使用
fcntl( fd, F_SETFL, O_NONBLOCK );
设置为非阻塞
在这里插入图片描述
可以看到服务器线程可以通过循环调用 recv()接口,可以在单个线程内实现对所有连
接的数据接收工作。但是上述模型绝不被推荐。因为,循环调用 recv()将大幅度推高 CPU 占用率;

此外,在这个方案中 recv()更多的是起到检测“操作是否完成”的作用,实际操作系统提供了更为高效的检测“操作是否完成“作用的接口,例如 select()多路复用模式,可以一次检测多个连接是否活跃。

3、多路复用IO(IO multiplexing)

select/epoll 的好处就在于单
个 process 就可以同时处理多个网络连接的 IO。

基本原理就是 select/epoll 这个 function
会不断的轮询所负责的所有 socket,当某个 socket 有数据到达了,就通知用户进程。它

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

4、异步IO

Linux 下的 asynchronous IO 用在磁盘 IO 读写操作,不用于网络 IO
在这里插入图片描述

用户进程执行 read 操作之后,立刻就可以开始去做其它的事。

而另一方面,从 kernel的角度,当它受到一个 asynchronous read 之后,首先它会立刻返回,所以不会对用户进程产生任何 block。

然后,kernel 会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel 会给用户进程发送一个 signal,告诉它 read 操作完成了。

5.信号驱动IO

首先我们允许套接口进行信号驱动 I/O,并安装一个信号处理函数,进程继续运行并不阻
塞。

当数据准备好时,进程会收到一个 SIGIO 信号,可以在信号处理函数中调用 I/O 操作函数处理数据。

当数据报准备好读取时,内核就为该进程产生一个 SIGIO 信号。

我们随后既可以在信号处理函数中调用 read 读取数据报,并通知主循环数据已准备好待处理,也可以立即通知主循环,让它来读取数据报。

无论如何处理 SIGIO 信号,这种模型的优势在于等待数据报到达(第一阶段)期间,进程可以继续执行,不被阻塞。免去了 select 的阻塞与轮询,当有活跃套接字时,由注册的 handler 处理。
在这里插入图片描述

经过上面的介绍,会发现 non-blocking IO 和 asynchronous IO 的区别还是很明显的。在
non-blocking IO 中,虽然进程大部分时间都不会被 block,但是它仍然要求进程去主动的 check,
并且当数据准备完成以后,也需要进程主动的再次调用 recvfrom 来将数据拷贝到用户内存。



#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>


int sockfd = 0;

// 算不算异步?是异步的操作。  
// 异步的理解:检测IO是否有数据,在另外一个流程里的。读写数据
// 两个不在同一个流程里的,叫异步。两者之间的关系
// 非阻塞 io 读的时候,不阻塞
// 为什么不常用    太频繁的信号操作,会影响。 信号是从内核里 回调回来的。
// 当信号很多,io很多的时候,数据很多的时候,性能不太好。需要从内核态到用户态,可能会造成内核内存过大

void do_sigio(int sig) {

	struct sockaddr_in cli_addr;
	int clilen = sizeof(struct sockaddr_in);
	int clifd = 0;
#if 0
	clifd = accept(sockfd, (struct sockaddr*)&cli_addr, &clilen);

	char buffer[256] = {0};
	int len = read(clifd, buffer, 256);
	printf("Listen Message : %s\r\n", buffer);

	int slen = write(clifd, buffer, len);
#else

	char buffer[256] = {0};
	int len = recvfrom(sockfd, buffer, 256, 0, (struct sockaddr*)&cli_addr, (socklen_t*)&clilen);
	printf("Listen Message : %s\r\n", buffer);

	int slen = sendto(sockfd, buffer, len, 0, (struct sockaddr*)&cli_addr, clilen);
#endif
}


int main(int argc, char *argv[]) {

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);

	struct sigaction sigio_action;
	sigio_action.sa_flags = 0;
	sigio_action.sa_handler = do_sigio;
	sigaction(SIGIO, &sigio_action, NULL);


	struct sockaddr_in serv_addr;
	memset(&serv_addr, 0, sizeof(serv_addr));
	
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(9096);
	serv_addr.sin_addr.s_addr = INADDR_ANY;

	fcntl(sockfd, F_SETOWN, getpid());

	int flags = fcntl(sockfd, F_GETFL, 0);
	flags |= O_ASYNC | O_NONBLOCK;

	fcntl(sockfd, F_SETFL, flags);

	bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

	while(1) sleep(1);

	close(sockfd);

	return 0;

}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值