Linux - IO的同步与异步以及五种网络IO模式(阻塞IO、非阻塞IO、IO多路复用、信号驱动IO和异步IO)

本文详细介绍了Linux中的五种网络IO模型,包括同步IO和异步IO的概念,以及阻塞IO、非阻塞IO、IO多路复用(SELECT、POLL、Epoll)和信号驱动IO的工作原理。通过实例代码展示了各种IO模式在服务器端和客户端的应用,帮助理解不同模型的优缺点和适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、同步IO和异步IO

1. 同步IO

  • 场景:小明去打开水,而开水塔此时没有水,小明在现场一直等待开水到来,或者不断的轮询查看是否有开水,直到有开水取到水为止,这是同步IO的一种案例!
  • 特点:
  1. 同步IO是指用户进程触发I/O操作并等待或轮询的查看I/O操作是否就绪
  2. 同步IO的执行者是IO操作的发起者
  3. 同步IO需要发起者进行内核态到用户态的数据拷贝过程,所以这里必须阻塞

2. 异步IO

  • 场景:小明去打开水,而开水塔此时没有水,开水塔的阿姨叫小明把水壶放到现场,来水后会帮他打好水,并打电话叫他来取,这是异步IO的一种案例!
  • 特点:
  1. 异步IO是指用户进程触发I/O操作后立即返回,继续做自己的事,而当I/O操作已完成时会得到I/O完成的通知
  2. 异步IO的执行者时内核线程
  3. 内核线程将数据从内核态拷贝到用户态,所以没有阻塞

二、五种网络IO模式 - 概述

  1. 对于一次IO访问(以read为例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,会经历两个阶段:等待数据准备阶段和将数据从内核拷贝到用户进程阶段。
  2. linux系统产生了以下五种网络模式的方案,分别是:阻塞IO(blocking IO)、非阻塞IO(nonblocking IO)、IO多路复用(IO multiplexing)、信号驱动IO(signal driven IO,不常用)和异步IO(asynchronous IO)

三、五种网络IO模式 - 阻塞IO

1. 场景

小明同学急用开水,打开水时发现开水龙头没水,他一直等待直到装满水然后离开。这一过程就可以看成是使用了阻塞IO模型,因为如果水龙头没有水,他也要等到有水并装满杯子才能离开去做别的事情。很显然,这种IO模型是同步的。
在Linux中默认情况下所有的socket都是阻塞IO。

下图是典型的读流程

读流程

2. 示例代码

// 服务器端
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define PORT  8888

int InitServer() {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        fprintf(stderr, "socket() - failed! reason: %s\n", strerror(errno));
        return -1;
    }

    struct sockaddr_in in;
    bzero(&in, sizeof(in));
    in.sin_family = AF_INET;
    in.sin_addr.s_addr = htonl(INADDR_ANY);
    in.sin_port = htons(PORT);

    int ret = bind(sock, (struct sockaddr*)(&in), sizeof(in));
    if (ret < 0) {
        fprintf(stderr, "bind() - failed! reason: %s\n", strerror(errno));
        return -1;
    }

    ret = listen(sock, 128);
    if (ret < 0) {
        fprintf(stderr, "listen() - failed! reason: %s\n", strerror(errno));
        return -1;
    }

    return sock;
}

int main(int argc, char *argv[]) {
    int server_fd = InitServer();
    if (server_fd < 0) {
        exit(1);
    }

    char buffer[1024] = { 0 };
    while (1) {
        struct sockaddr_in client;
        int client_len = sizeof(client);
        int client_fd = accept(server_fd, (struct sockaddr*)(&client), &client_len);
        if (client_fd < 0) {
            fprintf(stderr, "accept() - failed! reason: %s\n", strerror(errno));
            continue;
        }

        bzero(buffer, sizeof(buffer));
        int len = read(client_fd, buffer, sizeof(buffer) - 1);
        if (len < 0) {
            fprintf(stderr, "read() - failed! reason: %s\n", strerror(errno));
            close(client_fd);
            continue;
        }
        else if (len == 0) {
            fprintf(stderr, "read() - failed! reason: The client closes the socket.\n");
            close(client_fd);
            continue;
        }

        printf("read success! data: %s\n", buffer);

        for (int i = 0; i < len; ++i) {
            buffer[i] = toupper(buffer[i]);
        }

        if (write(client_fd, buffer, len) < 0) {
            fprintf(stderr, "write() - failed! reason: The client closes the socket.\n");
            close(client_fd);
            continue;
        }

        printf("write success! data: %s\n", buffer);
        close(client_fd);
    }


    return 0;
}

