reactor的应用

1.梳理一下reactor网络编程

2.编程细节 返回值以及错误码

3.redis nginx,memcached reactor具体应用

网络编程关注的问题

 

1. 连接的建立

分为两种:服务端处理接受客户端的连接,服务端作为客户端连接第三方服务;

int clientfd = accept(listenfd,addr,sz);
if (clientfd == -1 && errno == EWOLDBLOCK){
//说明全连接队列为空    
}
int connectfd = socket(AF_INET,SOCK_STREAM,0);
int ret = connect(connectfd, (struct sockaddr *)&addr, sizeof(addr));
errno = EINREOGRESS //正在建立连接
errno = EISCONN //连接建立成功

 

2.连接的断开

分为两种:主动断开和被动断开

close(fd)//主动断开
shutdown(fd,SHUT_RDWR);
shutdown(fd,SHUT_RD);//主动关闭本地读端,对端写段关闭
shutdown(fd,SHUT_WR);//主动关闭本地写端,对端读段关闭
//被动:读端关闭
//有的网络编程需要支持半关闭状态
int n = read(fd, buf, sz);
if (n == 0) {
   close_read(fd);//被动关闭读端
   //close(fd);                
}
int n =write(fd,buf,sz);
if (n == -1 && errno == EPIPE){
  close_write(fd); //被动关闭写端
  //close (fd);
}

 

3.消息的到达

从读缓冲区中读取数据

int n = read(fd,buf,sz);//n可以小于sz
int n = read(fd, buf, sz);
if (n < 0) { // n == -1
    if (errno == EINTR || errno == EWOULDBLOCK)
        break;
    close(fd);
} else if (n == 0) {
    close(fd);
} else {
// 处理 buf
}

如果n=-1,并且errno为EWOULDBLOCK表示都缓冲区为空,或者EINRT表示正向的错误,下次会继续读,如果是其他错误则close

 

4.消息发送完毕

往写缓冲去写数据 

int n=write (fd,buf,sz);//n必须等于sz才算写成功
int n = write(fd, buf, dz);
if (n == -1) {
      if (errno == EINTR || errno == EWOULDBLOCK) {
            return;
      }
      close(fd);
}

如果返回值n=-1并且errno=EWOULDBLOCK,表示写缓冲区已经满了,下一次再去写。

 

网络IO职责

检测IO

io 函数本身可以检测 io 的状态;但是只能检测一个 fd 对应的状态;io 多路复用可以同时检测多io的状态;区别是:io函数可以检测具体状态;io 多路复用只能检测出可读、可写、错误、断开等笼统的事件;

 

操作IO

只能使用io函数来进行操作;分为两种操作方式:阻塞io和非阻塞io;

 

阻塞IO和非阻塞IO

  • 阻塞在网络线程;
  • 连接的 fd 阻塞属性决定了 io 函数是否阻塞;
  • 具体差异在:io 函数在数据未到达时是否立刻返回;
// 默认情况下,fd 是阻塞的,设置非阻塞的方法如下;
int flag = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flag | O_NONBLOCK);

 

 

 阻塞和非阻塞正在数据拷贝阶段都是阻塞的,不同的是在数据准备阶段消息到达之前是不是的立刻返回

 tips:最好设置为非阻塞的,比如设置读事件,此时取消,但读事件已经爆出来了,读缓冲区为空,此时如果读则会一直阻塞。

IO多路复用

io多路复用只负责检测io,不负责操作io。epoll:事件管理器

结构以及接口

