文章目录
前言
一、同步异步与阻塞非阻塞
1、阻塞和非阻塞
阻塞(Blocking):在阻塞操作中,如果数据还没有准备好(例如,等待数据从磁盘读取或从网络接收),则调用者(通常是一个线程或进程)会被阻塞,直到数据准备好为止。在此期间,调用者无法执行其他任务,只能等待I/O操作完成。阻塞I/O操作的典型例子是普通的文件读写。
非阻塞(Non-blocking):在非阻塞操作中,如果数据还没有准备好,调用者不会被阻塞,而是立即返回一个错误码(例如,表示资源不可用)。调用者可以继续执行其他任务,然后在适当的时间点再次尝试I/O操作。非阻塞I/O操作的典型例子是使用select,poll或epoll等I/O多路复用技术处理的网络通信。
2、异步和同步
同步和异步则是与应用程序如何处理数据这一操作相关的。如果应用程序在读取数据时需要自己复制数据从内核空间到用户空间,则是同步方式;而如果应用程序不需要自己复制数据,而是由操作系统来完成这一过程,则是异步方式。通常情况下,同步方式对性能要求较高,因为数据复制操作是由应用程序自行完成的,而异步方式可以减少应用程序的运算负担,提升程序的处理性能。
1) I/O同步:
在应用程序上调用recv函数,这个sockfd我不管它工作在阻塞模式还是非阻塞模式,真的有数据准备好了之后(TCP的接收缓冲区有数据了,就是数据可读了),我们要读这个数据,这个buf是用户层自己定义的,recv就可以开始接收了,是应用程序卡在这里recv(),从内核的TCP接收缓冲区搬数据到应用层上的buf,在搬的过程中,因为size>0,这就表示从内核搬了多少字节的数据,我们就要访问buf了,没搬完之前,不会进入到下面的if语句。搬完了,recv才返回过来,看看size是多少,就是搬了多少数据,因此I/O同步是应用程序搬的数据。
I/O同步的意思就是:当我调用网络I/O的接口,当I/O阶段1数据准备好之后,在数据读写的时候,应用层自己调用网络I/O接口自己去读写,都花在应用层上。
recv和send是同步的I/O接口
2) I/O异步
当我请求内核的时候,我比较关心sockfd上的数据,远端如果发过来数据,我需要读sockfd上的数据,我有一个buf,到时候如果有数据来了,内核能不能帮忙把数据放到buf里面,我再给内核注册一个sigio信号,也就是说,对一个操作系统级别的异步的I/O接口来说,我先塞给内核一个sockfd,表示对这个sockfd上的事件感兴趣,如果sockfd上有数据可读的话,麻烦操作系统内核把数据搬到buf里面。
内核把内核缓冲区-sockfd对应的TCP接收缓冲区的数据搬到buf里面,搬完以后,通过信号sigio给应用程序通知一下。应用程序在这期间可以玩自己的了,做任何事清都可以。
3、总结
是否同步、是否阻塞,阻塞与非阻塞的区别是读取TCP接收数据缓冲区如果没有数据是否等待,如果等待即是阻塞,不等待即是不阻塞;至于同步和异步则是如果有数据应用程序是否自己复制数据从内核空间到用户空间,如果需要自己复制数据则是同步,否则即是异步
二、IO模型
1、同步阻塞IO
用户线程发起IO请求后,立即返回;但需要不断地调用read,尝试读取socket中的数据,直到读取成功后,才继续处理接收的数据。
虽然用户线程每次发起IO请求后可以立即返回,但是为了等到数据,仍需要不断地轮询、重复请求,消耗了大量的CPU的资源。一般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性。
int server_fd, conn_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len;
// 创建套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
// 绑定地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8000);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
// 监听连接
listen(server_fd, 5);
while (true) {
// 阻塞等待客户端连接
client_len = sizeof(client_addr);
conn_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
std::cout << "connect from: " << inet_ntoa(client_addr.sin_addr) << std::endl;
char buf[1024];
// 阻塞等待客户端发送消息
int len = recv(conn_fd, buf, sizeof(buf), 0);
std::cout << "receive: " << buf << std::endl;
// 发送数据给客户端
const char *msg = "Hello, Client";
send(conn_fd, msg, strlen(msg), 0);
close(conn_fd);