【Linux】异步网络库dyad代码阅读

简介

dyad是一个基于C编写的异步网络库,非常精简,单C文件,仅实现TCP,很适合用来学习Linux网络编程和异步非阻塞处理

链接

Github链接

基于Dyad的echo server实现

我写了一些注释

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "dyad.h"

/* An echo server: Echos any data received by a client back to the client */

static void onData(dyad_Event *e) {
  dyad_write(e->stream, e->data, e->size);
}

static void onAccept(dyad_Event *e) {
  dyad_addListener(e->remote, DYAD_EVENT_DATA, onData, NULL);
  dyad_writef(e->remote, "echo server\r\n");
}

static void onError(dyad_Event *e) {
  printf("server error: %s\n", e->msg);
}

int main(void) {
  dyad_Stream *s;
  dyad_init();//初始化dyad

  s = dyad_newStream();//新建一个流
  dyad_addListener(s, DYAD_EVENT_ERROR,  onError,  NULL);//添加监听
  dyad_addListener(s, DYAD_EVENT_ACCEPT, onAccept, NULL);//添加监听
  dyad_listen(s, 8000);//监听8000端口

  while (dyad_getStreamCount() > 0) {
    dyad_update();//更新
  }

  return 0;
}

设计思想

整体实现是基于select事件链表的方式(也就是它定义的stream)实现的,核心思想还是链表,所以分析也是从它的基础结构dyad_Stream结构体开始,我会试着分析下echo server实现中涉及到的函数.

dyad_Stream结构体

struct dyad_Stream {
  int state, flags;//状态标志
  dyad_Socket sockfd;//文件描述符
  char *address;//地址
  int port;//端口号
  int bytesSent, bytesReceived;//字节发送与接收
  double lastActivity, timeout;//上一次的活动时间,超时时间
  Vec(Listener) listeners;//listeners链表
  Vec(char) lineBuffer;//lineBuffer链表
  Vec(char) writeBuffer;//writeBuffer链表
  dyad_Stream *next;//链表结构
};

作者是这样定义TCP流的,可以看到该结构是存在链表数据结构的next指针,指向下一个dyad_Stream
Vec是实现模板动态数组的一组宏,应该说是这个网络库的核心数据结构了,可以看到这个“模板动态数组”是支持任意数据结构的。

Vec动态数组

static void vec_expand(char **data, int *length, int *capacity, int memsz) {
  if (*length + 1 > *capacity) {
    if (*capacity == 0) {
      *capacity = 1;
    } else {
      *capacity <<= 1;
    }
    *data = dyad_realloc(*data, *capacity * memsz);
  }
}

static void vec_splice(
  char **data, int *length, int *capacity, int memsz, int start, int count
) {
  (void) capacity;
  memmove(*data + start * memsz,
          *data + (start + count) * memsz,
          (*length - start - count) * memsz);
}


#define Vec(T)\
  struct { T *data; int length, capacity; }


#define vec_unpack(v)\
  (char**)&(v)->data, &(v)->length, &(v)->capacity, sizeof(*(v)->data)


#define vec_init(v)\
  memset((v), 0, sizeof(*(v)))


#define vec_deinit(v)\
  dyad_free((v)->data)

//清除长度
#define vec_clear(v)\
  ((v)->length = 0)


#define vec_push(v, val)\
  ( vec_expand(vec_unpack(v)),\
    (v)->data[(v)->length++] = (val) )

//分割
#define vec_splice(v, start, count)\
  ( vec_splice(vec_unpack(v), start, count),\
    (v)->length -= (count) )

这里我举一个例子(就下面这句),简单分析下它是如何实现入队列和扩容的

Listener listener;
vec_push(&stream->listeners, listener);

这里涉及到vec_push,也就是把listener变量压入链表中

#define vec_push(v, val)\
  ( vec_expand(vec_unpack(v)),\
    (v)->data[(v)->length++] = (val) )

可以看到这个宏,里面又包含了vec_unpack这个宏和vec_expand这个函数,展开就是这样

( vec_expand(vec_unpack(&stream->listeners)), (&stream->listeners)->data[(&stream->listeners)->length++] = (listener) )

再展开的话

vec_expand((char**)&(&stream->listeners)->data, &(&stream->listeners)->length, &(&stream->listeners)->capacity, sizeof(*(&stream->listeners)->data));

不得不感叹,作者把宏定义玩出了花~
我们着重看vec_expand函数

