阻塞I/O(Blocking I/O):
- 在阻塞I/O模式下,当程序执行一个I/O操作(如读取文件、网络通信等)时,它会等待直到该操作完成才会继续执行下一步操作。
- 在等待期间,程序的执行会被暂停,无法执行其他任务,直到I/O操作完成或者发生超时。
- 阻塞I/O通常较容易使用和理解,但可能会导致应用程序在高负载或大量I/O操作的情况下性能下降,因为大部分时间都在等待I/O操作完
代码
服务端代码 server.cpp
| #include <iostream> |
| #include <cstring> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <unistd.h> |
| |
| int main() { |
| |
| int serverSocket = socket(AF_INET, SOCK_STREAM, 0); |
| |
| if (serverSocket == -1) { |
| std::cerr << "无法创建套接字" << std::endl; |
| return 1; |
| } |
| |
| |
| sockaddr_in serverAddress; |
| serverAddress.sin_family = AF_INET; |
| serverAddress.sin_addr.s_addr = INADDR_ANY; |
| serverAddress.sin_port = htons(8080); |
| |
| if (bind(serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == -1) { |
| std::cerr << "绑定失败" << std::endl; |
| return 1; |
| } |
| |
| |
| if (listen(serverSocket, 5) == -1) { |
| std::cerr << "监听失败" << std::endl; |
| return 1; |
| } |
| |
| std::cout << "服务器正在监听端口 8080..." << std::endl; |
| |
| while (true) { |
| |
| sockaddr_in clientAddress; |
| socklen_t clientAddressSize = sizeof(clientAddress); |
| int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &clientAddressSize); |
| |
| if (clientSocket == -1) { |
| std::cerr << "无法接受客户端连接" << std::endl; |
| return 1; |
| } |
| |
| |
| char buffer[1024]; |
| std::memset(buffer, 0, sizeof(buffer)); |
| int bytesRead = read(clientSocket, buffer, sizeof(buffer)); |
| |
| if (bytesRead == -1) { |
| std::cerr << "读取数据失败" << std::endl; |
| return 1; |
| } |
| |
| std::cout << "收到客户端消息: " << buffer << std::endl; |
| |
| |
| close(clientSocket); |
| } |
| |
| |
| close(serverSocket); |
| |
| return 0; |
| } |
| |
客户端代码 client.cpp
| #include <iostream> |
| #include <cstring> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <unistd.h> |
| #include <arpa/inet.h> |
| |
| int main() { |
| |
| int clientSocket = socket(AF_INET, SOCK_STREAM, 0); |
| |
| if (clientSocket == -1) { |
| std::cerr << "无法创建套接字" << std::endl; |
| return 1; |
| } |
| |
| |
| sockaddr_in serverAddress; |
| serverAddress.sin_family = AF_INET; |
| serverAddress.sin_port = htons(8080); |
| serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1"); |
| |
| if (connect(clientSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == -1) { |
| std::cerr << "无法连接到服务器" << std::endl; |
| return 1; |
| } |
| |
| |
| const char* message = "Hello, Server!"; |
| int bytesSent = send(clientSocket, message, strlen(message), 0); |
| |
| if (bytesSent == -1) { |
| std::cerr << "发送消息失败" << std::endl; |
| return 1; |
| } |
| |
| |
| char buffer[1024]; |
| std::memset(buffer, 0, sizeof(buffer)); |
| int bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0); |
| |
| if (bytesRead == -1) { |
| std::cerr << "接收服务器响应失败" << std::endl; |
| return 1; |
| } |
| |
| std::cout << "服务器响应: " << buffer << std::endl; |
| |
| |
| close(clientSocket); |
| |
| return 0; |
| } |
| |
非阻塞I/O(Non-blocking I/O):
- 在非阻塞I/O模式下,程序会立即返回从I/O操作中获取的结果,而不会等待操作完成。
- 如果I/O操作尚未完成,程序不会被阻塞,而是可以继续执行其他任务,然后定期轮询或使用回调等方式来检查I/O操作是否已完
- 非阻塞I/O通常用于构建高并发的应用程序,因为它允许程序同时处理多个I/O操作,提高了应用程序的性能。
阻塞I/O适用于简单的、低并发的应用程序,而非阻塞I/O更适合需要高并发处理或对I/O操作的响应时间要求很高的应用程序。在实际应用中,通常会使用非阻塞I/O的方式来构建多线程、多进程或异步编程模型,以充分利用计算资源并提高应用程序的性能
代码
服务端代码 server.cpp
| #include <iostream> |
| #include <cstring> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <arpa/inet.h> |
| |
| int main() { |
| |
| int serverSocket = socket(AF_INET, SOCK_STREAM, 0); |
| |
| if (serverSocket == -1) { |
| std::cerr << "无法创建套接字" << std::endl; |
| return 1; |
| } |
| |
| |
| int flags = fcntl(serverSocket, F_GETFL, 0); |
| fcntl(serverSocket, F_SETFL, flags | O_NONBLOCK); |
| |
| |
| sockaddr_in serverAddress; |
| serverAddress.sin_family = AF_INET; |
| serverAddress.sin_addr.s_addr = INADDR_ANY; |
| serverAddress.sin_port = htons(8080); |
| |
| if (bind(serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == -1) { |
| std::cerr << "绑定失败" << std::endl; |
| return 1; |
| } |
| |
| |
| if (listen(serverSocket, 5) == -1) { |
| std::cerr << "监听失败" << std::endl; |
| return 1; |
| } |
| |
| std::cout << "服务器正在监听端口 8080..." << std::endl; |
| |
| while (true) { |
| |
| sockaddr_in clientAddress; |
| socklen_t clientAddressSize = sizeof(clientAddress); |
| int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &clientAddressSize); |
| |
| if (clientSocket == -1) { |
| if (errno == EAGAIN || errno == EWOULDBLOCK) { |
| |
| continue; |
| } else { |
| std::cerr << "无法接受客户端连接" << std::endl; |
| return 1; |
| } |
| } |
| |
| |
| char buffer[1024]; |
| std::memset(buffer, 0, sizeof(buffer)); |
| int bytesRead = read(clientSocket, buffer, sizeof(buffer)); |
| |
| if (bytesRead == -1) { |
| if (errno == EAGAIN || errno == EWOULDBLOCK) { |
| |
| close(clientSocket); |
| continue; |
| } else { |
| std::cerr << "读取数据失败" << std::endl; |
| return 1; |
| } |
| } else if (bytesRead == 0) { |
| |
| close(clientSocket); |
| continue; |
| } |
| |
| std::cout << "收到客户端消息: " << buffer << std::endl; |
| |
| |
| close(clientSocket); |
| } |
| |
| |
| close(serverSocket); |
| |
| return 0; |
| } |
| |
| |
客户端代码 client.cpp
| #include <iostream> |
| #include <cstring> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <unistd.h> |
| #include <arpa/inet.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| |
| int main() { |
| |
| int clientSocket = socket(AF_INET, SOCK_STREAM, 0); |
| |
| if (clientSocket == -1) { |
| std::cerr << "无法创建套接字" << std::endl; |
| return 1; |
| } |
| |
| |
| int flags = fcntl(clientSocket, F_GETFL, 0); |
| fcntl(clientSocket, F_SETFL, flags | O_NONBLOCK); |
| |
| |
| sockaddr_in serverAddress; |
| serverAddress.sin_family = AF_INET; |
| serverAddress.sin_port = htons(8080); |
| serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1"); |
| |
| if (connect(clientSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == -1) { |
| if (errno != EINPROGRESS) { |
| std::cerr << "无法连接到服务器" << std::endl; |
| return 1; |
| } |
| } |
| |
| |
| fd_set writeSet; |
| FD_ZERO(&writeSet); |
| FD_SET(clientSocket, &writeSet); |
| |
| struct timeval timeout; |
| timeout.tv_sec = 5; |
| timeout.tv_usec = 0; |
| |
| int selectResult = select(clientSocket + 1, nullptr, &writeSet, nullptr, &timeout); |
| |
| if (selectResult == -1) { |
| std::cerr << "select调用失败" << std::endl; |
| return 1; |
| } else if (selectResult == 0) { |
| std::cerr << "连接超时" << std::endl; |
| return 1; |
| } |
| |
| |
| const char* message = "Hello, Server!"; |
| int bytesSent = send(clientSocket, message, strlen(message), 0); |
| |
| if (bytesSent == -1) { |
| if (errno == EAGAIN || errno == EWOULDBLOCK) { |
| |
| close(clientSocket); |
| return 1; |
| } else { |
| std::cerr << "发送消息失败" << std::endl; |
| return 1; |
| } |
| } |
| |
| |
| close(clientSocket); |
| |
| return 0; |
| } |
| |
用一个形象的例子解释阻塞IO/非阻塞IO
- 阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程
- 你想要在书店买一本书,你通过打电话问书店老板有没有《C++ primer》这本书,如果是阻塞式调用,你会一直将自己“挂起”,一直等待电话对面老板返回给你结果,直到得到这本书有没有的结果,如果是非阻塞式调用,不管老板有没有返回你结果,你都会自己先一边去玩了,然后你会每过一段时间再去检查一下老板有没有返回你结果。在这里阻塞与非阻塞与是否和同步异步无关,跟老板通过什么方式返回你结果也跟无关
同步I/O(Blocking I/O):
- 阻塞模式:在同步I/O中,程序会阻塞等待I/O操作完成。如果没有数据可读或写入,程序将一直等待,无法执行其他任务。
- 调用会阻塞:当执行读取或写入操作时,函数调用会一直阻塞,直到操作完成为止。这意味着程序需要等待操作完成后才能继续执行后续代码。
- 简单易用:同步I/O通常更容易理解和使用,因为它的执行顺序更直观
- 适用性:同步I/O适用于许多应用程序,特别是对于简单的、低并发的任务,例如文件读写和网络通信,但在高并发环境中可能导致性能问题
代码
服务端代码 server.cpp
| #include <iostream> |
| #include <cstring> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <unistd.h> |
| |
| int main() { |
| |
| int serverSocket = socket(AF_INET, SOCK_STREAM, 0); |
| |
| if (serverSocket == -1) { |
| std::cerr << "无法创建套接字" << std::endl; |
| return 1; |
| } |
| |
| |
| sockaddr_in serverAddress; |
| serverAddress.sin_family = AF_INET; |
| serverAddress.sin_addr.s_addr = INADDR_ANY; |
| serverAddress.sin_port = htons(8080); |
| |
| if (bind(serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == -1) { |
| std::cerr << "绑定失败" << std::endl; |
| return 1; |
| } |
| |
| |
| if (listen(serverSocket, 5) == -1) { |
| std::cerr << "监听失败" << std::endl; |
| return 1; |
| } |
| |
| std::cout << "服务器正在监听端口 8080..." << std::endl; |
| |
| while (true) { |
| |
| sockaddr_in clientAddress; |
| socklen_t clientAddressSize = sizeof(clientAddress); |
| int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &clientAddressSize); |
| |
| if (clientSocket == -1) { |
| std::cerr << "无法接受客户端连接" << std::endl; |
| return 1; |
| } |
| |
| |
| char buffer[1024]; |
| std::memset(buffer, 0, sizeof(buffer)); |
| int bytesRead = read(clientSocket, buffer, sizeof(buffer)); |
| |
| if (bytesRead == -1) { |
| std::cerr << "读取数据失败" << std::endl; |
| return 1; |
| } else if (bytesRead == 0) { |
| |
| close(clientSocket); |
| continue; |
| } |
| |
| std::cout << "收到客户端消息: " << buffer << std::endl; |
| |
| |
| const char* response = "Hello, Client!"; |
| int bytesSent = write(clientSocket, response, strlen(response)); |
| |
| if (bytesSent == -1) { |
| std::cerr << "发送响应失败" << std::endl; |
| return 1; |
| } |
| |
| |
| close(clientSocket); |
| } |
| |
| |
| close(serverSocket); |
| |
| return 0; |
| } |
| |
客户端代码 client.cpp
| #include <iostream> |
| #include <cstring> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <unistd.h> |
| #include <arpa/inet.h> |
| |
| int main() { |
| |
| int clientSocket = socket(AF_INET, SOCK_STREAM, 0); |
| |
| if (clientSocket == -1) { |
| std::cerr << "无法创建套接字" << std::endl; |
| return 1; |
| } |
| |
| |
| sockaddr_in serverAddress; |
| serverAddress.sin_family = AF_INET; |
| serverAddress.sin_port = htons(8080); |
| serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1"); |
| |
| if (connect(clientSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == -1) { |
| std::cerr << "无法连接到服务器" << std::endl; |
| return 1; |
| } |
| |
| |
| const char* message = "Hello, Server!"; |
| int bytesSent = write(clientSocket, message, strlen(message)); |
| |
| if (bytesSent == -1) { |
| std::cerr << "发送消息失败" << std::endl; |
| return 1; |
| } |
| |
| |
| char buffer[1024]; |
| std::memset(buffer, 0, sizeof(buffer)); |
| int bytesRead = read(clientSocket, buffer, sizeof(buffer)); |
| |
| if (bytesRead == -1) { |
| std::cerr << "接收服务器响应失败" << std::endl; |
| return 1; |
| } |
| |
| std::cout << "服务器响应: " << buffer << std::endl; |
| |
| |
| close(clientSocket); |
| |
| return 0; |
| } |
| |
异步I/O(Non-blocking I/O):
- 非阻塞模式:在异步I/O中,程序不会等待I/O操作完成,而是可以继续执行其他任务。当I/O操作完成时,程序会得到通知
- 调用不会阻塞:异步I/O允许发起一个I/O操作后立即返回,而不会阻塞等待操作完成。程序可以继续执行其他任务,直到I/O操作完成并触发回调或通知
- 复杂性:异步I/O通常更复杂,因为它需要处理回调函数或事件通知机制,以及维护异步操作的状态
- 适用性:异步I/O适用于高并发的应用程序,例如网络服务器、实时多媒体处理和大规模数据处理,因为它可以提高系统的吞吐量和响应性
代码
- 在C++中实现异步I/O模式通常需要使用库或框架,例如
Boost.Asio
或使用C++11引入的std::async
和std::future
。以下是一个简单的异步I/O示例,使用std::async
和std::future
来实现服务器端和客户端的异步通信。
服务端代码 server.cpp
| #include <iostream> |
| #include <cstring> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <unistd.h> |
| #include <future> |
| #include <thread> |
| |
| void handleClient(int clientSocket) { |
| |
| char buffer[1024]; |
| std::memset(buffer, 0, sizeof(buffer)); |
| int bytesRead = read(clientSocket, buffer, sizeof(buffer)); |
| |
| if (bytesRead == -1) { |
| std::cerr << "读取数据失败" << std::endl; |
| } else if (bytesRead == 0) { |
| |
| close(clientSocket); |
| } else { |
| std::cout << "收到客户端消息: " << buffer << std::endl; |
| |
| |
| const char* response = "Hello, Client!"; |
| int bytesSent = write(clientSocket, response, strlen(response)); |
| |
| if (bytesSent == -1) { |
| std::cerr << "发送响应失败" << std::endl; |
| } |
| |
| |
| close(clientSocket); |
| } |
| } |
| |
| int main() { |
| |
| int serverSocket = socket(AF_INET, SOCK_STREAM, 0); |
| |
| if (serverSocket == -1) { |
| std::cerr << "无法创建套接字" << std::endl; |
| return 1; |
| } |
| |
| |
| sockaddr_in serverAddress; |
| serverAddress.sin_family = AF_INET; |
| serverAddress.sin_addr.s_addr = INADDR_ANY; |
| serverAddress.sin_port = htons(8080); |
| |
| if (bind(serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == -1) { |
| std::cerr << "绑定失败" << std::endl; |
| return 1; |
| } |
| |
| |
| if (listen(serverSocket, 5) == -1) { |
| std::cerr << "监听失败" << std::endl; |
| return 1; |
| } |
| |
| std::cout << "服务器正在监听端口 8080..." << std::endl; |
| |
| while (true) { |
| |
| sockaddr_in clientAddress; |
| socklen_t clientAddressSize = sizeof(clientAddress); |
| int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &clientAddressSize); |
| |
| if (clientSocket == -1) { |
| std::cerr << "无法接受客户端连接" << std::endl; |
| return 1; |
| } |
| |
| |
| std::future<void> future = std::async(std::launch::async, handleClient, clientSocket); |
| } |
| |
| |
| close(serverSocket); |
| |
| return 0; |
| } |
| |
客户端代码 client.cpp
| #include <iostream> |
| #include <cstring> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <unistd.h> |
| #include <arpa/inet.h> |
| #include <future> |
| |
| void sendMessage(int clientSocket) { |
| |
| const char* message = "Hello, Server!"; |
| int bytesSent = write(clientSocket, message, strlen(message)); |
| |
| if (bytesSent == -1) { |
| std::cerr << "发送消息失败" << std::endl; |
| } |
| } |
| |
| int main() { |
| |
| int clientSocket = socket(AF_INET, SOCK_STREAM, 0); |
| |
| if (clientSocket == -1) { |
| std::cerr << "无法创建套接字" << std::endl; |
| return 1; |
| } |
| |
| |
| sockaddr_in serverAddress; |
| serverAddress.sin_family = AF_INET; |
| serverAddress.sin_port = htons(8080); |
| serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1"); |
| |
| if (connect(clientSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == -1) { |
| std::cerr << "无法连接到服务器" << std::endl; |
| return 1; |
| } |
| |
| |
| std::future<void> future = std::async(std::launch::async, sendMessage, clientSocket); |
| |
| |
| char buffer[1024]; |
| std::memset(buffer, 0, sizeof(buffer)); |
| int bytesRead = read(clientSocket, buffer, sizeof(buffer)); |
| |
| if (bytesRead == -1) { |
| std::cerr << "接收服务器响应失败" << std::endl; |
| return 1; |
| } |
| |
| std::cout << "服务器响应: " << buffer << std::endl; |
| |
| |
| close(clientSocket); |
| |
| return 0; |
| } |
| |
- 在上述示例中,服务器端使用
std::async
启动一个异步任务来处理每个客户端的连接,这允许服务器同时处理多个客户端请求。客户端也使用std::async
来发送消息给服务器。这种方式实现了异步I/O模式,不会阻塞主线程,允许并发处理多个请求。请注意,这只是一个简单示例,实际的异步编程可能涉及更复杂的任务和错误处理。
用一个形象的例子解释同步IO/异步IO
- 同步与异步同步和异步关注的是
消息通信机制 (synchronous communication/ asynchronous communication)
所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者*通过状态、通知来通知调用者,或通过回调函数处理这个调用
- 你想要在书店买一本书,你通过打电话问书店老板有没有《C++ primer》这本书, 如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调
如何区分同步IO和异步IO
- 判断的标准是IO操作是否阻塞了当前的进程或线程。阻塞就是同步,没阻塞就是异步。NIO非阻塞的意思是指调用系统函数,会立即返回结果,但是需要自己主动去读取数据,读取到数据的过程 该进程或现场依然会被阻塞,原因是:读取数据的时候其实是一个内核空间复制用户空间的过程,这个过程CPU执行权移交给内核去执行了,因此当前的程序一定是被挂起的
以上代码由GPT-3.5生成 需在Linux环境下运行,感兴趣的可以参考改代码自己手动实现
参考链接
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理