// 客户端
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define PORT  8888
#define SERVER_ADDR "127.0.0.1"

int main(int argc, char* argv[]) {
	struct sockaddr_in in;
	bzero(&in, sizeof(in));

	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 0) {
		fprintf(stderr, "- socket() - failed! reason: %s\n", strerror(errno));
		exit(1);
	}

	in.sin_family = AF_INET;
	inet_pton(AF_INET, SERVER_ADDR, &in.sin_addr);
	in.sin_port = htons(PORT);

	if (connect(sock, (struct sockaddr*)(&in), sizeof(in)) < 0) {
		fprintf(stderr, "- connect() - failed! reason: %s\n", strerror(errno));
		exit(2);
	}

	char buffer[1024] = { 0 };
	strcpy(buffer, "abcdefg");
	int ret = write(sock, buffer, strlen(buffer));
	if (ret < 0) {
		fprintf(stderr, "- write() - failed! reason: %s\n", strerror(errno));
		exit(3);
	}
	printf("write success! data: %s\n", buffer);

	bzero(buffer, sizeof(buffer));
	ret = read(sock, buffer, sizeof(buffer));
	if (ret < 0) {
		fprintf(stderr, "- read() - failed! reason: %s\n", strerror(errno));
		exit(4);
	}

	printf("read success! data: %s\n", buffer);

	return 0;
}

四、五种网络IO模式 - 非阻塞IO

1. 场景

小明同学又一次急用开水,打开水龙头后发现没有水,因为还有其它急事他马上离开了,过一会他又拿着杯子来看看……在中间离开的这些时间里,小明同学离开了装水现场(回到用户进程空间),可以做他自己的事情。这就是非阻塞IO模型。但是它只有是检查无数据的时候是非阻塞的,在数据到达的时候依然要等待复制数据到用户空间(等着水将水杯装满),因此它还是同步IO。
当用户线程发起一个read操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。
所以事实上,在非阻塞IO模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU。

典型的非阻塞IO模型一般如下图所示

非阻塞IO模型

设置非阻塞IO的常用方式如下:

  1. 创建socket时指定(在type中增加 SOCK_NONBLOCK)
    int s = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
  2. 在使用前指定
    fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK);

2. 示例代码

// 服务器端
#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define PORT  8888

int InitServer() {
    // 在创建时指定
    int sock = socket(AF_INET, SOCK_STREAM/* | SOCK_NONBLOCK*/, 0);
    if (sock < 0) {
        fprintf(stderr, "socket() - failed! reason: %s\n", strerror(errno));
        return -1;
    }

    struct sockaddr_in in;
    bzero(&in, sizeof(in));
    in.sin_family = AF_INET;
    in.sin_addr.s_addr = htonl(INADDR_ANY);
    in.sin_port = htons(PORT);

    int ret = bind(sock, (struct sockaddr*)(&in), sizeof(in));
    if (ret < 0) {
        fprintf(stderr, "bind() - failed! reason: %s\n", strerror(errno));
        return -1;
    }

    ret = listen(sock, 128);
    if (ret < 0) {
        fprintf(stderr, "listen() - failed! reason: %s\n", strerror(errno));
        return -1;
    }

    return sock;
}

int main(int argc, char* argv[]) {
    int server_fd = InitServer();
    if (server_fd < 0) {
        exit(1);
    }

    char buffer[1024] = { 0 };

    // 在使用前指定
    fcntl(server_fd, F_SETFL, fcntl(server_fd, F_GETFL, 0) | O_NONBLOCK);

    while (1) {
        struct sockaddr_in client;
        int client_len = sizeof(client);
        int client_fd = accept(server_fd, (struct sockaddr*)(&client), &client_len);
        if (client_fd < 0) {
            if (EAGAIN || EWOULDBLOCK) {
                printf("accept() - failed! reason: No client connection!\n");
                sleep(1);
                continue;
            }
            fprintf(stderr, "accept() - failed! reason: %s\n", strerror(errno));
            continue;
        }

        bzero(buffer, sizeof(buffer));
        int len = read(client_fd, buffer, sizeof(buffer) - 1);
        if (len < 0) {
            fprintf(stderr, "read() - failed! reason: %s\n", strerror(errno));
            close(client_fd);
            continue;
        }
        else if (len == 0) {
            fprintf(stderr, "read() - failed! reason: The client closes the socket.\n");
            close(client_fd);
            continue;
        }

        printf("read success! data: %s\n", buffer);

        for (int i = 0; i < len; ++i) {
            buffer[i] = toupper(buffer[i]);
        }

        if (write(client_fd, buffer, len) < 0) {
            fprintf(stderr, "write() - failed! reason: The client closes the socket.\n");
            close(client_fd);
            continue;
        }

        printf("write success! data: %s\n", buffer);
        close(client_fd);
    }



    return 0;
}
// 客户端
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define PORT  8888
#define SERVER_ADDR "127.0.0.1"