static void vec_expand(char **data, int *length, int *capacity, int memsz) {
  if (*length + 1 > *capacity) {//判断容量是不是超
    if (*capacity == 0) {//如果容量是0
      *capacity = 1;//设定容量为1
    } else {
      *capacity <<= 1;//将容量翻倍(右移1表示乘以2)
    }
    *data = dyad_realloc(*data, *capacity * memsz);//重新分配内存,扩容
  }
}

dyad_init

void dyad_init(void) {
#ifdef _WIN32
  WSADATA dat;
  int err = WSAStartup(MAKEWORD(2, 2), &dat);
  if (err != 0) {
    panic("WSAStartup failed (%d)", err);
  }
#else
  /* 当对一个已经停止了的socket写操作时,停止SIGPIPE信号*/
  signal(SIGPIPE, SIG_IGN);
#endif
}

#define SIG_IGN (__p_sig_fn_t)1

这个是dyad库初始化的函数
在linux下这个函数就是把SIGPIPE信号重定向到SIG_IGN(忽略)
这里我把它原来的注释翻译了下,应该是确保socket如果停止了后,如果再对其写操作,不会触发SIGPIPE

就是客户端程序向服务器端程序发送了消息,然后关闭客户端,服务器端返回消息的时候就会收到内核给的SIGPIPE信号

dyad_newStream

初始化一个新的流

static dyad_Stream *dyad_streams;//链表头
....
//新建一个流
dyad_Stream *dyad_newStream(void) {
  dyad_Stream *stream = dyad_realloc(NULL, sizeof(*stream));
  memset(stream, 0, sizeof(*stream));
  stream->state = DYAD_STATE_CLOSED;
  stream->sockfd = INVALID_SOCKET;
  stream->lastActivity = dyad_getTime();
  /* Add to list and increment count */
  stream->next = dyad_streams;//新的元素的下一个指向链表头
  dyad_streams = stream;//链表头指向新的元素
  dyad_streamCount++;
  return stream;
}

可以看到它将新分配的stream插入到的链表中,dyad_streams是链表头,是一个全局静态变量.
在这里插入图片描述

dyad_addListener

添加一个新的监听事件(^ ^就这样翻译吧)

//添加监听
void dyad_addListener(
  dyad_Stream *stream, int event, dyad_Callback callback, void *udata
) {
  Listener listener;
  listener.event = event;
  listener.callback = callback;
  listener.udata = udata;
  vec_push(&stream->listeners, listener);//压入链表(先扩容,然后把数据加进去)
}

将指定的事件,指定的回调函数加到流结构体的listener链表上
这个库包含的事件有

enum {
  DYAD_EVENT_NULL,
  DYAD_EVENT_DESTROY,
  DYAD_EVENT_ACCEPT,
  DYAD_EVENT_LISTEN,
  DYAD_EVENT_CONNECT,
  DYAD_EVENT_CLOSE,
  DYAD_EVENT_READY,
  DYAD_EVENT_DATA,
  DYAD_EVENT_LINE,
  DYAD_EVENT_ERROR,
  DYAD_EVENT_TIMEOUT,
  DYAD_EVENT_TICK
};

dyad_listen

指定流的监听端口号
这个函数主要涉及到socket初始化,底层还套了一层函数dyad_listenEx

//监听
int dyad_listen(dyad_Stream *stream, int port) {
  return dyad_listenEx(stream, NULL, port, 511);
}

///listen底层初始化
int dyad_listenEx(
  dyad_Stream *stream, const char *host, int port, int backlog
) {
  struct addrinfo hints, *ai = NULL;
  int err, optval;
  char buf[64];
  dyad_Event e;

  /* 获取地址信息 Get addrinfo */
  memset(&hints, 0, sizeof(hints));
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags = AI_PASSIVE;
  sprintf(buf, "%d", port);
  err = getaddrinfo(host, buf, &hints, &ai);
  if (err) {
    stream_error(stream, "could not get addrinfo", errno);
    goto fail;
  }
  /* 初始化socket Init socket */
  err = stream_initSocket(stream, ai->ai_family, ai->ai_socktype,
                          ai->ai_protocol);
  if (err) goto fail;
  /* Set SO_REUSEADDR so that the socket can be immediately bound without
   * having to wait for any closed socket on the same port to timeout */
  optval = 1;
  setsockopt(stream->sockfd, SOL_SOCKET, SO_REUSEADDR,
             &optval, sizeof(optval));
  /* Bind and listen */
  err = bind(stream->sockfd, ai->ai_addr, ai->ai_addrlen);
  if (err) {
    stream_error(stream, "could not bind socket", errno);
    goto fail;
  }
  err = listen(stream->sockfd, backlog);
  if (err) {
    stream_error(stream, "socket failed on listen", errno);
    goto fail;
  }
  stream->state = DYAD_STATE_LISTENING;//修改流状态
  stream->port = port;
  stream_initAddress(stream);
  /* Emit listening event */
  e = createEvent(DYAD_EVENT_LISTEN);//创建事件
  e.msg = "socket is listening";
  stream_emitEvent(stream, &e);//触发事件
  freeaddrinfo(ai);
  return 0;
  fail:
  if (ai) freeaddrinfo(ai);
  return -1;
}

