UNIX高级编程【深入浅出】 高级 I/O

目录

非阻塞IO(Non-blocking IO):

如何将文件设置为非阻塞

IO多路转接(IO multiplexing):

简单的示例代码(select)

简单的示例代码(poll)

简单的示例代码(epoll)

存储映射文件(Memory-mapped Files):

有限状态机​编辑

实现"/dev/tty11"和"/dev/tty12"文件的交互(字符设备文件Ctrl+alt+f11)

select 实现(main改变) 

poll实现(main改变)


  1. 高级I/O:
    1. IO的特殊表现,例如非阻塞IO,文件锁,IO多路转接,存储映射等等
  2. 非阻塞IO(Non-blocking IO):

    1. 非阻塞IO是一种异步的IO操作方式。在进行非阻塞IO时,如果请求的IO操作无法立即完成,系统不会将进程阻塞,而是立即返回一个结果。这样可以让进程继续执行其他任务,而无需等待IO操作完成。通过轮询或事件通知等方式,可以判断IO操作是否已完成。

    2. 对于非阻塞IO,例如读空管道则会返回-1, errno会设置未EAGAIN,我们将这种错误称为假错
    3. 对于阻塞IO,如果读空管道,阻塞的期间被信号打断了,那么errno会设置未EINTR,这种错误也称为假错
    4. 如何将文件设置为非阻塞
      1. 未打开的文件,open的时候加上非阻塞选项(O_NONBLOCK)
        1. open(fd, | O_NONBLOCK)
      2. 已打开的文件,fcntl将文件设置未非阻塞
        1. old_f = fcntl(fd, F_GETFL);
        2. fcntl(fd, F_SETFL, old_f | O_NONBLOCK)
  3. IO多路转接(IO multiplexing):

    1. IO多路转接是一种允许单个进程同时监听多个IO事件的机制。通过IO多路转接,可以使用一个线程处理多个IO通道的读写操作。常见的 IO多路转接技术包括select、poll和epoll等。

    2. select:select是最古老和最常见的IO多路转接技术之一。通过select,可以将多个文件描述符注册到内核中,然后通过轮询的方式检查这些文件描述符是否就绪,即是否有可读、可写或出错的事件发生。

    3. 简单的示例代码(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");
          }
    4. poll:poll是对select的改进,它提供了更好的性能和扩展性。与select类似,poll也通过轮询的方式来检查多个文件描述符的状态,并返回就绪的文件描述符。

    5. 简单的示例代码(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");
          }
      
    6. epoll:epoll是Linux系统中引入的IO多路转接机制,它是目前最常用的IO多路转接技术。相比于select和poll,epoll具有更高的性能和更好的扩展性。通过使用epoll,可以在很大数量的连接中高效地进行事件检测。epoll提供了三种工作模式:边沿触发(Edge-Triggered)、水平触发(Level-Triggered)和一次性触发(One-Shot Triggered),可以根据需要选择适合的工作模式。因为epoll()采用了回调机制,它不需要像select()poll()那样每次都遍历所有文件描述符,而是只返回已发生事件的文件描述符,极大地减少了CPU资源的消耗

    7. 简单的示例代码(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;
      }
  4. 存储映射文件(Memory-mapped Files):

    1. 存储映射文件允许将文件的内容映射到进程的地址空间中。这样,进程可以像访问内存一样直接读取或写入文件中的数据,而无需进行显式的IO操作。内存映射文件提供了高效的IO方式,并且可以减少数据的拷贝次数。

  5. 有限状态机

  • 实现"/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);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值