分类 Linux 下的文章

epoll_详解


@epoll 详解和记录

创建epoll流程

#define EPOLL_SIZE 5000//这个为告诉内核需要监听的数量是多少,这个只需要大于0 就可以了因为内核会更加实际数量自动扩充,为了兼容linux平台 大于等于0 就可以了
int epfd = epoll_create(EPOLL_SIZE);

struct epoll_event ev;//创建事件
ev.data.fd = fd;//fd 一般为socketfd资源
ev.events = EPOLLIN;//将socketfd绑定为read()操作

epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev);//EPOLL_CTL_ADD 为 在epfd中注册指定的fd文件描述符并能把event和fd关联起来

//将文件描述符设置为非阻塞方式(利用fcntl函数)
 fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)| O_NONBLOCK);
 
 //主循环
 while(1){
     //events   => epoll_event 结构体
    int count = epoll_wait(epfd,events,EPOLL_SIZE,-1);//计算epoll就绪的事件 返回int  总数
    //处理就绪事件
    for(int i = 0 ; i < count ; ++i){
        int fd = events[i].data.fd;
        //fd 如果 等于 socket 返回的  sockfd  则表明是新用户加入 需要recv()其他情况就是已存在用户发发送的消息 调用send就可以了
        if(fd == sockfd){
        //新用户加入 
        recv(sockfd,clientfd
        }
    }
 }

@epoll_event 结构体

typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;

struct epoll_event {
    __uint32_t events;      /* Epoll events */
    epoll_data_t data;      /* User data variable */
};

events 宏定义

  • EPOLLIN - 当关联的文件可以执行 read ()操作时。
  • EPOLLOUT - 当关联的文件可以执行 write ()操作时。
  • EPOLLRDHUP - (从 linux 2.6.17 开始)当socket关闭的时候,或者半关闭写段的(当使用边缘触发的时候,这个标识在写一些测试代码去检测关闭的时候特别好用)
  • EPOLLPRI - 当 read ()能够读取紧急数据的时候。
  • EPOLLERR - 当关联的文件发生错误的时候,epoll_wait() 总是会等待这个事件,并不是需要必须设置的标识。
  • EPOLLHUP - 当指定的文件描述符被挂起的时候。epoll_wait() 总是会等待这个事件,并不是需要必须设置的标识。当socket从某一个地方读取数据的时候(管道或者socket),这个事件只是标识出这个已经读取到最后了(EOF)。所有的有效数据已经被读取完毕了,之后任何的读取都会返回0(EOF)。
  • EPOLLET - 设置指定的文件描述符模式为边缘触发,默认的模式是水平触发。
  • EPOLLONESHOT - (从 linux 2.6.17 开始)设置指定文件描述符为单次模式。这意味着,在设置后只会有一次从epoll_wait() 中捕获到事件,之后你必须要重新调用 epoll_ctl() 重新设置。

@epoll_ctl 设置epoll事件

include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epfd  为 epoll_create 创建的
fd 为 scoketfd
有效的op值有以下几种:
EPOLL_CTL_ADD 在epfd中注册指定的fd文件描述符并能把event和fd关联起来。
EPOLL_CTL_MOD 改变*** fd和evetn***之间的联系。
EPOLL_CTL_DEL 从指定的epfd中删除fd文件描述符。在这种模式中event是被忽略的,并且为可以等于NULL。

@epoll_wait 唤起事件

函数返回唤起的事件数量

epoll_wait(epfd,events,EPOLL_SIZE,-1)
return count;


位运算


位运算

字节

1.一个int 占4个字节
2.一个char 占1个字节内存
3.一个字节由8个二进制位组成

1字节最大 对应二进制 8个111 => 最大255

运算公式

1.% 取模与 & 转换

  • 取模 和 按位与在 条件为2的平方根的时候可以互相转换
  • 因为使用按位与的效率要高很多

a % (2^n) == a & (2^n -1)

运算符

& 按位与

有0 则 0
0 & 0 = 0;

0 & 1 = 0;
1 & 1 = 1;

unsigned int temp = 38 & 22;
//进行二进制进行按位与
100110 //38
010110 // 22
------------------
000110 => 6
38 & 22 = 6;

| 按位或

有1 则 1

0 | 0 = 0;
0 | 1 = 1;
1 | 1 = 1;

unsigned int temp = 38 | 22;
//进行二进制进行按位或
100110 //38
010110 // 22
---------------
110110 => 54
38 & 22 = 6;

^ 按位异或

相同则0 不同则 1

0 ^ 0 = 0;
0 ^ 1 = 1;
1 ^ 1 = 0;

unsigned int temp = 38 ^ 22;
100110 //38
010110 // 22
---------------
110000 => 48
38 ^ 22 = 48;

~ 取反

对单个数字进行按位取反

unsigned int temp = ~38;
00000000,00000000,00000000,00100110//38
-------------------------------------------------------
1111111,11111111,11111111,11011001;//4294657357 42亿。。。。

<< 左移

将一个数的二进制位左移若干个位
每移动一个 都是乘以2

unsigned int temp = 15 << 1;//30
unsigned int temp = 15 << 2;//60

>> 右移

将一个数的二进制右移若干个位
每右移一位都相当于除以2
unsigned int temp = 15 >> 1;//7
unsigned int temp = 15 >> 2;//3
1111 15

||
v

/0111 7

位运算符结合使用

&= |= >>= <<= ^=

a &= b ===> a = a&b

案列

#define BIT(x)  (1<<(x))
int main(){
    unsigned int task;//有32个位    
    BIT(2) => 1 << 2;    
    
    
    return 0;
}

epoll_多路复用


epoll 多路复用

@ET模式 && @LT模式

et 边缘触发 lt 水平触发

他们的最大的区别有两点

1.水平模式LT 支持处理阻塞非阻塞套接字

  • ET 边缘模式 只支持处理非阻塞套接字
  1. 水平模式 在处理epol_wait 返回的事件时,如果没有处理完,则在次调用epoll_wait 依然可以获取这个事件
  • 边缘模式 无法再次获取,

I/0复用-SELECT


I/O复用 - select

@select 函数

select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)