这个函数主要干了这些事,我这里不贴其他涉及的函数了,比较多

  1. 获取地址信息
  2. 初始化socket并设置为非阻塞和端口复用
  3. socket绑定和监听
  4. 修改流的状态,设置流的地址信息
  5. 创建事件并触发事件

dyad_getStreamCount

获取流的总数,这个没啥好说的

dyad_update

这个库的核心函数,着重分析这个函数
这个函数就是用来处理整个事件触发机制的

///更新
void dyad_update(void) {
  dyad_Stream *stream;
  struct timeval tv;

  destroyClosedStreams();//销毁掉已经关掉的流
  updateTickTimer();//更新计时器
  updateStreamTimeouts();//更新流的超时计时器

  /* Create fd sets for select() */
  select_zero(&dyad_selectSet);
  //遍历每一个流链表的子元素,根据TCP状态处理select事件
  stream = dyad_streams;
  while (stream) {
    switch (stream->state) {
      case DYAD_STATE_CONNECTED:
        select_add(&dyad_selectSet, SELECT_READ, stream->sockfd);
        if (!(stream->flags & DYAD_FLAG_READY) ||
            stream->writeBuffer.length != 0
        ) {
          select_add(&dyad_selectSet, SELECT_WRITE, stream->sockfd);
        }
        break;
      case DYAD_STATE_CLOSING:
        select_add(&dyad_selectSet, SELECT_WRITE, stream->sockfd);
        break;
      case DYAD_STATE_CONNECTING:
        select_add(&dyad_selectSet, SELECT_WRITE, stream->sockfd);
        select_add(&dyad_selectSet, SELECT_EXCEPT, stream->sockfd);
        break;
      case DYAD_STATE_LISTENING:
        select_add(&dyad_selectSet, SELECT_READ, stream->sockfd);
        break;
    }
    stream = stream->next;
  }

  /* Init timeout value and do select */
  #ifdef _MSC_VER
    #pragma warning(push)
    /* Disable double to long implicit conversion warning,
     * because the type of timeval's fields don't agree across platforms */
    #pragma warning(disable: 4244)
  #endif
  tv.tv_sec = dyad_updateTimeout;
  tv.tv_usec = (dyad_updateTimeout - tv.tv_sec) * 1e6;
  #ifdef _MSC_VER
    #pragma warning(pop)
  #endif
  //监视文件描述符的读/写/异常变化
  select(dyad_selectSet.maxfd + 1,
         dyad_selectSet.fds[SELECT_READ],
         dyad_selectSet.fds[SELECT_WRITE],
         dyad_selectSet.fds[SELECT_EXCEPT],
         &tv);

  /* Handle streams */
  //遍历每一个流链表的子元素,根据select的状态做事情
  stream = dyad_streams;
  while (stream) {
    switch (stream->state) {

      case DYAD_STATE_CONNECTED://连接上
        if (select_has(&dyad_selectSet, SELECT_READ, stream->sockfd)) {
          stream_handleReceivedData(stream);
          if (stream->state == DYAD_STATE_CLOSED) {
            break;
          }
        }
        /* Fall through */

      case DYAD_STATE_CLOSING://关闭
        if (select_has(&dyad_selectSet, SELECT_WRITE, stream->sockfd)) {
          stream_flushWriteBuffer(stream);
        }
        break;

      case DYAD_STATE_CONNECTING://正在连接
        if (select_has(&dyad_selectSet, SELECT_WRITE, stream->sockfd)) {
          /* Check socket for error */
          int optval = 0;
          socklen_t optlen = sizeof(optval);
          dyad_Event e;
          getsockopt(stream->sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen);
          if (optval != 0) goto connectFailed;//判断有没有socket错误
          /* Handle succeselful connection */
          stream->state = DYAD_STATE_CONNECTED;
          stream->lastActivity = dyad_getTime();
          stream_initAddress(stream);
          /* Emit connect event */
          e = createEvent(DYAD_EVENT_CONNECT);
          e.msg = "connected to server";
          stream_emitEvent(stream, &e);
        } else if (
          select_has(&dyad_selectSet, SELECT_EXCEPT, stream->sockfd)
        ) {
          /* Handle failed connection */
connectFailed:
          stream_error(stream, "could not connect to server", 0);
        }
        break;

      case DYAD_STATE_LISTENING://正在监听
        if (select_has(&dyad_selectSet, SELECT_READ, stream->sockfd)) {
          stream_acceptPendingConnections(stream);
        }
        break;
    }

    /* If data was just now written to the stream we should immediately try to
     * send it */
    if (
      stream->flags & DYAD_FLAG_WRITTEN &&
      stream->state != DYAD_STATE_CLOSED
    ) {
      stream_flushWriteBuffer(stream);
    }

    stream = stream->next;
  }
}

