非阻塞式IO模型
非阻塞IO模型图
非阻塞IO模型使用案例
下面是一个利用非阻塞IO模型实现的简易http服务器
代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
#define PORT 8080
#define BUFFERSIZE 4089
int set_non_blocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
int main() {
int listenfd, connfd;
struct sockaddr_in serv_addr, client_addr;
char buffer[BUFFERSIZE];
socklen_t addr_size;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (set_non_blocking(listenfd) == -1) {
perror("设置非阻塞失败");
exit(EXIT_FAILURE);
}
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(PORT);
if (bind(listenfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) == -1) {
perror("绑定失败");
exit(EXIT_FAILURE);
}
if (listen(listenfd, SOMAXCONN) == -1) {
perror("监听失败");
exit(EXIT_FAILURE);
}
addr_size = sizeof(client_addr);
printf("服务器启动, 监听端口 %d\n", PORT);
while (1) {
connfd = accept(listenfd, (struct sockaddr *) &client_addr, &addr_size);
if (connfd < 0) {
if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {
continue;
}
perror("accept失败");
break;
}
if (set_non_blocking(connfd) == -1) {
perror("设置非阻塞失败");
break;
}
int port = ntohs(client_addr.sin_port);
struct in_addr in = client_addr.sin_addr;
char str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &in, str, sizeof(str));
printf("客户端信息: ip:port = %s:%d\n", str, port);
ssize_t n;
do {
n = read(connfd, buffer, BUFFERSIZE);
if (n < 0) {
if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {
printf("read重试\n");
continue;
} else {
perror("读取错误");
break;
}
}
buffer[n] = '\0';
printf("收到的请求信息:\n%s", buffer);
} while (n == BUFFERSIZE || n < 0);
printf("\n");
char *response = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/html;charset=utf-8\r\n"
"\r\n"
"<html><body><h1>Hello, World!</h1></body></html>\r\n";
ssize_t r;
ssize_t left = (ssize_t) strlen(response);
while (left) {
r = write(connfd, response, strlen(response));
if (r < 0 && (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)) {
printf("write重试\n");
continue;
}
if (r == 0) {
break;
}
left -= r;
response += r;
}
close(connfd);
}
close(listenfd);
return 0;
}
代码逻辑
代码中,实现了和上一篇中基于阻塞IO模型实现的http服务器一样的服务器,和之前的阻塞IO的实现方式有所不同,这次使用了非阻塞 I/O。首先,创建了一个监听套接字,使用
set_non_blocking函数将其设置为非阻塞模式。(注:set_non_blocking函数用于将文件描述符设置为非阻塞,通过调用fcntl函数获取文件描述符的标志并将O_NONBLOCK设置为该标志的一部分。);在主循环中调用accept函数,接受客户端连接请求。如果接收不到客户端请求,则继续等待。如果接收到请求,则将连接套接字也设置为非阻塞模式。
阻塞 I/O意味着在读取或写入数据的过程中,如果没有数据可读或写,程序将会一直阻塞,直到有数据可读或写。
非阻塞 I/O则相反,如果在读取或写入数据的过程中,如果没有数据可读或写,程序将不会一直阻塞,而是立刻返回一个错误码(EAGAIN),表示当前读或写不可完成。
运行截图
注释
- 在VxWorks和Windows上,
EAGAIN的名字叫做EWOULDBLOCK