@timeout 参数的意义

  1. 如果传入NULL 空指针,则select函数永远等待下去
  2. timeval 结构体
struct timeval {
    long tv_sec; /*seconds*/
    long tv_usec; /*microseconds*/
}
  • 秒和微妙都为0 的时候,select函数不用等待,直接返回,检查描述符后立即返回
  1. 在指定时间内,若有一个描述符准备好I/O后直接返回

example

//创建事件集
fd_set rset;
//添加监听套接字句柄到  事件集
FD_SET(listenfd,&rset);
//直接进入事件循环
for(;;){
    //一直阻塞  到有事件发生
    nready = select(maxfd + 1,&rset,NULL,NULL,NULL);
    ...下面说明有事件发生
    //先判断是否是监听句柄发生的事件
    if(FD_ISSET(listenfd,&rset)){
        //这里处理accept 建立新连接
        ....
        //新的连接添加到事件集里
        FD_SET(connfd,&rset);
    }
    for(i = 0 ;i <= maxi;i++){
        if((sockfd = client[i]) <0){
            continue;
        }
        进行连接的事件处理
    }
}

I/0复用——PSELECT


I/O复用 - pselect

@pselect 函数

select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timespec *timeout,const sigset_t *sigmask)

pselect 相较于 select 多了一个参数:信号掩码的指针,而且pselect支持纳秒级别的time超时机制,而select是微秒

@timeout 参数的意义

  1. 如果传入NULL 空指针,则select函数永远等待下去
  2. timeval 结构体
struct timeval {
    long tv_sec; /*seconds*/
    long tv_usec; /*microseconds*/
}
  • 秒和微妙都为0 的时候,select函数不用等待,直接返回,检查描述符后立即返回
  1. 在指定时间内,若有一个描述符准备好I/O后直接返回

example

//创建事件集
fd_set rset;
//添加监听套接字句柄到  事件集
FD_SET(listenfd,&rset);
//直接进入事件循环
for(;;){
    //一直阻塞  到有事件发生
    nready = select(maxfd + 1,&rset,NULL,NULL,NULL);
    ...下面说明有事件发生
    //先判断是否是监听句柄发生的事件
    if(FD_ISSET(listenfd,&rset)){
        //这里处理accept 建立新连接
        ....
        //新的连接添加到事件集里
        FD_SET(connfd,&rset);
    }
    for(i = 0 ;i <= maxi;i++){
        if((sockfd = client[i]) <0){
            continue;
        }
        进行连接的事件处理
    }
}