在分析它之前,我们先看看SelectSet结构体,因为这个和后面的select有很大的关系

SelectSet

enum {
  SELECT_READ,
  SELECT_WRITE,
  SELECT_EXCEPT,
  SELECT_MAX
};
//Select组
typedef struct {
  int capacity;
  dyad_Socket maxfd;
  fd_set *fds[SELECT_MAX];
} SelectSet;

#define DYAD_UNSIGNED_BIT (sizeof(unsigned) * CHAR_BIT)


static void select_deinit(SelectSet *s) {
  int i;
  for (i = 0; i < SELECT_MAX; i++) {
    dyad_free(s->fds[i]);
    s->fds[i] = NULL;
  }
  s->capacity = 0;
}

//select组扩容
static void select_grow(SelectSet *s) {
  int i;
  int oldCapacity = s->capacity;
  s->capacity = s->capacity ? s->capacity << 1 : 1;
  for (i = 0; i < SELECT_MAX; i++) {
    s->fds[i] = dyad_realloc(s->fds[i], s->capacity * sizeof(fd_set));
    memset(s->fds[i] + oldCapacity, 0,
           (s->capacity - oldCapacity) * sizeof(fd_set));
  }
}


static void select_zero(SelectSet *s) {
  int i;
  if (s->capacity == 0) return;
  s->maxfd = 0;
  for (i = 0; i < SELECT_MAX; i++) {
#if _WIN32
    s->fds[i]->fd_count = 0;
#else
    memset(s->fds[i], 0, s->capacity * sizeof(fd_set));
#endif
  }
}

//向select组添加select
static void select_add(SelectSet *s, int set, dyad_Socket fd) {
#ifdef _WIN32
  fd_set *f;
  if (s->capacity == 0) select_grow(s);
  while ((unsigned) (s->capacity * FD_SETSIZE) < s->fds[set]->fd_count + 1) {
    select_grow(s);
  }
  f = s->fds[set];
  f->fd_array[f->fd_count++] = fd;
#else
  unsigned *p;
  while (s->capacity * FD_SETSIZE < fd) {
    select_grow(s);//select组扩容
  }
  p = (unsigned*) s->fds[set];
  p[fd / DYAD_UNSIGNED_BIT] |= 1 << (fd % DYAD_UNSIGNED_BIT);//除以DYAD_UNSIGNED_BIT和% DYAD_UNSIGNED_BIT是为了防止数组越界
  if (fd > s->maxfd) s->maxfd = fd;
#endif
}

//判断指定的selectset有没有对应的事件发生
static int select_has(SelectSet *s, int set, dyad_Socket fd) {
#ifdef _WIN32
  unsigned i;
  fd_set *f;
  if (s->capacity == 0) return 0;
  f = s->fds[set];
  for (i = 0; i < f->fd_count; i++) {
    if (f->fd_array[i] == fd) {
      return 1;
    }
  }
  return 0;
#else
  unsigned *p;
  if (s->maxfd < fd) return 0;
  p = (unsigned*) s->fds[set];
  return p[fd / DYAD_UNSIGNED_BIT] & (1 << (fd % DYAD_UNSIGNED_BIT));
#endif
}