int main(int argc, char* argv[]) {
	struct sockaddr_in in;
	bzero(&in, sizeof(in));

	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 0) {
		fprintf(stderr, "- socket() - failed! reason: %s\n", strerror(errno));
		exit(1);
	}

	in.sin_family = AF_INET;
	inet_pton(AF_INET, SERVER_ADDR, &in.sin_addr);
	in.sin_port = htons(PORT);

	if (connect(sock, (struct sockaddr*)(&in), sizeof(in)) < 0) {
		fprintf(stderr, "- connect() - failed! reason: %s\n", strerror(errno));
		exit(2);
	}

	char buffer[1024] = { 0 };
	strcpy(buffer, "abcdefg");
	int ret = write(sock, buffer, strlen(buffer));
	if (ret < 0) {
		fprintf(stderr, "- write() - failed! reason: %s\n", strerror(errno));
		exit(3);
	}
	printf("write success! data: %s\n", buffer);

	bzero(buffer, sizeof(buffer));
	ret = read(sock, buffer, sizeof(buffer));
	if (ret < 0) {
		fprintf(stderr, "- read() - failed! reason: %s\n", strerror(errno));
		exit(4);
	}

	printf("read success! data: %s\n", buffer);

	return 0;
}

五、五种网络IO模式 - IO多路复用

1. 场景

有一天,学校里面优化了热水的供应,增加了很多水龙头,这个时候小明同学再去装水,舍管阿姨告诉他这些水龙头都还没有水,你可以去忙别的了,等有水了告诉他。于是等啊等(select调用中),过了一会阿姨告诉他有水了。
这里有两种情况:

情况1: 阿姨只告诉来水了,但没有告诉小明是哪个水龙头来水了,要自己一个一个去尝试。(select/poll 场景)
情况2: 舍管阿姨会告诉小明同学哪几个水龙头有水了,小明同学不需要一个个打开看(epoll 场景)

SELECT图示

SELECT图示
当用户进程调用了select,那么整个进程就会被block,而同时,kernel会 “监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
所以,IO多路复用的特点是通过一种机制,一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入就绪状态,select()函数就可以返回。
这里需要使用两个system call(select 和 recvfrom),而blocking IO只调用了一个system call(recvfrom)。但是,用select的优势在于它可以同时处理多个connection。
如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用mutil-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll 的优势并不是对于单个连接能处理得更好,而是在于能同时处理更多的连接。

2. SELECT

在一段指定的时间内,监听用户感兴趣的文件描述符上可读、可写和异常等事件。

  • select函数及参数所需函数
/*************************************************************************************************
函数:int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
头部:#include <sys/select.h>
功能:监视多个文件描述符,直到一个或多个文件描述符变得“准备好”进行某类I/O操作(例如,可能的输入)。
参数:
	nfds 		- 最大的文件描述符+1,FD_SETSIZE:系统默认最大的文件描述符
	readfds		- 用于检查可读性
	writefds	- 用于检查可写性
	exceptfds	- 用于检查异常数据
	timeout		- 指向一个timeval结构的指针,用于决定select等待IO操作的最长时间,为空则一直等待。
返回:
	> 0			- 已就绪的文件句柄总数
	= 0			- 超时
	-1			- 出错,errno表示出错信息
**************************************************************************************************/

// timeval 结构体
struct timeval {
	long tv_sec;	// seconds
	long tv_usec;	// microseconds
};

// fd_set结构体所用到的函数
void FD_ZERO(fd_set *set);			// 一个 fd_set类型变量的所有位都设为 0
void FD_SET(int fd, fd_set *set);	// 设置变量的某个位置位
void FD_CLR(int fd, fd_set *set);	// 清除某个位时可以使用
int  FD_ISSET(int fd, fd_set *set);	// 测试某个位是否被置位
  • 示例代码