struct eventpoll {
// ...
    struct rb_root rbr; // 管理 epoll 监听的事件
    struct list_head rdllist; // 保存着 epoll_wait 返回满⾜条件的事件
// ...
};
struct epitem {
// ...
    struct rb_node rbn; // 红⿊树节点
    struct list_head rdllist; // 双向链表节点
    struct epoll_filefd ffd; // 事件句柄信息
    struct eventpoll *ep; // 指向所属的eventpoll对象
    struct epoll_event event; // 注册的事件类型
// ...
};
struct epoll_event {
    __uint32_t events; // epollin epollout epollel(边缘触发)
    epoll_data_t data; // 保存 关联数据
};
typedef union epoll_data {
    void *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
}epoll_data_t;
int epoll_create(int size);
/**
    op:
    EPOLL_CTL_ADD
    EPOLL_CTL_MOD
    EPOLL_CTL_DEL
    event.events:
    EPOLLIN 注册读事件
    EPOLLOUT 注册写事件
    EPOLLET 注册边缘触发模式,默认是水平触发
*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
/**
    events[i].events:
    EPOLLIN 触发读事件
    EPOLLOUT 触发写事件
    EPOLLERR 连接发生错误
    EPOLLRDHUP 连接读端关闭
    EPOLLHUP 连接双端关闭
*/
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int
timeout);

 调用 epoll_create 会创建一个 epoll 对象;调用 epoll_ctl 添加到 epoll 中的事件都会与网卡驱动程序建立回调关系,相应事件触发时会调用回调函数 ( ep_poll_callback ),将触发的

事件拷贝到 rdlist 双向链表中;调用 epoll_wait 将会把 rdlist 中就绪事件拷贝到用户态中;

 

epoll编程

连接建立

// 一、处理客户端的连接
// 1. 注册监听 listenfd 的读事件
struct epoll_event ev;
ev.events |= EPOLLIN;
epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &ev);
// 2. 当触发 listenfd 的读事件,调用 accept 接收新的连接
int clientfd = accept(listenfd, addr, sz);
struct epoll_event ev;
ev.events |= EPOLLIN;
epoll_ctl(efd, EPOLL_CTL_ADD, clientfd, &ev);
// 二、处理连接第三方服务
// 1. 创建 socket 建立连接
int connectfd = socket(AF_INET, SOCK_STREAM, 0);
connect(connectfd, (struct sockaddr *)&addr, sizeof(addr));
// 2. 注册监听 connectfd 的写事件
struct epoll_event ev;
ev.events |= EPOLLOUT;
epoll_ctl(efd, EPOLL_CTL_ADD, connectfd, &ev);
// 3. 当 connectfd 写事件被触发,连接建立成功
if (status == e_connecting && e->events & EPOLLOUT) {
    status == e_connected;
    // 这里需要把写事件关闭
    epoll_ctl(epfd, EPOLL_CTL_DEL, connectfd, NULL);
}

连接断开

if (e->events & EPOLLRDHUP) {
// 读端关闭
    close_read(fd);
    close(fd);
} 
if (e->events & EPOLLHUP) {
// 读写端都关闭
    close(fd);
}

数据到达

// reactor 要用非阻塞io
// select
if (e->events & EPOLLIN) {
while (1) {
    int n = read(fd, buf, sz);
    if (n < 0) {
        if (errno == EINTR)//被信号打断
            continue;
        if (errno == EWOULDBLOCK)
            break;
        close(fd);
    } else if (n == 0) {
        close_read(fd);
// close(fd);
    }     /
/ 业务逻辑了
    }
}

数据发送完毕

int n = write(fd, buf, dz);
if (n == -1) {
    if (errno == EINTR)
        continue;
    if (errno == EWOULDBLOCK) {
        struct epoll_event ev;
        ev.events = EPOLLOUT;
        epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
    } 
    close(fd);
} /
/ ...
if (e->events & EPOLLOUT) {
    int n = write(fd, buf, sz);
    //...
    if (n > 0) {
        epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
    }
}

 

reactor应用

组成:io多路复用+非阻塞io

将对io的处理转化成对事件的处理

 

1/redis

单线程  socket在主线程,计算在主线程,read与decode,encode与send在多线程(增加速度,减轻主线程的压力)

为什么是单线程?

因为kv结构,v采用多种数据结构,加锁比较繁琐

水平触发

 

 

2/memcached

多线程,kv结构,固定结构,加锁方便,

水平触发,需要界定数据包

3/nginx

多进程,需要界定数据包, 惊群现象,使用共享内存加锁,负载均衡

边沿触发:反向代理,不需要界定数据包

posted @ 2022-06-28 21:49  放弃吧  阅读(85)  评论(0编辑  收藏  举报