IO模型:非阻塞式IO模型

213 阅读2分钟

非阻塞式IO模型

非阻塞IO模型图

image.png

非阻塞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 则相反,如果在读取或写入数据的过程中,如果没有数据可读或写,程序将不会一直阻塞,而是立刻返回一个错误码(EAGAIN1^1),表示当前读或写不可完成。

运行截图

image.png

image.png

注释

  1. 在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK