zoukankan      html  css  js  c++  java
  • dyad源码分析

    背景介绍

    dyad是一个异步网络库,目标是可移植、轻量级、易于使用。地址位于https://github.com/rxi/dyad。

    echoserver如何写

    我们来看看一个简单的echo Server来看看其API的设计

    #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) { 
        puts("Accept a connection ");
        dyad_addListener(e->remote, DYAD_EVENT_DATA, onData, NULL);
    
    }
    static void onError(dyad_Event *e) {
      printf("server error: %s
    ", e->msg);
    }
    
    static void onListen(dyad_Event* e){ 
        fprintf(stdout, "listen success %d", e->type); 
    }
    int main(void) {
      dyad_Stream *s;
      dyad_init();
      s = dyad_newStream();
      dyad_addListener(s, DYAD_EVENT_LISTEN, onListen, NULL);
      /* 添加错误的回调 */
      dyad_addListener(s, DYAD_EVENT_ERROR,  onError,  NULL);
       /* 添加连接成功后的回调 */
      dyad_addListener(s, DYAD_EVENT_ACCEPT, onAccept, NULL);
      dyad_listen(s, 8000);
      fprintf(stdout, "stream num %d", dyad_getStreamCount());
      while (dyad_getStreamCount() > 0) {
        dyad_update();
      }
      return 0;
    }
    

    我们从这个例子中,看到了几个简单的API:

    • dyad_init();
    • dyad_newStream();
    • dyad_addListener
    • dyad_update

    dyad_init分析

    这个函数非常简单,就是要改变SIGPIPE的默认行为(终止进程),忽略它。

    void dyad_init(void) {
    #ifdef _WIN32
      WSADATA dat;
      int err = WSAStartup(MAKEWORD(2, 2), &dat);
      if (err != 0) {
        panic("WSAStartup failed (%d)", err);
      }
    #else
      /* Stops the SIGPIPE signal being raised when writing to a closed socket */
      signal(SIGPIPE, SIG_IGN);
    #endif
    }
    

    SIGPIPE信号的产生

    当服务器close一个连接时,若client端接着发数据。根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不要再写了。

    又或者当一个进程向某个已经收到RST的socket执行写操作是,内核向该进程发送一个SIGPIPE信号。该信号的缺省学位是终止进程,因此进程必须捕获它以免不情愿的被终止。

    dyad_Stream

    dyad_stream 用来表示一个套接字链接,最重要的几个字段分别是listener,linebuffer,和writeBuffer,分别对应为回调函数,读缓冲区,写缓冲区。我们应该注意的是所有的tcp连接都是通过list来连在一起。

    struct dyad_Stream {
      int state, flags;
      dyad_Socket sockfd;
      char *address;
      int port;
      int bytesSent, bytesReceived;
      double lastActivity, timeout;
       /* 该连接的回调 */
      Vec(Listener) listeners;
      Vec(char) lineBuffer;
      Vec(char) writeBuffer;
       /* 指向下一个连接 */
      dyad_Stream *next;
    };
    

    Vec宏定义

    dyad用宏来模拟了C++模板的功能,我们来看看,这里的Vec主要的作用就是模拟C++中的vector也就是动态数组。

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

    dyad_newStream函数

    我们再来看看dyad_newStream函数,这个函数很简单,就是要分批额一个dyad_Stream内存,并初始化

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

    注意到dyad_streams这个变量,其是一个全局的变量,表示当前和服务器请求的TCP连接队列的头部,当我们新分配一个tcp连接的资源的时候,头部发生变化,指向了当前新分配的连接资源(Stream)。

    static dyad_Stream *dyad_streams;
    static int dyad_streamCount;
    

    dyad_addListener函数

    这个函数就比较简单了,就是给当前的套接字,添加相应事件的回调函数,在上面的例子中,我们看到了添加了listen,accept和error三个回调函数。我们来看看事件类型的定义:

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

    再看看添加相应的回调函数

    typedef void (*dyad_Callback)(dyad_Event*);
    
    /* 每个事件回调的定义 */
    typedef struct {
       // 事件的类型
      int event;
      // 事件的回调
      dyad_Callback callback;
      // 参数类型
      void *udata;
    } Listener;
    
    
    
    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);
    }
    

    dyad_listen

    这个函数看命字就知道干什么,就是创建监听套接字,注意到这个套接字是non-block的,注意到。过程就是典型的sock-->bind->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);
    
      // 获取ip地址
      err = getaddrinfo(host,  buf,  &hints, &ai);
      if (err) {
        stream_error(stream, "could not get addrinfo", errno);
        goto fail;
      }
      /* 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;
    }
    // 监听套接字
    int dyad_listen(dyad_Stream *stream, int port) {
      return dyad_listenEx(stream, NULL, port, 511);
    }
    

    getaddrinfo系统调用

    我们在上面看到了getaddrinfo,这个系统调用的man page 如下:

    int getaddrinfo(const char *node, const char *service,
                           const struct addrinfo *hints,
                           struct addrinfo **res);
    

    Given node and service, which identify an Internet host and a service, getaddrinfo() returns one or more addrinfo structures, each of which contains an Internet address that can be specified in a call to bind(2) or connect(2). The getaddrinfo() function combines the functionality provided by the gethostbyname(3) and getservbyname(3) functions into a single interface, but unlike the latter functions, getaddrinfo() is reentrant and allows programs to eliminate IPv4-versus-IPv6 dependencies.

    实际上,上面的形参node表示的是主机名称,service表示的是端口号。这个函数目的是获取其internet address,为后面的bind系统调用所使用。注意到在经历socket-->bind--->listen之后,套接字被设置成non-blocking,然后是“发射了”一个事件,这就是listen事件。我们来看看这个发射事件的函数。

    static void stream_emitEvent(dyad_Stream *stream, dyad_Event *e) {
      int i;
      e->stream = stream;
      // 遍历整个该socket的回调
      for (i = 0; i < stream->listeners.length; i++) {
        Listener *listener = &stream->listeners.data[i];
        // 如果回调的事件类型为e
        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--;
        }
      }
    }
    

    从上面的代码中可以看到,就是遍历该连接所有的回调函数,如果该连接的事件类型满足则应该调用。

    dyad_update函数

    dyad_update这个函数是非常重要的,这就是这个简单的异步库的核心,相当于Reactor中的Event Loop,所以现在来好好好分析这个函数。

    void dyad_update(void) {
      dyad_Stream *stream;
      struct timeval tv;
      destroyClosedStreams();
      updateTickTimer();
      // 处理超时回调
      updateStreamTimeouts();
      /* Create fd sets for select() */
      select_zero(&dyad_selectSet);
    
      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 */
      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;
              /* 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;
      }
    }
    

    我们来看一下,这个事件循环的思路,首先destroyClosedStreams遍历整个stream,将死连接释放掉,当然会调用起注册的回调函数,如果有的话。然后更新循环中的一个滴答数。接着初始化Set中文件描述集合为全部为0,要注意使用select函数时,每次都要重新设置关注的文件描述符事件。

    我们分几种情况,来跟踪以下源码:

    #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) { 
        puts("Accept a connection ");
        dyad_addListener(e->remote, DYAD_EVENT_DATA, onData, NULL);
        dyad_writef(e->remote, "echo server
    ");
    }
    static void onError(dyad_Event *e) {
      printf("server error: %s
    ", e->msg);
    }
    
    static void onListen(dyad_Event* e){ 
        fprintf(stdout, "listen success %d", e->type); 
    }
    int main(void) {
      dyad_Stream *s;
      dyad_init();
      s = dyad_newStream();
      dyad_addListener(s, DYAD_EVENT_LISTEN, onListen, NULL);
      /* 添加错误的回调 */
      dyad_addListener(s, DYAD_EVENT_ERROR,  onError,  NULL);
       /* 添加连接成功后的回调 */
      dyad_addListener(s, DYAD_EVENT_ACCEPT, onAccept, NULL);
      dyad_listen(s, 8000);
      fprintf(stdout, "stream num %d", dyad_getStreamCount());
      while (dyad_getStreamCount() > 0) {
        dyad_update();
      }
      return 0;
    }
    

    (1) 没有任何客户端尝试进行连接
    我们在24行, s = dyad_newStream();,这行打下断点,发现

    注意到这里的创建的listen fd的值为3,因为0, 1, 2都被标准I/O占用,这也说明每个进程能打开的文件描述符都是独立的,不会因为别的进程占用了3,你的进程就所能够打开的文件描述符必须从4开始。这里我们应该注意state为4, 对应的状态为DYAD_EVENT_LISTEN,在dyad_listen那句打上断点,我们看看s的状态:

    这个时候,address和port都已经初始化好了。断点现在设置到dyad_getStreamCount调用,我们发现这里返回的只有1,只有一个listen socket 值为3,s的next指向NULL,现在进入到dyad_update(),因为s的状态是DYAD_EVENT_LISTEN同样对应的DYAD_STATE_LISTENING的值,此时将调用:

    select_add(&dyad_selectSet, SELECT_READ, stream->sockfd);
    break;
    

    意思很明显,将3的读事件加入到关注列表中。因为此时整个服务器后台处理的连接只有一个,s的next为NULL,所以马上跳出while循环。接着就是设置超时的时间为1s:

    tv.tv_sec = dyad_updateTimeout;
    tv.tv_usec = (dyad_updateTimeout - tv.tv_sec) * 1e6;
    

    然后调用select进行多路I/O复用,由前面可知,select最多1秒后,返回。

    select(dyad_selectSet.maxfd + 1,
             dyad_selectSet.fds[SELECT_READ],
             dyad_selectSet.fds[SELECT_WRITE],
             dyad_selectSet.fds[SELECT_EXCEPT],
             &tv);
    

    从select返回后,然后就对整个stream链表进行遍历,因为只有一个listen fd 所以遍历一次,就结束了,遍历的时候,会根据当前fd的状态来进行不同的操作,因为listen fd 此时的状态为DYAD_STATE_LISTENING,那么就会进入:

    
    if (select_has(&dyad_selectSet, SELECT_READ, stream->sockfd)) {
        stream_acceptPendingConnections(stream);
    }
    

    显然判断,是否是当前的套接字3,是否有读事件发生,如果有读事件,那么if里面为true。在这里因为没有客户端端接入,所以并没有进入到这if里面。然后就是判断是否该套接字是可写的,如果是的,那么就应该将数据发送出去。

     if (
          stream->flags & DYAD_FLAG_WRITTEN &&
          stream->state != DYAD_STATE_CLOSED
        ) {
          stream_flushWriteBuffer(stream);
        }
    

    没有任何客户端连接就这样结束了。
    (2)有客户端接入
    首先,还是stream的listen sock fd为3,状态还是DYAD_STATE_LISTENING,所以,首先添加的select set集合中,将其读事件注册,然后调用select等待超时,或者是listen fd读事件发生,因为这里有客户端接入,那么就应该返回listen fd 可读。然后进入到下面的代码:

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

    因为这里的stream sockfd 可读,那么将会调用stream_acceptPendingConnections(stream)。下面是这个函数的关键字代码:

    // 创建accept 套接字
    dyad_Socket sockfd = accept(stream->sockfd, NULL, NULL);
    // Create client stream 
    remote = dyad_newStream();
    remote->state = DYAD_STATE_CONNECTED;
    /* Set stream's socket */
    stream_setSocket(remote, sockfd);
     e = createEvent(DYAD_EVENT_ACCEPT);
     e.msg = "accepted connection";
     e.remote = remote;
     stream_emitEvent(stream, &e);
    

    这个里面最重要的是创建remote Stream,创建后其会加入到全局的stream链表,设置remote的 sockfd为accept套接字。然后调用accept事件的回调。此时,sockef为4,注意:我们的echoSever中有onAccept回调函数,这个函数将创建remote stream读事件回调。

    static void onAccept(dyad_Event *e) { 
        puts("Accept a connection ");
        // 添加对remote stream中套接字的回调
        dyad_addListener(e->remote, DYAD_EVENT_DATA, onData, NULL);
    }
    

    stream_acceptPendingConnections调用完成后,这时也设置当前socket为4的回调也就是onData,同样也是调用select函数,那么这个时候,同样遍历整个stream 流,包含socket值为3和4,根据stream的状态来选择相应的状态,现在4套接字排在了3,而且,4的状态为DYAD_STATE_CONNECTED,所以进入到:

    case DYAD_STATE_CONNECTED:
            if (select_has(&dyad_selectSet, SELECT_READ, stream->sockfd)) {
              stream_handleReceivedData(stream);
              if (stream->state == DYAD_STATE_CLOSED) {
                break;
              }
            }
    

    首先判断当前套接字4是否可读,如果可读,那么进入到stream_handleReceivedData(stream),这里我们假设有数据读入,那么select_has将会返回true,我们看看stream_handleReceivedData读入:

      char data[8192];
      int size = recv(stream->sockfd, data, sizeof(data) - 1, 0);
        if (size <= 0) {
          // 这样size为0、可能
          if (size == 0 || errno != EWOULDBLOCK) {
            /* Handle disconnect */
            dyad_close(stream);
            return;
          } else {
            /* No more data */
            return;
          }
        }
        data[size] = 0;
    

    关键recv是用来读数据,应该注意的是最多读取8192-1个数据,这次获取数据后,将会产生事件。产生事件后,调用回调函数。

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

    destroyClosedStreams()

    在dyad_update中,首先调用的是destroyClosedStreams这个函数的作用是,在遍历整个系统接收到的请求队列中,释放掉已经关闭的请求连接。应该注意到的是,所有的socket连接是拉了一个链表串起来的,所以,就是遍历整个链表,将处于close状态的连接删掉

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

    注意到这里的删掉,除了是释放掉相应的dyad_Stream的内存,还有就是要对回调函数进行调用,这个过程在stream_destroy中实现。

    stream_destroy

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

    在stream_destroy中,我们可以看到,首先要关闭的是套接字描述符,然后是调用相应的回调函数,最后从连接链表中删除连接,最后是释放相应的内存。

    updateTickTimer();

    这个函数主要的作用就是更新上次遍历整个循环的时间,然后对整个已经建立连接进行遍历,如果你关注了从上次loop时间到现在loop时间的每一秒事件(),那么就会调用该事件的回调函数。

    static void updateTickTimer(void) {
      /* Update tick timer */
      if (dyad_lastTick == 0) {
        dyad_lastTick = dyad_getTime();
      }
      // 如果上次遍历的时间小于当前时间
      // 那么关注了DYAD_EVENT_TICK事件
      // 对其中的每一秒都调用相应的回调函数
      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;
      }
    }
    

    select_zero

    这个函数就是要初始化select 使用的fd_set为0。

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

    dyad_getStreamCount

    这个函数是返回当前有多少个套接字流要处理。

  • 相关阅读:
    吴裕雄--天生自然ANDROID开发学习:1.9 Android程序签名打包
    吴裕雄--天生自然ANDROID开发学习:1.8 工程相关解析(各种文件,资源访问)
    html 上传预览图片
    git笔记
    iscroll 下拉刷新功能
    移动端页面 弹出框滚动,底部body锁定,不滚动 / 微信网页禁止回弹效果
    getElementsByClassName 兼容性
    登录页面-输入框清空按钮
    FireFox中iframe的返回上页问题
    ajax是异步的,异步取数据,如何能保证数据是存在的。
  • 原文地址:https://www.cnblogs.com/bofengqiye/p/7353194.html
Copyright © 2011-2022 走看看