Linux中的IO模型
-
阻塞IO:最常用、最简单,但效率最低(read、write) -
非阻塞IO:防止进程阻塞在IO操作上,但必须使用轮询(浪费cpu资源) -
***IO多路复用:同时对多个IO进行操作,效率比阻塞高、浪费的cpu资源比非阻塞少 -
信号驱动IO:一种异步通信
int fcntl(int fd, int cmd, ... );
{
更改模式(如阻塞改为非阻塞)
fd:文件描述符
cmd:F_GETFD(空白)返回(作为函数结果)文件描述符;arg参数被忽略。
F_SETFD (int)将文件描述符标志设置为该值指定的参数。
arg:F_SETFD中使用
F_GETFD模式的返回值可作参数,与阻塞,非阻塞等模式按位或,可更改模式
ex:将标准输入改为非阻塞模式
{
int flag = fcntl(0, F_GETFL, 0);
flag |= O_NONBLOCK;
fcntl(0, F_SETFL, flag);
}
}
IO多路复用的思路
-
先构造一张描述符的集合表 -
使用一个函数,检测该表中哪一个或多个 文件描述符可进行IO操作 该函数就立即返回 该文件描述符的标志 -
根据该函数 返回的哪一个文件描述符,就可以进行IO操作 -
select、poll、epoll -
select缺点,主要是: 其一,每次调用select都需要将进程加入到所有监视socket的等待队列,每次唤醒都需要从每个队列中移除。这里涉及了两次遍历,而且每次都要将整个fds列表传递给内核,有一定的开销。正是因为遍历操作开销大,出于效率的考量,才会规定select的最大监视数量,默认只能监视1024个socket。 其二,进程被唤醒后,程序并不知道哪些socket收到数据,还需要遍历一次。 那么,有没有减少遍历的方法?有没有保存就绪socket的方法?这两个问题便是epoll技术要解决的。
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
{
nfds : 为需要加入io描述符集的最大描述符+1
readfds:读描述符集合表
writefds:写描述符集合表
exceptfds:其他描述符集合表 不需要时null
timout:NULL时为阻塞,
0为非阻塞
》0表在当前time时间中阻塞,超过time后,立即返回
返回值:
timeout = null时 失败返回-1
timeout = time时,失败返回-1,超时返回0
值得注意的是每次调用select函数时,最好清理一下所对应的描述符文件。
}
void FD_CLR(int fd, fd_set *set);
将fd从set里清除
int FD_ISSET(int fd, fd_set *set);
判断fd是否在set中(调用select函数后,set中会对所触发的fd进行标记),在返回真,不在返回0
void FD_SET(int fd, fd_set *set);
将fd加入到set中
void FD_ZERO(fd_set *set);
删除fd中的所有文件描述符
同类型的描述符(读,写)装到同类型的集合表中.
fd:文件描述符 open打开的
set:一个文件描述符集合
int ret = poll(pfd, 2, 5000);
{
pfd[0].fd = 0;
pfd[0].events = POLLIN;
if( pfd[i].revents & pfd[i].events ){
if( pfd[i].fd == 0){
struct polfd {
.fd,
.events,
.revents
}
}
TCP服务器端的并发
代码位置:$HOME/220304/05_network/day3/
poll/select/线程/进程
epoll
简单来说,就是epoll维护了一个红黑树和一个链表,需要监听的socket添加到红黑树上,哪个socket有事件过来,就把它加到链表上,然后发给用户通知。用户直接遍历这个链表,挨个处理即可。 而poll和select只返回有时间的fd的数量,并没有说是哪个fd,所以还需要挨个遍历所有fd去检查
https:
int epoll_create(int size);
失败返回 -1
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
{
epfd : create的返回值
op: EPOLL_CTL_ADD:往事件表中注册fd上的事件
EPOLL_CTL_MOD:修改fd上的注册事件
EPOLL_CTL_DEL:删除fd上的注册事件
fd : 要操作的文件描述符
event:指定事件
}
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
{
epfd:creat返回值
events:存放返回事件的数组首地址
maxevents:最多返回个数
timeout:指定-1的超时将导致epoll_wait()无限阻塞,而指定等于0的超时将导致epoll_wait()立即返回,即使没有可用的事件。
返回值:超时返回0;
错误返回-1
成功返回可操作文件数量
}
struct epoll_event {
uint32_t events;
epoll_data_t data;
};
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
events事件
EPOLLONESHOT
对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多出发其上注册的一个可读,可写或异常事件,且只能触发一次。
使用:
注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置这个socket上的EPOLLONESHOT事件,以确保这个socket下一次可读时,其EPOLLIN事件能被触发,进而让其他工作线程有机会继续处理这个sockt。
EPOLLET
使用EPOLLET标志的应用程序应该使用非阻塞的文件描述符,以避免阻塞的读或写饿死正在运行的任务
其他看man手册
https:
7 #include <stdio.h>
8 #include <string.h>
9 #include <stdlib.h>
10 #include <strings.h>
11 #include <unistd.h>
12 #include <sys/epoll.h>
13 #include <sys/types.h>
14 #include <sys/stat.h>
15 #include <fcntl.h>
16
17 int main(int argc, char *argv[])
18 {
19 int epfd, nfds;
20
21 int fd = open("/dev/input/mouse0", O_RDONLY);
22 if(fd < 0)
23 {
24 perror("open");
25 return -1;
26 }
27 struct epoll_event ev[2], events[5];
28
29 epfd = epoll_create(2);
30
31 ev[0].data.fd = 0;
32
33 ev[0].events = EPOLLIN ;
34
35 ev[1].data.fd = fd;
36
37 ev[1].events = EPOLLIN | EPOLLET;
38
39 epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &ev[0]);
40 epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev[1]);
41
42 for(;;)
43 {
44 nfds = epoll_wait(epfd, events, 5, -1);
45 for(int i = 0; i < nfds; i++)
46 {
47 if(events[i].data.fd == 0)
48 printf("aaa\n");
49 if(events[i].data.fd == fd)
50 printf("bbb\n");
51
52 }
53 }
54
55
56
57 return 0;
58 }
网络属性设置
int setsockopt(int sockfd, int level, int optname, const void * optval, socklent optlen)
{
sockfd:套接字
level: SOL_SOCKET :通用套接字选项
IPPROTO_IP :IP选项
IPPROTO_TCP:TCP选项
optname:如下图
optval|optlen: 根据数据类型决定,如:类型为int,那么就设置一个int on 为optval,on为1表示打开,
optlen就是sizeof(on),类型为sturct timeval,就是设置超时时间
}
|