涉及到的代码先贴上
在这里插入图片描述
结构体的结构如上图示
后面要用select函数来找我们感兴趣的文件描述符,select函数的参数如下

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

可以看到需要3个fd_set,分别对应感兴趣的读/写/异常的文件描述符集合
那这个文件描述符集合又是啥

其实这个东西就是一个64位的数,每一个位对应一个文件描述符,如果这一位为1说明对应的文件描述符发生了指定的事[读/写/异常],就是位图

为了实现对这些位图的操作,他提供了上面这些函数,包括赋值/清空/判断/初始化/释放操作函数。
其中我这里重点说下select_add
函数原型是

static void select_add(SelectSet *s, int set, dyad_Socket fd)

static void select_add(SelectSet *s, int set, dyad_Socket fd) {
#ifdef _WIN32
  fd_set *f;
  if (s->capacity == 0) select_grow(s);
  while ((unsigned) (s->capacity * FD_SETSIZE) < s->fds[set]->fd_count + 1) {
    select_grow(s);
  }
  f = s->fds[set];
  f->fd_array[f->fd_count++] = fd;
#else
  unsigned *p;
  while (s->capacity * FD_SETSIZE < fd) {
    select_grow(s);//select组扩容
  }
  p = (unsigned*) s->fds[set];
  p[fd / DYAD_UNSIGNED_BIT] |= 1 << (fd % DYAD_UNSIGNED_BIT);//除以DYAD_UNSIGNED_BIT和% DYAD_UNSIGNED_BIT是为了防止数组越界
  if (fd > s->maxfd) s->maxfd = fd;
#endif
}

流程图如下
在这里插入图片描述
把这个弄明白,就可以来分析逐个函数update函数的实现了

destroyClosedStreams

销毁掉已经关掉的流

static void destroyClosedStreams(void) {
  dyad_Stream *stream = dyad_streams;
  while (stream) {
    if (stream->state == DYAD_STATE_CLOSED) {
      dyad_Stream *next = stream->next;
      stream_destroy(stream);//把需要删掉的流从流链表里销毁
      stream = next;//把后一个替补上去
    } else {
      stream = stream->next;
    }
  }
}

这个函数的作用就是将需要删除的流(已经关闭的流)移出链表,然后把后面的元素接上。
移出链表的实现函数在stream_destroy
关闭socket,将待移除的元素移出链表之后,触发销毁流的事件,释放掉分配的内存。

//销毁流
static void stream_destroy(dyad_Stream *stream) {
  dyad_Event e;
  dyad_Stream **next;
  /* Close socket */
  if (stream->sockfd != INVALID_SOCKET) {
    close(stream->sockfd);
  }
  /* Emit destroy event */
  e = createEvent(DYAD_EVENT_DESTROY);
  e.msg = "the stream has been destroyed";
  stream_emitEvent(stream, &e);
  /* 从链表中删掉这个元素 Remove from list and decrement count */
  next = &dyad_streams;
  while (*next != stream) {
    next = &(*next)->next;
  }
  *next = stream->next;
  dyad_streamCount--;
  /* Destroy and free */
  vec_deinit(&stream->listeners);
  vec_deinit(&stream->lineBuffer);
  vec_deinit(&stream->writeBuffer);
  dyad_free(stream->address);
  dyad_free(stream);
}

updateTickTimer

更新计时器
每一个流都有一个时间计数器和超时计数器
这个函数就是用来维护时间计数器这个计时变量的,每次更新时间之后,触发更新时间的事件

static void updateTickTimer(void) {
  /* Update tick timer */
  if (dyad_lastTick == 0) {
    dyad_lastTick = dyad_getTime();
  }
  while (dyad_lastTick < dyad_getTime()) {
    /* 发射给所有流 Emit event on all streams */
    dyad_Stream *stream;
    dyad_Event e = createEvent(DYAD_EVENT_TICK);
    e.msg = "a tick has occured";
    stream = dyad_streams;
    while (stream) {
      stream_emitEvent(stream, &e);
      stream = stream->next;
    }
    dyad_lastTick += dyad_tickInterval;//更新时间戳
  }
}

updateStreamTimeouts

更新流的超时计时器
这个函数就是用来维护超时计时器这个计时变量的,每次更新时间之后,触发时间超时的事件

