用来监视文件描述符号。
函数:
select( ); 移植性好,太古老了。以事件为单位组织文件描述符的监视
poll( ); 以文件描述符为单位组织事件。
epoll( ); Linux方言,对于poll的优化。
SYNOPSIS
/* According to POSIX.1-2001, POSIX.1-2008 */
#include
/* According to earlier standards */
#include
#include
#include
/*
nfds 所要监视的文件描述符最大的+1 不是数量!!!
readfds 希望关心的读情况文件描述符集合
writefds 希望关心的写情况文件描述符集合
exceptfds 希望关心的异常情况文件描述符集合
timeout 超时设置 不设置的话为死等
*/
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set); //删除
int FD_ISSET(int fd, fd_set *set); //是否存在
void FD_SET(int fd, fd_set *set); //添加
void FD_ZERO(fd_set *set); //清空
#include
int pselect(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout,
const sigset_t *sigmask);
RETURN VALUE
On success, select() and pselect() return the number of file d
escriptors contained in the three returned descriptor sets (that
is, the total number of bits that are set in readfds, writefds,
exceptfds) which may be zero if the timeout expires before anything
interesting happens. On error, -1 is returned, and errno is set to
indicate the error; the file descriptor sets are unmodified, and timeout
becomes undefined.
select 问题:监视文件描述符的集合和返回结果的集合为同一个变量,监视现场和监视结果放在同一个位置,对于大量多次次循环操作需要重复布置大量监视现场;nfds可能溢出。以事件为单位组织文件描述符,监视种类过于单一。
NAME
poll, ppoll - wait for some event on a file descriptor
SYNOPSIS
#include
/*
fds 文件描述符结构体数组起点地址
nfds 文件描述符结构体的长度!!!
timeout 超时设置ms 0 非阻塞 -1 阻塞
*/
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
//return 有多少个事件发生 失败 -1 并设置erron
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include
#include
int ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *tmo_p, const sigset_t *sigmask);
DESCRIPTION
poll() performs a similar task to select(2): it waits for one of a
set of file descriptors to become ready to perform I/O.
The set of file descriptors to be monitored is specified in the fds
argument, which is an array of structures of the following form:
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
// events revents 的选项 以下都是位图
POLLIN There is data to read.
POLLPRI
There is some exceptional condition on the file descriptor.
Possibilities include:
* There is out-of-band data on a TCP socket (see tcp(7)).
* A pseudoterminal master in packet mode has seen a state
change on the slave (see ioctl_tty(2)).
* A cgroup.events file has been modified (see cgroups(7)).
POLLOUT
Writing is now possible, though a write larger that the
available space in a socket or pipe will still block (unless
O_NONBLOCK is set).
POLLRDHUP (since Linux 2.6.17)
Stream socket peer closed connection, or shut down writing
half of connection. The _GNU_SOURCE feature test macro must
be defined (before including any header files) in order
to obtain this definition.
POLLERR
Error condition (only returned in revents; ignored in events).
This bit is also set for a file descriptor referring to the
write end of a pipe when the read end has been closed.
POLLHUP
Hang up (only returned in revents; ignored in events). Note
that when reading from a channel such as a pipe or a stream
socket, this event merely indicates that the peer closed its
end of the channel. Subsequent reads from the channel will
return 0 (end of file) only after all outstanding data in the
channel has been consumed.
POLLNVAL
Invalid request: fd not open (only returned in revents; ignored
in events).
When compiling with _XOPEN_SOURCE defined, one also has the following,
which convey no further information beyond the bits listed above:
POLLRDNORM
Equivalent to POLLIN.
POLLRDBAND
Priority band data can be read (generally unused on Linux).
NAME
epoll - I/O event notification facility
SYNOPSIS
#include
DESCRIPTION
The epoll API performs a similar task to poll(2): monitoring multiple
file descriptors to see if I/O is possible on any of them. The epoll
API can be used either as an edge-triggered or a level-triggered
interface and scales well to large numbers of watched file descriptors.
The following system calls are provided to create and manage an epoll
instance:
* epoll_create(2) creates a new epoll instance and returns a file
descriptor referring to that instance. (The more recent
epoll_create1(2) extends the functionality of epoll_create(2).)
* Interest in particular file descriptors is then registered via
epoll_ctl(2). The set of file descriptors currently registered
on an epoll instance is sometimes called an epoll set.
* epoll_wait(2) waits for I/O events, blocking the calling thread
if no events are currently available.
epoll_create
NAME
epoll_create, epoll_create1 - open an epoll file descriptor
SYNOPSIS
#include
/*
size 以size宽度处理?随便给个正数?
return 成功返回文件描述符 失败返回-1
*/
int epoll_create(int size);
int epoll_create1(int flags);
DESCRIPTION
epoll_create() creates a new epoll(7) instance. Since Linux 2.6.8,
the size argument is ignored, but must be greater than zero; see NOTES
below.
epoll_create() returns a file descriptor referring to the new epoll
instance. This file descriptor is used for all the subsequent calls
to the epoll interface. When no longer required, the file descriptor
returned by epoll_create() should be closed by using close(2). When
all file descriptors referring to an epoll instance have been closed,
the kernel destroys the instance and releases the associated resources for reuse.
epoll_ctl
NAME
epoll_ctl - control interface for an epoll file descriptor
SYNOPSIS
#include
/*
epfd epoll实例id
op 操作 指定动作
fd opt操作文件描述符 指定对象
event 事件 指定事件
*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//opt 的行为
EPOLL_CTL_ADD
Register the target file descriptor fd on the epoll instance
referred to by the file descriptor epfd and associate the event
event with the internal file linked to fd.
EPOLL_CTL_MOD
Change the event event associated with the target file
descriptor fd.
EPOLL_CTL_DEL
Remove (deregister) the target file descriptor fd from the
epoll instance referred to by epfd. The event is ignored and
can be NULL (but see BUGS below).
epoll_wait
NAME
epoll_wait, epoll_pwait - wait for an I/O event on an epoll file descriptor
SYNOPSIS
#include
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event *events,
int maxevents, int timeout,
const sigset_t *sigmask);
/******************************************************************************
* @file relay.c
* @brief 非阻塞有限状态机数据中继简单实现,
* 可以通过状态机实现两个终端的数据同步
* 使用epoll监视文件名描述符行为 解决忙等问题
* @author wangs7__
* @date 2020/10/08
*
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define TTY1 "/dev/tty11" //设备1
#define TTY2 "/dev/tty12" //设备2
#define BUFSIZE 1024 //缓冲区大小
//状态图
enum{
STATE_R = 1, //写状态
STATE_W , //读状态
STATE_AUTO,
STATE_Ex, //出错状态
STATE_T //退出态
};
//状态机结构体
struct rel_fsm_st {
int state; //状态描述变量
int sfd; //源文件描述符
int dfd; //目标文件描述符
int len; //读取的字节数
int pos; //写指针
char buf[BUFSIZE]; //读写缓冲区
char *errstr; //报错提示
};
/**
* @name fsm_driver
* @brief 状态机驱动函数,推动状态机运转
* @param fsm 状态机结构体指针
* @return void
*/
static void fsm_driver(struct rel_fsm_st *fsm){
int ret; //写函数返回值
//开关选择语句 状态机分支
switch (fsm->state)
{
//R态-------------------------------------------------------------------
case STATE_R:
fsm->len = read(fsm->sfd, fsm->buf, BUFSIZE);//从源文件读取字符
if(fsm->len == 0){ //读取文件为空 转T态
fsm->state = STATE_T;
}
else if (fsm->len < 0){ //读取失败
if(errno == EAGAIN){ //假错 保持R态
fsm->state = STATE_R;
}
else{ //真错 转Ex态
fsm->errstr = "read()";
fsm->state = STATE_Ex;
}
}
else{ //读成功 转W态
fsm->pos = 0; //写指针初始化
fsm->state = STATE_W;
}
break;
//W态-------------------------------------------------------------------
case STATE_W:
ret = write(fsm->dfd, fsm->buf+fsm->pos, fsm->len);//从缓冲区向目标文件写入
if(ret < 0){ //写错误
if(errno == EAGAIN){ //假错 保持写
fsm->state = STATE_W;
}
else{ //真错 转Ex态
fsm->errstr = "write()";
fsm->state = STATE_Ex;
}
}
else{ //写成功
fsm->len -= ret; //写剩余长度
fsm->pos += ret; //写指针位置更新
if(fsm->len == 0){ //写完 转读态
fsm->state = STATE_R;
}
else{ //未写完 保持写
fsm->state = STATE_W;
}
}
break;
//Ex态------------------------------------------------------------------
case STATE_Ex:
perror(fsm->errstr); //报错提示
fsm->state = STATE_T; //转T态
break;
//T态-------------------------------------------------------------------
case STATE_T:
/* do something */
break;
//---------------------------------------------------------------------
default:
abort();
break;
}
}
/**
* @name relay
* @brief 数据中继函数
* @param fd1 设备1文件描述符
* @param fd2 设备2文件描述符
* @return void
*/
void relay(int fd1, int fd2){
int fd1_save, fd2_save; //原始文件打开状态
struct rel_fsm_st fsm12, fsm21; //1->>2状态机 和 2->1状态机
int epfd;
struct epoll_event ev;
//添加非阻塞打开方式
fd1_save = fcntl(fd1, F_GETFL);
fcntl(fd1, F_SETFL, fd1_save | O_NONBLOCK);
fd2_save = fcntl(fd2, F_GETFL);
fcntl(fd2, F_SETFL, fd2_save | O_NONBLOCK);
//初始化状态机
fsm12.state = STATE_R;
fsm12.sfd = fd1;
fsm12.dfd = fd2;
fsm21.state = STATE_R;
fsm21.sfd = fd2;
fsm21.dfd = fd1;
epfd = epoll_create(10);
ev.events = 0;
ev.data.fd = fd1;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd1, &ev);
ev.events = 0;
ev.data.fd = fd2;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd2, &ev);
//状态机运转
while (fsm12.state != STATE_T ||fsm21.state != STATE_T){
// 布置监视任务
ev.events = 0;
ev.data.fd = fd1;
if(fsm12.state == STATE_R){
ev.events |= EPOLLIN;
}
if(fsm21.state == STATE_W){
ev.events |= EPOLLOUT;
}
epoll_ctl(epfd, EPOLL_CTL_MOD, fd1, &ev);
ev.events = 0;
ev.data.fd = fd2;
if(fsm12.state == STATE_W){
ev.events |= EPOLLOUT;
}
if(fsm21.state == STATE_R){
ev.events |= EPOLLIN;
}
epoll_ctl(epfd, EPOLL_CTL_MOD, fd2, &ev);
//监视
if(fsm12.state < STATE_AUTO || fsm21.state < STATE_AUTO){
while( epoll_wait(epfd, &ev, 1, -1) ){
if (errno == EINTR){
continue;
}
perror("epoll_wait:");
exit(1);
}
}
//查看监视结果
if( ev.data.fd == fd1 && ev.events & EPOLLIN \
|| ev.data.fd == fd2 && ev.events & EPOLLOUT\
|| fsm12.state > STATE_AUTO)
fsm_driver(&fsm12);//驱动 1->2
if( ev.data.fd == fd2 && ev.events & EPOLLIN \
|| ev.data.fd == fd1 && ev.events & EPOLLOUT\
|| fsm21.state > STATE_AUTO)
fsm_driver(&fsm21);//驱动 2->1
}
//回复文件原始打开状态
fcntl(fd1, F_SETFL, fd1_save);
fcntl(fd2, F_SETFL, fd2_save);
close(epfd);
}
//主函数
int main(int argc, char *argv[]){
int fd1, fd2;
fd1 = open(TTY1, O_RDWR);
/* 如果出错 */
write(fd1, "TTY1\n", 5);
fd2 = open(TTY2, O_RDWR | O_NONBLOCK);
/* 如果出错 */
write(fd2, "TTY2\n", 5);
//数据中继驱动函数
relay(fd1, fd2);
close(fd1);
close(fd2);
exit(0);
}