网络编程流程及redix、nginx介绍(上)

在介绍select\poll\epoll及阻塞、非阻塞I\O的那篇文章里,着重点在于实现,本篇文章主要讲一下流程。

流程可以分为连接的建立,消息的到达,消息发送完毕,连接的断开 。之前讲的fd的阻塞与非阻塞属性是可以进行设置的,但是非阻塞其实也只是在数据准备阶段为非阻塞,如下图:

在数据拷贝阶段两者都是阻塞的。

I/O多路复用,只负责检测I/O,如果说数据已准备,再通过系统调用来操作I/O。

数据已准备对于上述四个过程(连接建立、消息到达、消息发送完毕、连接断开)有着不同的意义,同时调用的I/O函数也是有差别的。

以epoll为例:

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

其中

struct epoll_event {
  __uint32_t events; // epollin epollout epollel(边缘触发)
  epoll_data_t data; // 保存 关联数据
};

在网络编程中是非常重要的。

对红黑树和双向链表等数据结构在数据结构专栏会有介绍。

调用  epoll_create 会创建一个  epoll 对象;调用 epoll_ctl 添加到 epoll 中的事件都会与网
卡驱动程序建立回调关系,相应事件触发时会调用回调函数 ( ep_poll_callback ),将触发的
事件拷贝到  rdlist 双向链表中;调用  epoll_wait 将会把  rdlist 中就绪事件拷贝到用户态
中;

连接的建立:

// 一、处理客户端的连接
// 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);
}

消息到达:

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);
 }
}


版权声明:本文为weixin_42381791原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。