///更新流的超时计数器
static void updateStreamTimeouts(void) {
  double currentTime = dyad_getTime();
  dyad_Stream *stream;
  dyad_Event e = createEvent(DYAD_EVENT_TIMEOUT);
  e.msg = "stream timed out";
  stream = dyad_streams;
  while (stream) {
    if (stream->timeout) {
      if (currentTime - stream->lastActivity > stream->timeout) {
        stream_emitEvent(stream, &e);
        dyad_close(stream);
      }
    }
    stream = stream->next;
  }
}

dyad_update流程图

点开大图更清晰
请添加图片描述
画出来搞明白了,就是前半部分switch-case注册select事件,select函数之后,后半部分switch-case处理各个状态下事件发生后的具体处理。

事件

在这个库的框架下,事件也是一个重要的概念
当用户调用dyad_addListener添加需要关注的事件的时候,其实就是向对应的流注册事件,将事件挂到对应的流上。
事件的结构体定义如下:

typedef struct {
  int type;//类型
  void *udata;//用户数据
  dyad_Stream *stream;//对应的流
  dyad_Stream *remote;//对应远端的流
  const char *msg;//消息
  char *data;//数据
  int size;//大小
} dyad_Event;

库提供了函数用于触发事件

stream_emitEvent

static void stream_emitEvent(dyad_Stream *stream, dyad_Event *e) {
  int i;
  e->stream = stream;
  for (i = 0; i < stream->listeners.length; i++) {
    Listener *listener = &stream->listeners.data[i];
    if (listener->event == e->type) {//被触发的事件的类型 == 流中注册的事件类型
      e->udata = listener->udata;
      listener->callback(e);//回调函数
    }
    /* Check to see if this listener was removed: If it was we decrement `i`
     * since the next listener will now be in this ones place */
    if (listener != &stream->listeners.data[i]) {
      i--;
    }
  }
}

事件处理子函数

Update函数里还有一类重要的函数,事件处理子函数
这里我重要读了stream_handleReceivedData,stream_acceptPendingConnections和stream_flushWriteBuffer

stream_handleReceivedData

处理数据的接收

//处理接收到的数据
static void stream_handleReceivedData(dyad_Stream *stream) {
  for (;;) {
    /* Receive data */
    dyad_Event e;
    char data[8192];
    int size = recv(stream->sockfd, data, sizeof(data) - 1, 0);
    if (size <= 0) {
      if (size == 0 || errno != EWOULDBLOCK) {
        /* Handle disconnect */
        dyad_close(stream);
        return;
      } else {
        /* No more data */
        return;
      }
    }
    data[size] = 0;
    /* Update status */
    stream->bytesReceived += size;
    stream->lastActivity = dyad_getTime();
    /* Emit data event */
    e = createEvent(DYAD_EVENT_DATA);
    e.msg = "received data";
    e.data = data;
    e.size = size;
    stream_emitEvent(stream, &e);
    /* Check stream state in case it was closed during one of the data event
     * handlers. */
    if (stream->state != DYAD_STATE_CONNECTED) {
      return;
    }

    /* 处理线事件 Handle line event */
    if (stream_hasListenerForEvent(stream, DYAD_EVENT_LINE)) {
      int i, start;
      char *buf;
      for (i = 0; i < size; i++) {
        vec_push(&stream->lineBuffer, data[i]);//逐字节压入lineBuffer
      }
      start = 0;
      buf = stream->lineBuffer.data;
      for (i = 0; i < stream->lineBuffer.length; i++) {
        if (buf[i] == '\n') {
          dyad_Event e;
          buf[i] = '\0';
          e = createEvent(DYAD_EVENT_LINE);
          e.msg = "received line";
          e.data = &buf[start];
          e.size = i - start;
          /* Check and strip carriage return */
          if (e.size > 0 && e.data[e.size - 1] == '\r') {
            e.data[--e.size] = '\0';//字符串分段处理
          }
          stream_emitEvent(stream, &e);//触发事件
          start = i + 1;
          /* Check stream state in case it was closed during one of the line
           * event handlers. */
          if (stream->state != DYAD_STATE_CONNECTED) {
            return;
          }
        }
      }
      if (start == stream->lineBuffer.length) {
        vec_clear(&stream->lineBuffer);
      } else {
        vec_splice(&stream->lineBuffer, 0, start);
      }
    }
  }
}

可以看到函数先是调用了recv函数,接收数据,然后将接收到的数据挂载到流上,并触发数据接收事件。下半部分,作者设计了一个Line事件(可以理解为行事件嘛),专门处理字符串吧

