26 July 2017

socket 的低水位

每个套接字还有一个 接收低水位标记 和一个 发送低水位标记 ,这两个标志主要用于 select, poll, epoll 等 IO 复用中
接收低水位就是用来控制该 socket 是否可读。
发送低水位就是用来控制该 socket 是否可写。

当接收缓存区的 接收 的数据大于接收低水位的时候,就表示当前已经有足够多的数据可以读,状态可读
当发送缓存区的 剩余 的空间大于发送低水位的时候,就表示当前已经有足够多的空间可以写入,状态可写

SO_RCVLOWAT 和 SO_SNDLOWAT 这两个套接字选项就是用来控制 接收低水位和发送低水位。

默认情况下,这两个值都为 1,即只要 socket 收到一个字节就是可读的;只要 socket 的发送空间还要剩下 1,就是可写

libevent 的低水位和高水位

libevent 有 4 个水位标识,分别用来表示 读取低水位、读取高水位、写入低水位、写入高水位。

读低水位 和 写低水位 都是用来标识 读/写回调函数 是否被调用。当读取到输入缓冲区的 buffer 大于读低水位时,读回调被调用;当写入到输出缓冲区的 buffer 大于写低水位时,写回调被调用

static inline void bufferevent_trigger_nolock_(struct bufferevent *bufev, short iotype, int options) {
    if ((iotype & EV_READ) &&
        ((options & BEV_TRIG_IGNORE_WATERMARKS)
         || evbuffer_get_length(bufev->input) >= bufev->wm_read.low)) {
        bufferevent_run_readcb_(bufev, options);
    }
    if ((iotype & EV_WRITE) &&
        ((options & BEV_TRIG_IGNORE_WATERMARKS)
         || evbuffer_get_length(bufev->output) <= bufev->wm_write.low)) {
        bufferevent_run_writecb_(bufev, options);
    }
}

static void bufferevent_readcb(evutil_socket_t fd, short event, void *arg) {
    ...
    bufferevent_trigger_nolock_(bufev, EV_READ, 0);
    ...
}

struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, int options) {
    ...
    event_assign(&bufev->ev_read, bufev->ev_base, fd,
            EV_READ|EV_PERSIST|EV_FINALIZE, bufferevent_readcb, bufev);    // 设置了 read callback
        event_assign(&bufev->ev_write, bufev->ev_base, fd,
            EV_WRITE|EV_PERSIST|EV_FINALIZE, bufferevent_writecb, bufev);  // 设置了 write callback
    ...
}

读高水位 表示一次读取只能读取这么多,剩下的数据留着到下一次再读取。读高水位是由 ioctl 的 FIONREAD 控制的,FIONREAD 可以查看当前的 read buffer 里面有多少数据

ioctl(socket_fd, FIONREAD, &nread);
static int get_n_bytes_readable_on_socket(evutil_socket_t fd) {
    unsigned long lng = EVBUFFER_MAX_READ;
    if (ioctlsocket(fd, FIONREAD, &lng) < 0) {
        return -1;
    }

    /* Can overflow, but mostly harmlessly. XXXX */
    return (int)lng;
}

int evbuffer_read(struct evbuffer *buf, evutil_socket_t fd, int howmuch) {
    struct evbuffer_chain **chainp;
    int n;
    int result;

    struct evbuffer_chain *chain;
    unsigned char *p;

    n = get_n_bytes_readable_on_socket(fd);
    if (n <= 0 || n > EVBUFFER_MAX_READ) {   // 如果 buffer 里面的数据 大于 EVBUFFER_MAX_READ,那么顶多只读取 EVBUFFER_MAX_READ
        n = EVBUFFER_MAX_READ;
    }

    if (howmuch < 0 || howmuch > n) {
        howmuch = n;
    }

    ...

    evbuffer_invoke_callbacks_(buf);

    ...

    return result;
}