目录
实现"/dev/tty11"和"/dev/tty12"文件的交互(字符设备文件Ctrl+alt+f11)
- 高级I/O:
- IO的特殊表现,例如非阻塞IO,文件锁,IO多路转接,存储映射等等
-
非阻塞IO(Non-blocking IO):
-
非阻塞IO是一种异步的IO操作方式。在进行非阻塞IO时,如果请求的IO操作无法立即完成,系统不会将进程阻塞,而是立即返回一个结果。这样可以让进程继续执行其他任务,而无需等待IO操作完成。通过轮询或事件通知等方式,可以判断IO操作是否已完成。
- 对于非阻塞IO,例如读空管道则会返回-1, errno会设置未EAGAIN,我们将这种错误称为假错
- 对于阻塞IO,如果读空管道,阻塞的期间被信号打断了,那么errno会设置未EINTR,这种错误也称为假错
-
如何将文件设置为非阻塞
- 未打开的文件,open的时候加上非阻塞选项(O_NONBLOCK)
- open(fd, | O_NONBLOCK)
- 已打开的文件,fcntl将文件设置未非阻塞
- old_f = fcntl(fd, F_GETFL);
- fcntl(fd, F_SETFL, old_f | O_NONBLOCK)
- 未打开的文件,open的时候加上非阻塞选项(O_NONBLOCK)
-
-
IO多路转接(IO multiplexing):
-
IO多路转接是一种允许单个进程同时监听多个IO事件的机制。通过IO多路转接,可以使用一个线程处理多个IO通道的读写操作。常见的 IO多路转接技术包括select、poll和epoll等。
-
select:select是最古老和最常见的IO多路转接技术之一。通过select,可以将多个文件描述符注册到内核中,然后通过轮询的方式检查这些文件描述符是否就绪,即是否有可读、可写或出错的事件发生。
-
简单的示例代码(select)
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); nfds 表示最大的文件描述符加1。 readfds、writefds、exceptfds 分别表示可读、可写和异常事件的文件描述符集合。 timeout 是超时时间,即在等待事件发生时最多等待的时间。 int max_fd; // 最大的文件描述符加1 fd_set read_fds; // 可读事件的文件描述符集合 struct timeval timeout; // 超时时间 // 清空文件描述符集合 FD_ZERO(&read_fds); // 添加标准输入的文件描述符到集合中 FD_SET(0, &read_fds); max_fd = 1; // 设置超时时间为5秒 timeout.tv_sec = 5; timeout.tv_usec = 0; int ret = select(max_fd, &read_fds, NULL, NULL, &timeout); if (ret > 0) { if (FD_ISSET(0, &read_fds)) { printf("标准输入可读\n"); } } else if (ret == 0) { printf("超时\n"); } else { perror("select error"); }
-
poll:poll是对select的改进,它提供了更好的性能和扩展性。与select类似,poll也通过轮询的方式来检查多个文件描述符的状态,并返回就绪的文件描述符。
-
简单的示例代码(poll)
int poll(struct pollfd *fds, nfds_t nfds, int timeout); struct pollfd 是一个结构体,用于描述一个文件描述符及其关注的事件。 nfds 是结构体数组的长度,即关注的文件描述符的数量。 timeout 是超时时间,单位为毫秒。传入负值表示无限等待,传入0表示立即返回。 事件发生后,poll()函数会填充struct pollfd 中的revents 字段来指示具体发生的事件。 struct pollfd fds[2]; int ret; // 监听标准输入的可读事件 fds[0].fd = 0; fds[0].events = POLLIN; // 监听标准输出的可写事件 fds[1].fd = 1; fds[1].events = POLLOUT; ret = poll(fds, 2, 5000); // 设置超时时间为5秒 if (ret > 0) { if (fds[0].revents & POLLIN) { printf("标准输入可读\n"); } if (fds[1].revents & POLLOUT) { printf("标准输出可写\n"); } } else if (ret == 0) { printf("超时\n"); } else { perror("poll error"); }
-
epoll:epoll是Linux系统中引入的IO多路转接机制,它是目前最常用的IO多路转接技术。相比于select和poll,epoll具有更高的性能和更好的扩展性。通过使用epoll,可以在很大数量的连接中高效地进行事件检测。epoll提供了三种工作模式:边沿触发(Edge-Triggered)、水平触发(Level-Triggered)和一次性触发(One-Shot Triggered),可以根据需要选择适合的工作模式。因为
epoll()
采用了回调机制,它不需要像select()
和poll()
那样每次都遍历所有文件描述符,而是只返回已发生事件的文件描述符,极大地减少了CPU资源的消耗。 -
简单的示例代码(epoll)
int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); epoll_create()函数用于创建一个epoll实例,并返回一个文件描述符epfd。 epoll_ctl()函数用于向epoll实例中添加、修改或删除文件描述符及其关注的事件。 epoll_wait()函数用于等待文件描述符上的事件,并返回已发生事件的文件描述符。 需要使用struct epoll_event结构体来描述事件. #define MAX_EVENTS 10 int main() { int epfd; // epoll实例的文件描述符 struct epoll_event event; struct epoll_event events[MAX_EVENTS]; // 创建epoll实例 epfd = epoll_create(1); if (epfd == -1) { perror("epoll_create error"); return 1; } // 添加标准输入的文件描述符到epoll实例中 event.events = EPOLLIN; event.data.fd = 0; if (epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &event) == -1) { perror("epoll_ctl error"); return 1; } int ret = epoll_wait(epfd, events, MAX_EVENTS, -1); if (ret > 0) { for (int i = 0; i < ret; ++i) { if (events[i].data.fd == 0) { printf("标准输入可读\n"); } } } else if (ret == -1) { perror("epoll_wait error"); } // 关闭epoll实例 close(epfd); return 0; }
-
-
存储映射文件(Memory-mapped Files):
-
存储映射文件允许将文件的内容映射到进程的地址空间中。这样,进程可以像访问内存一样直接读取或写入文件中的数据,而无需进行显式的IO操作。内存映射文件提供了高效的IO方式,并且可以减少数据的拷贝次数。
-
-
有限状态机
-
实现"/dev/tty11"和"/dev/tty12"文件的交互(字符设备文件Ctrl+alt+f11)
#ifndef __FSM_H__
#define __FSM_H__
#define BUFSIZE 1024
// ADT
enum {STATE_R, STATE_W, STATE_E, STATE_T};
typedef struct {
int rfd, wfd;
char buf[BUFSIZE];
int read_cnt; // 读到的字节个数
int write_cnt; // 写入的字节个数
int state;
char *errmsg; // 记录异常函数
}fsm_t;
// 接口
// 初始化状态机
int fsm_init(fsm_t **fsm, int rfd, int wfd);
// 状态机运行
int fsm_drive(fsm_t *fsm);
// 销毁
void fsm_destroy(fsm_t **fsm);
#endif
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include "fsm.h"
#define handler_msg(en, msg)\
do { fsm->state = en; fsm->errmsg = msg; } while(0)
int fsm_init(fsm_t **fsm, int rfd, int wfd)
{
int rfd_old_flag, wfd_old_flag;
fsm_t *me = malloc(sizeof(fsm_t));
if (NULL == me)
return -1;
me->rfd = rfd;
me->wfd = wfd;
memset(me->buf, '\0', BUFSIZE);
me->read_cnt = 0;
me->state = STATE_R;
me->errmsg = NULL;
me->write_cnt = 0;
// 确定状态机的文件都是非阻塞的
rfd_old_flag = fcntl(me->rfd, F_GETFL);
fcntl(me->rfd, F_SETFL, rfd_old_flag | O_NONBLOCK);
wfd_old_flag = fcntl(me->wfd, F_GETFL);
fcntl(me->wfd, F_SETFL, wfd_old_flag | O_NONBLOCK);
*fsm = me;
return 0;
}
// 四个状态, 功能各不相同
int fsm_drive(fsm_t *fsm)
{
int ret;
// 根据当前状态机的状态,满足的条件推到下一个状态
switch (fsm->state) {
case STATE_R:
fsm->read_cnt = read(fsm->rfd, fsm->buf, BUFSIZE);
if (fsm->read_cnt == 0) {
// eof
fsm->state = STATE_T;
} else if (fsm->read_cnt == -1) {
if (errno != EAGAIN) {
// 不是假错
handler_msg(STATE_E, "read()");
}
} else {
// 读成功了
fsm->state = STATE_W;
fsm->write_cnt = 0;
}
break;
case STATE_W:
// 读多少写多少
ret = write(fsm->wfd, fsm->buf + fsm->write_cnt, fsm->read_cnt);
if (-1 == ret) {
handler_msg(STATE_E, "write()");
} else {
// 没写完
if (ret < fsm->read_cnt) {
fsm->write_cnt += ret;
fsm->read_cnt -= ret;
} else {
// 写完了
fsm->state = STATE_R;
}
}
break;
case STATE_E:
perror(fsm->errmsg);
fsm->state = STATE_T;
break;
case STATE_T:
// 保留
break;
}
return 0;
}
void fsm_destroy(fsm_t **fsm)
{
free(*fsm);
*fsm = NULL;
}
select 实现(main改变)
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>
#include "fsm.h"
#define TTY1 "/dev/tty11"
#define TTY2 "/dev/tty12"
int main(void)
{
fsm_t *fd12, *fd21;
int fd1, fd2, ret;
fd_set rset, wset;
// 设置超时时间
struct timeval timeout;
timeout.tv_sec = 2;
timeout.tv_usec = 0;
fd1 = open(TTY1, O_RDWR | O_NONBLOCK);
if (-1 == fd1) {
perror("open()");
exit(1);
}
write(fd1, "[********]", 10);
fd2 = open(TTY2, O_RDWR);
if (-1 == fd2) {
perror("open()");
close(fd1);
exit(1);
}
write(fd2, "[!!!!!!!!]", 10);
fsm_init(&fd12, fd1, fd2);
fsm_init(&fd21, fd2, fd1);
// 轮询--->通知 监听文件的数据到来
while (!(fd12->state == STATE_T && fd21->state == STATE_T)) {
// 监听文件描述符的事件
FD_ZERO(&rset);
FD_ZERO(&wset);
// 判断状态机的状态
if (fd12->state == STATE_R)
// 想要读fd12->rfd
FD_SET(fd12->rfd, &rset);
else if (fd12->state == STATE_W){
// 想要写fd12->wfd
write(fd2, TTY1, 10);
FD_SET(fd12->wfd, &wset);
}
if (fd21->state == STATE_R)
FD_SET(fd21->rfd, &rset);
else if (fd21->state == STATE_W){
write(fd1, TTY2, 10);
FD_SET(fd21->wfd, &wset);
}
// 请求内核监听
if (-1 == (ret = select((fd1 > fd2 ? fd1 : fd2) + 1, &rset, &wset, NULL, &timeout))) {
// 信号会打断阻塞的系统调用
if (errno == EINTR) {
continue;
}
perror("select()");
goto ERROR;
}
#if 0
if(ret == 0){
write(fd1, "timeout~~", 9);
}
#endif
// 判断是哪个集合事件发生了
if (FD_ISSET(fd12->rfd, &rset) || FD_ISSET(fd12->wfd, &wset))
fsm_drive(fd12);
if (FD_ISSET(fd21->rfd, &rset) || FD_ISSET(fd21->wfd, &wset))
fsm_drive(fd21);
}
fsm_destroy(&fd12);
fsm_destroy(&fd21);
close(fd1);
close(fd2);
return 0;
ERROR:
close(fd1);
close(fd2);
exit(1);
}
poll实现(main改变)
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <unistd.h>
#include "fsm.h"
#define TTY1 "/dev/tty11"
#define TTY2 "/dev/tty12"
int main(void)
{
fsm_t *fsm12, *fsm21;
int fd1, fd2, ret;
struct pollfd fds[2];
fd1 = open(TTY1, O_RDWR | O_NONBLOCK);
if (-1 == fd1) {
perror("open()");
exit(1);
}
write(fd1, "[YY11]", 6);
fd2 = open(TTY2, O_RDWR);
if (-1 == fd2) {
perror("open()");
close(fd1);
exit(1);
}
write(fd2, "[YY12]", 6);
fsm_init(&fsm12, fd1, fd2);
fsm_init(&fsm21, fd2, fd1);
fds[0].fd = fd1;
fds[1].fd = fd2;
// 轮询--->通知 监听文件的数据到来
while (!(fsm12->state == STATE_T && fsm21->state == STATE_T)) {
fds[0].events = 0;
fds[1].events = 0;
// 监听文件的可读性可写性
if (fsm12->state == STATE_R){
// 想要读fsm12->rfd ---> fd1
fds[0].events |= POLLIN;
}
else if (fsm12->state == STATE_W){
// 想要写fsm12->wfd ---> fd2
write(fd2, TTY1, 10);
fds[1].events |= POLLOUT;
}
if (fsm21->state == STATE_R){
fds[1].events |= POLLIN;
}
else if (fsm21->state == STATE_W){
write(fd1, TTY2, 10);
fds[0].events |= POLLOUT;
}
// 请求内核监听
while(-1 == (ret = poll(fds, 2, -1))) { // -1 永久 0 立即 >0 延时
// 信号会打断阻塞的系统调用
if (errno == EINTR) {
continue;
}
perror("poll()");
goto ERROR;
}
#if 0
if(ret == 0){// 超时
write(fd1, "timeout~~", 9);
}
#endif
// 判断是哪个集合事件发生了
if ( (fds[0].revents & POLLIN) || (fds[1].revents & POLLOUT))
fsm_drive(fsm12);
if ( (fds[1].revents & POLLIN) || (fds[0].revents & POLLOUT))
fsm_drive(fsm21);
}
fsm_destroy(&fsm12);
fsm_destroy(&fsm21);
close(fd1);
close(fd2);
return 0;
ERROR:
close(fd1);
close(fd2);
exit(1);
}