作者将接收到的数据逐个字节压入流的lineBuffer里,然后也是创建对应的“行事件”,触发“行事件”

stream_acceptPendingConnections

static void stream_acceptPendingConnections(dyad_Stream *stream) {
  for (;;) {
    dyad_Stream *remote;
    dyad_Event e;
    int err = 0;
    dyad_Socket sockfd = accept(stream->sockfd, NULL, NULL);
    if (sockfd == INVALID_SOCKET) {
      err = errno;
      if (err == EWOULDBLOCK) {
        /* No more waiting sockets */
        return;
      }
    }
    /* Create client stream */
    remote = dyad_newStream();
    remote->state = DYAD_STATE_CONNECTED;
    /* Set stream's socket */
    stream_setSocket(remote, sockfd);
    /* Emit accept event */
    e = createEvent(DYAD_EVENT_ACCEPT);
    e.msg = "accepted connection";
    e.remote = remote;
    stream_emitEvent(stream, &e);
    /* Handle invalid socket -- the stream is still made and the ACCEPT event
     * is still emitted, but its shut immediately with an error */
    if (remote->sockfd == INVALID_SOCKET) {
      stream_error(remote, "failed to create socket on accept", err);
      return;
    }
  }
}

这个函数是处理accept的,可以看到调用了accept,如果accept成功,则创建accept事件,触发事件,执行accept的回调函数。

stream_flushWriteBuffer

//处理写缓存
static int stream_flushWriteBuffer(dyad_Stream *stream) {
  stream->flags &= ~DYAD_FLAG_WRITTEN;
  if (stream->writeBuffer.length > 0) {
    /* Send data */
    int size = send(stream->sockfd, stream->writeBuffer.data,
                    stream->writeBuffer.length, 0);
    if (size <= 0) {
      if (errno == EWOULDBLOCK) {
        /* No more data can be written */
        return 0;
      } else {
        /* Handle disconnect */
        dyad_close(stream);
        return 0;
      }
    }
    if (size == stream->writeBuffer.length) {
      vec_clear(&stream->writeBuffer);
    } else {
      vec_splice(&stream->writeBuffer, 0, size);
    }
    /* Update status */
    stream->bytesSent += size;
    stream->lastActivity = dyad_getTime();
  }

  if (stream->writeBuffer.length == 0) {
    dyad_Event e;
    /* If this is a 'closing' stream we can properly close it now */
    if (stream->state == DYAD_STATE_CLOSING) {
      dyad_close(stream);
      return 0;
    }
    /* Set ready flag and emit 'ready for data' event */
    stream->flags |= DYAD_FLAG_READY;
    e = createEvent(DYAD_EVENT_READY);
    e.msg = "stream is ready for more data";
    stream_emitEvent(stream, &e);
  }
  /* Return 1 to indicate that more data can immediately be written to the
   * stream's socket */
  return 1;
}

这个函数是处理写操作的函数,调用了send函数,在处理完send之后,判断是否空闲下来(待发送数据空)来决定是不是需要触发ready事件,执行对应的回调函数。

总结

