《LINUX系统编程》笔记p14
高级IO(Advanced IO)
标准IO是创建文件描述符并操作文件描述符进行读写的操作。
高级IO是IO中的一直用特殊现象,用于更方便的操作IO。
高级IO 包括
- 非阻塞 IO
- IO多路转接
- 存储器映射
- 文件锁等技术
非阻塞IO
非阻塞IO是让IO 变为非阻塞的方式进行读写操作。适用于多个IO 需要同时读取数据的场合(通常使用轮询)。
两种方法:
- open(2) 函数打开时指定打开方式标志位为 O_NONBLOCK。
- 使用 fcntl(2)的F*SETFL重设为 * O_NONBLOCK标志位。
使用非阻塞IO 轮询的方式实现UDP 客户端
// UDP 客户端实现
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h> // man 7 ip
#include <netinet/ip.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <fcntl.h>// #define SERVER_IP "192.144.206.112"
#define SERVER_IP "127.0.0.1"
// #define SERVER_IP "10.11.9.208"
#define SERVER_PORT 6666#define BUF_SIZE (1024)int main(int argc, char * argv[]) {int sock_fd;int ret;struct sockaddr_in sin; // 服务器端IPv4地址族结构struct sockaddr_in cin; // 客户端IPv4地址族结构char buf_read[BUF_SIZE]; // 读数据缓冲区char buf_write[BUF_SIZE]; // 写数据缓冲区// 创建套接字sock_fd = socket(AF_INET, SOCK_DGRAM, 0);if (-1 == sock_fd) {perror("socket");}sin.sin_family = AF_INET;sin.sin_port = htons(SERVER_PORT);inet_pton(AF_INET, SERVER_IP, &sin.sin_addr);// 将 sock_fd 设置成非阻塞IOint flag = fcntl(sock_fd, F_GETFL);flag |= O_NONBLOCK;ret = fcntl(sock_fd, F_SETFL, flag);if (-1 == ret) {perror("fcntl");return 3;}flag = fcntl(0, F_GETFL);flag |= O_NONBLOCK;ret = fcntl(0, F_SETFL, flag);if (-1 == ret) {perror("fcntl: 0");return 3;}// 与服务器端通信while(1) {ret = read(0, buf_write, BUF_SIZE);if (ret > 0) {buf_write[ret-1] = 0;// 将buf_write的内容发送给,服务器端.ret = sendto(sock_fd, buf_write, strlen(buf_write), 0, (struct sockaddr*)&sin, sizeof(sin));if (-1 == ret) {perror("sendto");break;}if(0 == strcmp("exit", buf_write))break;}ret = recv(sock_fd, buf_read, BUF_SIZE-1, 0);if (ret > 0) {buf_read[ret] = 0;printf("服务器回复:%s\n", buf_read);} }// 断开连接 close(sock_fd);printf("主进程结束\n");return 0;
}
假错:
IO读取时由于中断信号到来,导致阻塞IO立即返回,错误号 errno 等于 EINTR (error interrupt)。
IO 多路转接(也叫IO多路复用)
用一个函数来阻塞等待多个IO,当其中一个IO 有信号(有数据需要解除阻塞时),则此函数解除阻塞进行处理。
IO多路复用的三种方式:
- select
- poll
- epoll(仅Linux支持)
select IO 多路转接
/* According to POSIX.1-2001, POSIX.1-2008 */
#include <sys/select.h>/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
// 参数
// nfds 最大的 fd的值。
// readfds 读集合,不用时可以置空NULL.
// writefds 写集合,不用时可以置空NULL.
// exceptfds 异常集合,不用时可以置空NULL.
// timeout 超时时间,不用时可以置空NULL,表示不超时。
// 返回值:
// 大于0,是有信号的文件描述符的数量。
// 等于0, 超时。
// 小于0, 出错,同时设置错误号。struct timeval {long tv_sec; /* seconds */long tv_usec; /* microseconds */
};void FD_CLR(int fd, fd_set *set); // 从 FD 集合 set中 删除fd
int FD_ISSET(int fd, fd_set *set); // 判断 fd 是否在 FD 集合set 中
void FD_SET(int fd, fd_set *set); // 将 fd 加入 FD 集合 set
void FD_ZERO(fd_set *set); // 清空所有 FD 集合 set
使用 select 实现 udp客户端的代码
实现丢包是逻辑正常
// UDP 客户端实现
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h> // man 7 ip
#include <netinet/ip.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>// #define SERVER_IP "192.144.206.112"
#define SERVER_IP "127.0.0.1"
// #define SERVER_IP "10.11.9.208"
#define SERVER_PORT 6666#define BUF_SIZE (1024)int main(int argc, char * argv[]) {int sock_fd;int ret;struct sockaddr_in sin; // 服务器端IPv4地址族结构struct sockaddr_in cin; // 客户端IPv4地址族结构char buf_read[BUF_SIZE]; // 读数据缓冲区char buf_write[BUF_SIZE]; // 写数据缓冲区// 创建套接字sock_fd = socket(AF_INET, SOCK_DGRAM, 0);if (-1 == sock_fd) {perror("socket");}sin.sin_family = AF_INET;sin.sin_port = htons(SERVER_PORT);inet_pton(AF_INET, SERVER_IP, &sin.sin_addr);// 使用 IO 多路复用对阻塞的IO进行统一管理int max_fd = sock_fd; // 统一管理文件描述的最大值fd_set read_fds; // 创建一个读文件描述符集合fd_set rfds; // 创建一个读文件描述符,准备保存临时值struct timeval tv; // 用于设置超时时间FD_ZERO(&read_fds); // 清空集合FD_SET(0, &read_fds);FD_SET(sock_fd, &read_fds);// 与服务器端通信while(1) {tv.tv_sec = 10; // 设置超时时间为 10s.tv.tv_usec = 0;rfds = read_fds; // 复制 read_fds;printf("select ...\n");ret = select(max_fd+1, &rfds, NULL, NULL, &tv);if (-1 == ret) {perror("select");break;} else if (0 == ret) {printf("超时...\n");} else {if (FD_ISSET(0, &rfds)) { // 需要读取键盘ret = read(0, buf_write, BUF_SIZE);buf_write[ret-1] = 0;// 将buf_write的内容发送给,服务器端.ret = sendto(sock_fd, buf_write, strlen(buf_write), 0, (struct sockaddr*)&sin, sizeof(sin));if (-1 == ret) {perror("sendto");break;}if(0 == strcmp("exit", buf_write))break;}if (FD_ISSET(sock_fd, &rfds)) { // 需要读取sock_fd;ret = recv(sock_fd, buf_read, BUF_SIZE-1, 0);buf_read[ret] = 0;printf("服务器回复:%s\n", buf_read);}}}// 断开连接 close(sock_fd);printf("主进程结束\n");return 0;
}
poll IO 多路转接(IO多路复用)
作用:使用poll 函数同时等待多个IO(文件描述符)的读写信号。当某个IO 有读写需求是,则此函数解除阻塞并处理对应 IO 的读写请求。
poll 函数
#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);
// 参数
// fds 用于存储多个 pollfd 的结构体数组的起始地址。
// nfds 数据中有效数据的个数。
// timeout 超时时间(毫秒)
//
struct pollfd {int fd; /* file descriptor */short events; /* requested events */short revents; /* returned events */
};
// 参数:
// fd 文件描述符
// events 请求事件
// POLLIN 输入事件
// POLLOUT 输出事件
// POLLHUP 检测需要挂起的数据流事件。
// revents 返回的事件结果。
// 返回值:
// 大于0,是有信号的文件描述符的数量。
// 等于0, 超时。
// 小于0, 出错,同时设置错误号。
poll 实现 bot 的客户端
// UDP 客户端实现
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h> // man 7 ip
#include <netinet/ip.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <poll.h>// #define SERVER_IP "192.144.206.112"
#define SERVER_IP "127.0.0.1"
// #define SERVER_IP "10.11.9.208"
#define SERVER_PORT 6666#define BUF_SIZE (1024)int main(int argc, char * argv[]) {int sock_fd;int ret;struct sockaddr_in sin; // 服务器端IPv4地址族结构struct sockaddr_in cin; // 客户端IPv4地址族结构char buf_read[BUF_SIZE]; // 读数据缓冲区char buf_write[BUF_SIZE]; // 写数据缓冲区struct pollfd fds[1024]; // 用于方文件描述符和等待事件int fds_count = 0; // 用于记录fds数组中的数据个数.// 创建套接字sock_fd = socket(AF_INET, SOCK_DGRAM, 0);if (-1 == sock_fd) {perror("socket");}sin.sin_family = AF_INET;sin.sin_port = htons(SERVER_PORT);inet_pton(AF_INET, SERVER_IP, &sin.sin_addr);// struct pollfd fds[1024]; // 用于方文件描述符和等待事件// int fds_count = 0; // 用于记录fds数组中的数据个数.// 将0 和 sock_fd 加入到 fds数组,并将 fds_count置为2fds[0].fd = 0;fds[0].events = POLLIN | POLLHUP; // 等待输入事件fds[1].fd = sock_fd;fds[1].events = POLLIN | POLLHUP; fds_count = 2;// 与服务器端通信while(1) {printf("poll...\n");ret = poll(fds, fds_count, 10000);if (-1 == ret) {perror("poll");continue; // 进行下一次等待} else if (0 == ret) {printf("timeout ...\n");continue;}if (fds[0].revents) { // 有键盘输入信号ret = read(0, buf_write, BUF_SIZE);buf_write[ret-1] = 0;// 将buf_write的内容发送给,服务器端.ret = sendto(sock_fd, buf_write, strlen(buf_write), 0, (struct sockaddr*)&sin, sizeof(sin));if (-1 == ret) {perror("sendto");break;}if(0 == strcmp("exit", buf_write))break;}if (fds[1].revents) { // 有UDP输入信号ret = recv(sock_fd, buf_read, BUF_SIZE-1, 0);buf_read[ret] = 0;printf("服务器回复:%s\n", buf_read);}}// 断开连接 close(sock_fd);printf("主进程结束\n");return 0;
}