博客园  :: 首页  :: 新随笔  :: 管理

2.2.2 redis/memcached/nginx网络组件

Posted on 2025-05-19 23:19  wsg_blog  阅读(32)  评论(0)    收藏  举报

Linux c/c++ 高性能后端

redis/memcached/nginx网络组件

网络编程关注的问题

连接的建立、连接的断开、消息的到达、消息发送完毕

连接的建立

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

int clientfd = accept(listenfd, addr, sz);
// 举例为非阻塞io,阻塞io成功直接返回0;
int connectfd = socket(AF_INET, SOCK_STREAM, 0);
int ret = connect(connectfd, (struct sockaddr*)&addr, sizeof(addr));
// ret == -1 && errno == EINPROGRESS 正在建立连接
// ret == -1 && errno = EISCONN 连接建立成功

连接的断开

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

// 主动关闭
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);
// write()
// close(fd);
}
// 被动:写端关闭
int n = write(fd, buf, sz);
if (n == -1 && errno == EPIPE) {
close_write(fd);
// close(fd);
}

消息的到达

从缓冲区中读取数据

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
}

消息发送完毕

往写缓冲区中写数据

int n = write(fd, buf, dz);
if (n == -1) {
if (errno == EINTR || errno == EWOULDBLOCK) {
return;
}
close(fd);
}

网络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);

IO多路复用

io多路复用只负责检测io,不负责操作io;
int n = epoll_wait(epfd, evs, sz, timeout);
timeout = -1 一直阻塞直到网络事件到达;
timeout = 0 不管是否有事件就绪立刻返回;
timeout = 1000 最多等待1s,如果1s内没有事件触发则返回;

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中的就绪事件拷贝到用户态中

reactor编程

连接建立

// 一、处理客户端的连接
// 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);
    return;
  }
  close(fd);
}
// ...
if (e->events & EPOLLOUT) {
  int n = write(fd, buf, sz);
  //...
  if (n == sz) {
    epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
  }
}

redis、memcached、nginx网络组件

上边主要在说网络、io、reactor;要知道想要快速看懂redis、memcached、nginx中的网络组件上边是必备的知识;以至于使用中哪里出了问题也能较容易的去定位和解决。

redis、memcached、nginx中的网络组件都是基于reactor去实现的,区别就在于redis是单reactor、memcached是多线程的reactor、nginx是多进程reactor模型

redis、memcached、nginx都为开源应用,网络组件为对reactor(epoll)的封装,按照网络、io、epoll相关的接口去理清网络模块的代码就会容易很多,相关的接口在上文中都有提及,只要对linux网络足够熟悉,读它们的开源代码难度并不大