大部分的核心功能差不多就分析完了,对我这个刚刚入门写Linux程序的菜鸡来说,真是学到了不少东西。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: C语言的线程池的代码实现可以在GitHub上找到很多开源项目,以下是其中一个例子: https://github.com/rxi/dyad 这是一个简单的C语言线程池实现,主要使用了POSIX线程代码非常简洁和易于理解。它包含一个pool结构体,用于管理线程池的状态和任务队列等信息。主要函数包括pool_init用于初始化线程池,pool_submit用于提交任务,pool_wait用于等待线程池执行完所有任务,pool_destroy用于销毁线程池。 使用这个线程池框架,只需要简单地定义一个任务函数,并通过pool_submit提交任务,即可由线程池中的线程来执行任务。线程池内部会自动调度任务,并根据设置的线程池大小控制并发执行的线程数。 这个线程池实现还提供了一些额外的功能,例如支持任务超时设置,可以在任务执行的一定时间内获取任务的返回结果,也可以设置任务的最大重试次数。 通过在GitHub上搜索"C thread pool"关键词,还可以找到其他很多C语言线程池的实现,这些开源项目提供了完整的代码实现和详细的文档说明,可以根据个人需求选择使用。 ### 回答2: 线程池是一种用于管理和复用线程的技术,通过预先创建一组线程并将其放入池中,以便在需要的时候可以重复使用。这样可以避免频繁创建和销毁线程的开销,提高系统的性能和效率。 在GitHub上有很多关于线程池实现的代码,我以下将以Java语言为例来介绍一个常见的线程池实现。 Java的线程池是通过`ThreadPoolExecutor`类来实现的。我们可以在GitHub上搜索"ThreadPoolExecutor"关键字,就会找到很多相关的代码。 比如,一个名为"java线程池的简单实现"的代码,这个提供了一个简单的线程池实现,包括线程池类`MyThreadPoolExecutor`和任务类`MyTask`。通过查看代码,可以了解到该线程池实现了以下几个功能: 1. 创建线程池:通过`MyThreadPoolExecutor`类的构造函数可以指定线程池的大小和其他相关的参数。 2. 提交任务:通过调用`MyThreadPoolExecutor`类的`submit()`方法将任务提交到线程池中。 3. 执行任务:线程池会自动管理和调度线程,并调用任务的`run()`方法来执行任务。 4. 监控线程池:可以通过`MyThreadPoolExecutor`类提供的方法获取线程池的状态,比如当前活动的线程数、完成的任务数等。 5. 终止线程池:通过调用`MyThreadPoolExecutor`类的`shutdown()`方法可以优雅地关闭线程池,等待当前正在执行的任务完成后再关闭线程池。 这只是一个简单的线程池实现,如果对线程池的实现原理和更高级的应用有兴趣,可以进一步了解和探索更多的线程池实现代码。 ### 回答3: C语言中的线程池可以通过使用Pthreads来实现。以下是一个简单的C语言线程池的代码实现,你可以在GitHub上找到完整的代码。 #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define MAX_THREADS 10 #define MAX_QUEUE 1000 typedef struct { void (*function)(void *); // 线程执行的函数指针 void *argument; // 函数参数 } task_t; task_t task_queue[MAX_QUEUE]; int queue_size = 0; int head = 0; int tail = 0; pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER; pthread_t worker_threads[MAX_THREADS]; int pool_shutdown = 0; // 添加任务到线程池 void pool_add_task(void (*function)(void *), void *argument) { pthread_mutex_lock(&queue_mutex); if (queue_size >= MAX_QUEUE) { fprintf(stderr, "Warning: task queue is full, the task is dropped.\n"); pthread_mutex_unlock(&queue_mutex); return; } task_queue[tail].function = function; task_queue[tail].argument = argument; tail = (tail + 1) % MAX_QUEUE; queue_size++; pthread_cond_signal(&queue_cond); pthread_mutex_unlock(&queue_mutex); } // 线程池的工作线程函数 void *worker(void *arg) { while (1) { pthread_mutex_lock(&queue_mutex); while (queue_size == 0 && !pool_shutdown) { pthread_cond_wait(&queue_cond, &queue_mutex); } if (pool_shutdown) { pthread_mutex_unlock(&queue_mutex); pthread_exit(NULL); } void (*function)(void *) = task_queue[head].function; void *argument = task_queue[head].argument; head = (head + 1) % MAX_QUEUE; queue_size--; pthread_mutex_unlock(&queue_mutex); function(argument); } } // 初始化线程池 void pool_init() { int i; for (i = 0; i < MAX_THREADS; i++) { pthread_create(&worker_threads[i], NULL, worker, NULL); } } // 关闭线程池 void pool_shutdown() { int i; pool_shutdown = 1; pthread_mutex_lock(&queue_mutex); pthread_cond_broadcast(&queue_cond); pthread_mutex_unlock(&queue_mutex); for (i = 0; i < MAX_THREADS; i++) { pthread_join(worker_threads[i], NULL); } } // 测试函数 void print_number(void *arg) { int number = *((int *)arg); printf("Number: %d\n", number); } int main() { pool_init(); int i; for (i = 0; i < 100; i++) { int *number = malloc(sizeof(int)); *number = i; pool_add_task(print_number, (void *)number); } pool_shutdown(); return 0; } 这个线程池的实现包括了添加任务到队列,工作线程从请求队列中获取任务并执行,还有线程池的初始化和关闭。你可以在任务函数中处理自己的逻辑,此处的示例是打印数字。注意,这只是一个简单的线程池实现,还有许多其他特性可以添加。你可以在GitHub上查找更多更完整的C语言线程池实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值