// 服务器端
#include <errno.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define PORT  8888

int InitServer() {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        fprintf(stderr, "socket() - failed! reason: %s\n", strerror(errno));
        return -1;
    }

    struct sockaddr_in in;
    bzero(&in, sizeof(in));
    in.sin_family = AF_INET;
    in.sin_addr.s_addr = htonl(INADDR_ANY);
    in.sin_port = htons(PORT);

    int ret = bind(sock, (struct sockaddr*)(&in), sizeof(in));
    if (ret < 0) {
        fprintf(stderr, "bind() - failed! reason: %s\n", strerror(errno));
        return -1;
    }

    ret = listen(sock, 5);
    if (ret < 0) {
        fprintf(stderr, "listen() - failed! reason: %s\n", strerror(errno));
        return -1;
    }

    return sock;
}

int main(int argc, char* argv[]) {
    int server_fd = InitServer();
    if (server_fd < 0) {
        exit(1);
    }

    fd_set readfds, tmpfds;
    char buffer[1024] = { 0 };

    FD_ZERO(&readfds);
    FD_SET(server_fd, &readfds);    // 将服务器端socket加入到集合中

    while (1) {
        tmpfds = readfds;   // 将需要监视的集合和源集合分开,因为select()会对源集合进行修改

        // 无限期阻塞
        int result = select(FD_SETSIZE, &tmpfds, (fd_set*)0, (fd_set*)0, (struct timeval*)0);
        if (result < 0) {
            fprintf(stderr, "select() - failed! reason: %s\n", strerror(errno));
            exit(1);
        }
        else if (result == 0) {
            printf("select() - timeout!\n");
            continue;
        }

        // 扫描所有文件描述符
        for (int fd = 0; fd < FD_SETSIZE; ++fd) {
            if (!FD_ISSET(fd, &tmpfds)) {   // 文件描述符不在集合中
                continue;
            }

            if (fd == server_fd) {          // 文件描述符为服务器描述符,即有客户端请求
                struct sockaddr_in client;
                int client_len = sizeof(client);
                bzero(&client, client_len);
                int cfd = accept(server_fd, (struct sockaddr*)(&client), &client_len);
                if (cfd < 0) {
                    fprintf(stderr, "accept() - failed! reason: %s\n", strerror(errno));
                    continue;
                }
                FD_SET(cfd, &readfds);  // 将客户端socket加入到集合中
                printf("add client socketfd[%d] to the set!\n", cfd);
            }
            else {  // 客户端有数据请求
                int nread = 0;
                ioctl(fd, FIONREAD, &nread);    // 获取数据量,放入nread
                if (nread == 0) {   // 客户端关闭socket
                    close(fd);
                    FD_CLR(fd, &readfds);   // 去除关闭的socket
                    printf("remove client socketfd[%d] from set!\n", fd);
                }
                else {
                    char buffer[1024] = { 0 };
                    int len = read(fd, buffer, nread < 1024 ? nread : 1023);
                    if (len < 1) {
                        close(fd);
                        FD_CLR(fd, &readfds);
                        fprintf(stderr, "read() - failed! reason: %s\n", strerror(errno));
                    }
                    buffer[len] = '\0';
                    printf("read() - success! data: %s\n", buffer);
                    // 转换成大写返回
                    for (int i = 0; i < len; ++i) { buffer[i] = toupper(buffer[i]); }
                    write(fd, buffer, len);
                }
            }
        }

    }

    return 0;
}
// 客户端
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define PORT  8888
#define SERVER_ADDR "127.0.0.1"

