您现在的位置是:首页 >技术杂谈 >Linux 五种网络IO模式(阻塞IO、非阻塞IO、IO多路复用、信号驱动IO、异步IO)网站首页技术杂谈
Linux 五种网络IO模式(阻塞IO、非阻塞IO、IO多路复用、信号驱动IO、异步IO)
Linux网络编程中,有五种网络IO模式,分别是阻塞IO、非阻塞IO、IO多路复用、信号驱动IO、异步IO;
虽然说不能全都认识得很透彻,但至少得都知道一点!
开始之前,先了解以下同步IO和异步IO;
1. 同步IO
场景1: 小明去打开水,而开水塔此时没有水,小明在现场一直等待开水到来,或者不断的轮询查看是否有开水,直到有开水取到水为止,这是同步IO的一种案例!
同步IO的特点:
同步IO指的是用户进程触发I/O操作并等待或者轮询的去查看I/O操作是否就绪。
同步IO的执行者是IO操作的发起者。
同步IO需要发起者进行内核态到用户态的数据拷贝过程,所以这里必须阻。
2. 异步IO
场景2: 小明去打开水,而开水塔此时没有水,开水塔的阿姨叫小明把水壶放到现场,来水后会帮他打好水,并打电话叫他来取,这是异步IO的一种案例!
异步IO的特点:
异步IO是指用户进程触发I/O操作以后就立即返回,继续开始做自己的事情,而当I/O操作已经完成的时候会得到I/O完成的通知;
异步IO的执行者是内核线程,内核线程将数据从内核态拷贝到用户态,所以这里没有阻塞。
目录
一、阻塞IO
小明同学急用开水,打开水时发现开水龙头没水,他一直等待直到装满水然后离开。这一过程就可以看成是使用了阻塞IO模型,因为如果水龙头没有水,他也要等到有水并装满杯子才能离开去做别的事情。很显然,这种IO模型是同步的。
在linux 中,默认情况下所有的socket都是blocking IO(阻塞IO), 一个典型的读操作流程:
二、非阻塞IO
小明同学又一次急用开水,打开水龙头后发现没有水,因为还有其它急事他马上离开了,过一会他又拿着杯子来看看……在中间离开的这些时间里,小明同学离开了装水现场(回到用户进程空间),可以做他自己的事情。这就是非阻塞IO模型。但是它只有是检查无数据的时候是非阻塞的,在数据到达的时候依然要等待复制数据到用户空间(等着水将水杯装满),因此它还是同步IO。
当用户线程发起一个read操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。
所以事实上,在非阻塞IO模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU。
典型的非阻塞IO模型一般如下:
设置非阻塞常用方式:
方式一:创建socket 时指定
// SOCK_NONBLOCK: 非阻塞IO
server_sockfd = socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
方式二:在读取数据前通过如下方式设定
fcntl(server_sockfd, F_SETFL, fcntl(server_sockfd, F_GETFL, 0) | O_NONBLOCK);
需要包含头文件 #include <fcntl.h> // fcntl
在读取数据接口中,如果还没有数据可以读取,则会立刻返回EAGAIN 和 EWOULDBLOCK;
所以只需要判断返回值即可,如:
int recv_len = recvfrom(server_sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&client_addr, &client_addr_len);
if (recv_len < 0) {
if (EAGAIN == errno || EWOULDBLOCK == errno) {
printf("EAGAIN = %d EWOULDBLOCK = %d errno = %d
", EAGAIN, EWOULDBLOCK, errno);
printf("do something...
");
sleep(2);
continue;
}
perror("recvfrom");
exit(errno);
}
do something... 即可以去做其它事情,做完后再回来看看是否有数据可以读取了!
设置端口复用
int opt = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
三、IO多路复用
有一天,学校里面优化了热水的供应,增加了很多水龙头,这个时候小明同学再去装水,舍管阿姨告诉他这些水龙头都还没有水,你可以去忙别的了,等有水了告诉他。于是等啊等(select调用中),过了一会阿姨告诉他有水了。
这里有两种情况:
情况1: 阿姨只告诉来水了,但没有告诉小明是哪个水龙头来水了,要自己一个一个去尝试。(select/poll 场景);
即1000个socket,其中一个socket有消息来了,阿姨就会通知,让我们一个一个 的取遍历,找到有数据的那个socket,然后读取数据;
情况2: 舍管阿姨会告诉小明同学哪几个水龙头有水了,小明同学不需要一个个打开看(epoll 场景);
即1000个socket,其中一个socket有消息来了,阿姨就会通知到具体某一个socket来信息了,不需要我们一个一个的去遍历,效率一下子就高起来了;
当用户进程调用了select,那么整个进程就会被block(阻塞),而同时,kernel会 “监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel(内核)拷贝到用户进程。
所以,IO多路复用的特点是通过一种机制,一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入就绪状态,select()函数就可以返回。
这里需要使用两个system call(select 和 recvfrom),而blocking IO(阻塞IO)只调用了一个system call(recvfrom)。但是,用select的优势在于它可以同时处理多个connection。
如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用mutil-threading + blocking IO(多线程 + 阻塞IO)的web server性能更好,可能延迟还更大。select/epoll 的优势并不是对于单个连接能处理得更好,而是在于能同时处理更多的连接。
1. 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);
描述:监听多个(最多1024)socket;
参数
nfds
最大的文件描述符加1,使用FD_SETSIZE即可;
readfds
用于监听可读read;不关心则使用:(fd_set *)0;
writefds
用于监听可写write;不关心则使用:(fd_set *)0;
exceptfds
用于监听异常的数据;不关心则使用:(fd_set *)0;
timeout
一个指向timeval结构的指针,用于决定select等待I/o的最长时间;如果为空将一直等待;
返回值
大于0:是已就绪的文件句柄的总数;
等于0:超时;
小于0:表示出错,错误: errno;
void FD_CLR(int fd, fd_set *set); // 一个 fd_set类型变量的所有位都设为 0
int FD_ISSET(int fd, fd_set *set); // 清除某个位时可以使用
void FD_SET(int fd, fd_set *set); // 设置变量的某个位置位
void FD_ZERO(fd_set *set); // 测试某个位是否被置位
例:
select_server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#define BUFF_MAX 1024
// 英文小写转换成大写
static void str2up(char *str) {
while (*str) {
if (*str >= 'a' && *str <= 'z') {
*str = *str - 'a' + 'A';
}
str++;
}
}
int main(void) {
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_in server_address;
struct sockaddr_in client_address;
int result = 0;
fd_set readfds, writefds, rfds, wfds;
char buff[BUFF_MAX] = { '