int main(int argc, char* argv[]) {
	struct sockaddr_in in;
	bzero(&in, sizeof(in));

	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 0) {
		fprintf(stderr, "- socket() - failed! reason: %s\n", strerror(errno));
		exit(1);
	}

	in.sin_family = AF_INET;
	inet_pton(AF_INET, SERVER_ADDR, &in.sin_addr);
	in.sin_port = htons(PORT);

	if (connect(sock, (struct sockaddr*)(&in), sizeof(in)) < 0) {
		fprintf(stderr, "- connect() - failed! reason: %s\n", strerror(errno));
		exit(2);
	}

	int ret = 0;
	char buffer[1024] = { 0 };
	// 第一次读写
	strcpy(buffer, "abcdefg");
	ret = write(sock, buffer, strlen(buffer));
	if (ret < 0) {
		fprintf(stderr, "- write() - failed! reason: %s\n", strerror(errno));
		exit(3);
	}
	printf("write success! data: %s\n", buffer);

	bzero(buffer, sizeof(buffer));
	ret = read(sock, buffer, sizeof(buffer));
	if (ret < 0) {
		fprintf(stderr, "- read() - failed! reason: %s\n", strerror(errno));
		exit(4);
	}
	printf("read success! data: %s\n", buffer);

	sleep(5);	// 休眠5秒

	// 第二次读写
	strcpy(buffer, "hijklmn");
	ret = write(sock, buffer, strlen(buffer));
	if (ret < 0) {
		fprintf(stderr, "- write() - failed! reason: %s\n", strerror(errno));
		exit(3);
	}
	printf("write success! data: %s\n", buffer);

	bzero(buffer, sizeof(buffer));
	ret = read(sock, buffer, sizeof(buffer));
	if (ret < 0) {
		fprintf(stderr, "- read() - failed! reason: %s\n", strerror(errno));
		exit(4);
	}
	printf("read success! data: %s\n", buffer);

	return 0;
}

3. POLL

  • 和select 一样,如果没有事件发生,则进入休眠状态,如果在规定时间内有事件发生,则返回成功,规定时间过后仍然没有事件发生则返回失败。可见,等待期间将进程休眠,利用事件驱动来唤醒进程,将更能提高CPU的效率。
  • poll 和select 区别: select 有文件句柄上线设置,值为FD_SETSIZE,
    而poll 理论上没有限制!
  • poll函数
/*************************************************************************************************
函数:int poll(struct pollfd *fds, nfds_t nfds, int timeout);
头部:#include <poll.h>
功能:它等待一组文件描述符中的一个准备好执行I/O。
参数:
	fds			- 可以传递多个结构体,也就是说可以监测多个驱动设备所产生的事件,只要有一个产生了请求事件,就能立即返回
	nfds 		- 监测驱动文件的个数
	timeout		- 超时时间,单位是ms
返回:
	> 0			- struct pollfd结构体中 revents 域不为0的文件描述符个数
	= 0			- 超时
	-1			- 出错,errno表示出错信息
**************************************************************************************************/

// pollfd 结构体
struct pollfd {
	int		fd;			// 文件描述符
	short 	events;		// 请求的事件类型,监视驱动文件的事件掩码。可用位或
	short 	revents;	// 驱动文件实际返回的事件
};

// pollfd.events 域可取如下值
POLLIN		// 有数据可读
POLLRDNORM	// 有普通数据可读,等效于 POLLIN
POLLPRI		// 有紧迫数据可读
POLLOUT		// 写数据
POLLER		// 指定的文件描述符发生错误
POLLHUP		// 指定的文件描述符挂起事件
POLLNVAL	// 无效的请求,打不开指定的文件描述符
  • 示例代码
// 服务器端
#include <poll.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define PORT    8888

#define MAX_FD  8192

struct pollfd fds[MAX_FD];
int cur_max_fd = 1;         // 当前最大文件描述符

int InitServer() {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        fprintf(stderr, "socket() - failed! reason: %s\n", strerror(errno));
        return -1;
    }

    struct sockaddr_in in;
    bzero(&in, sizeof(in));
    in.sin_family = AF_INET;
    in.sin_addr.s_addr = htonl(INADDR_ANY);
    in.sin_port = htons(PORT);

    int ret = bind(sock, (struct sockaddr*)(&in), sizeof(in));
    if (ret < 0) {
        fprintf(stderr, "bind() - failed! reason: %s\n", strerror(errno));
        return -1;
    }

    ret = listen(sock, 5);
    if (ret < 0) {
        fprintf(stderr, "listen() - failed! reason: %s\n", strerror(errno));
        return -1;
    }

    return sock;
}

void setMaxFD(int fd) {
    if (fd >= cur_max_fd) {
        cur_max_fd = fd + 1;
    }
}

int main(int argc, char *argv[]) {
    int server_fd = InitServer();
    if (server_fd < 0) {
        exit(1);
    }

    fds[server_fd].fd = server_fd;
    fds[server_fd].events = POLLIN;
    fds[server_fd].revents = 0;
    setMaxFD(server_fd);

    char buffer[1024] = { 0 };

    while (1) {
        int result = poll(fds, cur_max_fd, 2000);

        if (result < 0) {
            fprintf(stderr, "poll() - failed! reason: %s\n", strerror(errno));
            exit(2);
        }
        else if (result == 0) {
            printf("timeout!\n");
            continue;
        }

        for (int i = 0; i < cur_max_fd; ++i) {
            if (!fds[i].revents) {   // 未找到相关描述符
                sleep(1);
                continue;
            }

            int fd = fds[i].fd;
            if (fd == server_fd) {  // 判断是否是服务器套接字,是则表示为客户端请求连接
                struct sockaddr_in client;
                int client_len = sizeof(client);
                int client_fd = accept(server_fd, (struct sockaddr*)(&client), &client_len);
                if (client_fd < 0) {
                    fprintf(stderr, "accept() - failed! reason: %s\n", strerror(errno));
                    exit(3);
                }

                // 将客户端socket加入到集合中
                fds[client_fd].fd = client_fd;
                fds[client_fd].events = POLLIN;
                fds[client_fd].revents = 0;
                setMaxFD(client_fd);

                printf("add clientfd[%d] in set!\n", client_fd);
            }
            else {  // 客户端socket中有请求时
                if (fds[i].revents & POLLIN) {  // 数据可读
                    bzero(buffer, sizeof(buffer));
                    int nread = read(fd, buffer, sizeof(buffer) - 1);
                    if (nread == 0) {
                        close(fd);
                        // 去除关闭的套接字
                        memset(&fds[i], 0, sizeof(struct pollfd));
                        printf("remove socketfd[%d] from set!\n", fd);
                    }
                    else {
                        buffer[nread] = '\0';
                        printf("recv client data: %s\n", buffer);

                        for (int j = 0; j < nread; ++j) {
                            buffer[j] = toupper(buffer[j]);
                        }
                        fds[i].events = POLLOUT;
                        printf("write success! data: %s\n", buffer);
                    }
                }
                else if (fds[i].revents & POLLOUT) {    // 数据可写
                    if (write(fd, buffer, strlen(buffer)) < 1) {
                        fprintf(stderr, "write() - failed! reason: %s\n", strerror(errno));
                        exit(3);
                    }

                    fds[i].events = POLLIN;
                }
            }
        }

    }

    return 0;
}

// 客户端
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define PORT  8888
#define SERVER_ADDR "127.0.0.1"

int main(int argc, char* argv[]) {
	struct sockaddr_in in;
	bzero(&in, sizeof(in));

	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 0) {
		fprintf(stderr, "socket() - failed! reason: %s\n", strerror(errno));
		exit(1);
	}

	in.sin_family = AF_INET;
	inet_pton(AF_INET, SERVER_ADDR, &in.sin_addr);
	in.sin_port = htons(PORT);

	if (connect(sock, (struct sockaddr*)(&in), sizeof(in)) < 0) {
		fprintf(stderr, "connect() - failed! reason: %s\n", strerror(errno));
		exit(2);
	}

	char buffer[1024] = { 0 };
	strcpy(buffer, "abcdefg");
	int ret = write(sock, buffer, strlen(buffer));
	if (ret < 0) {
		fprintf(stderr, "write() - failed! reason: %s\n", strerror(errno));
		exit(3);
	}
	printf("write success! data: %s\n", buffer);

	bzero(buffer, sizeof(buffer));
	ret = read(sock, buffer, sizeof(buffer));
	if (ret < 0) {
		fprintf(stderr, "read() - failed! reason: %s\n", strerror(errno));
		exit(4);
	}

	printf("read success! data: %s\n", buffer);

	return 0;
}

4. Epoll

参考链接:Linux - Epoll的基础使用

5. libevent框架

参考链接:libevent框架的使用

六、五种网络IO模式 - 信号驱动IO

1. 场景

有一天,学校里面优化了热水的供应,增加了很多水龙头,这个时候小明同学再去装水,舍管阿姨告诉他你可以先去做别的事,等有水了我通知你。
使用信号驱动I/O时,当网络套接字可读后,内核通过发送SIGIO信号通知应用进程,于是应用可以开 始读取数据。该方式并不是异步I/O,因为实际读取数据到应用进程缓存的工作仍然是由应用自己负责的。
信号驱动IO流程

七、五种网络IO模式 - 异步IO

当用户进程发起一个read操作后,内核收到该read操作后,首先它会立刻返回,所以不会对用户进程 阻塞,然后它会等待数据的准备完成,再把数据拷贝到用户内存,完成之后,它会给用户进程发送一个信号,告诉用户进程read操作已完成。
异步IO流程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值