mongoose 源码解读(一)

17 篇文章 63 订阅

        这几天一直在调试用 mongoose 源码开发的应用程序,然后就发现在下载文件时速度特别地慢,比如我下载一张图片,在浏览器里图片会一点点显示出来,直到数据完全发送完毕,一张完整的图片才能呈现。

随着数据发送 完成,这张完整的图片是这样的:

 

 从数据发送的打印知道,数据每次只发送 1460 bytes

其实数据发送慢的原因并不是这个发送大小限制的,而是 main 函数里这个:

    while (1)
    {
        mg_mgr_poll(&mgr, 100);
        sleep(1);
    }

 因为这里休眠一秒,其效果就是每秒发送 1460 bytes 数据,这样当然就很慢了,可以把 sleep(1) 去掉,再去下载操作时,就可以非常快了。

在上一篇文章中,我们提到 mg_mgr_poll 函数里其实调用的是 select 系统调用,最终调用的是这个函数:time_t mg_socket_if_poll(struct mg_iface *iface, int timeout_ms);而这个函数里会先设置要监控的 fd 集:

time_t mg_socket_if_poll(struct mg_iface *iface, int timeout_ms) {
  struct mg_mgr *mgr = iface->mgr;
  double now = mg_time();
  double min_timer;
  struct mg_connection *nc, *tmp;
  struct timeval tv;
  fd_set read_set, write_set, err_set;
  sock_t max_fd = INVALID_SOCKET;
  int num_fds, num_ev, num_timers = 0;

read_set、write_set、err_set 分别就是要监控的 读、写、出错 文件描述符集合了。然后进行socket 设置:

      if (nc->recv_mbuf.len < nc->recv_mbuf_limit &&
          (!(nc->flags & MG_F_UDP) || nc->listener == NULL)) {
        mg_add_to_set(nc->sock, &read_set, &max_fd);
      }

      if (((nc->flags & MG_F_CONNECTING) && !(nc->flags & MG_F_WANT_READ)) ||
          (nc->send_mbuf.len > 0 && !(nc->flags & MG_F_CONNECTING))) {
        mg_add_to_set(nc->sock, &write_set, &max_fd);
        mg_add_to_set(nc->sock, &err_set, &max_fd);
      }
    }

select 操作

  tv.tv_sec = timeout_ms / 1000;
  tv.tv_usec = (timeout_ms % 1000) * 1000;

  num_ev = select((int) max_fd + 1, &read_set, &write_set, &err_set, &tv);

返回值  num_ev > 0 表示有事件发生,然后会在一个 for 循环里处理这些事件:

  for (nc = mgr->active_connections; nc != NULL; nc = tmp) {
    int fd_flags = 0;
    if (nc->sock != INVALID_SOCKET) {
      if (num_ev > 0) {
        fd_flags = (FD_ISSET(nc->sock, &read_set) &&
                            (!(nc->flags & MG_F_UDP) || nc->listener == NULL)
                        ? _MG_F_FD_CAN_READ
                        : 0) |
                   (FD_ISSET(nc->sock, &write_set) ? _MG_F_FD_CAN_WRITE : 0) |
                   (FD_ISSET(nc->sock, &err_set) ? _MG_F_FD_ERROR : 0);
      }
#if MG_LWIP
      /* With LWIP socket emulation layer, we don't get write events for UDP */
      if ((nc->flags & MG_F_UDP) && nc->listener == NULL) {
        fd_flags |= _MG_F_FD_CAN_WRITE;
      }
#endif
    }
    tmp = nc->next;
    mg_mgr_handle_conn(nc, fd_flags, now);
  }

fd_flags 是一个标志,对应的read、write、error 事件会把这个 fd_flags 置相应的位,然后传给 mg_mgr_handle_conn() 函数进行处理。而这个函数里会进行各种判断,这里我只关心发送,即写事件。

if (fd_flags & _MG_F_FD_CAN_WRITE) mg_if_can_send_cb(nc);

那这个就是数据发送接口了,要发送的数据存放在了 nc 参数的发送缓冲区了,就是我们调用 mg_send() 接口发送的数据。

void mg_if_can_send_cb(struct mg_connection *nc) {
  int n = 0;
  const char *buf = nc->send_mbuf.buf;
  size_t len = nc->send_mbuf.len;

  if (nc->flags & (MG_F_CLOSE_IMMEDIATELY | MG_F_CONNECTING)) {
    return;
  }
  if (!(nc->flags & MG_F_UDP)) {
    if (nc->flags & MG_F_LISTENING) return;
    if (len > MG_TCP_IO_SIZE) len = MG_TCP_IO_SIZE;
  }

函数开头 buf 指向了数据缓冲区头,len 为要发送的数据部长度。而每次发送多少 byte 个数据,由len 决定,如果 len > MG_TCP_IO_SIZE,则 len = MG_TCP_IO_SIZE,即每次最大发送 MG_TCP_IO_SIZE  字节数据,而这个宏定义为:

就是上面最开始提到的 1460 个字节长度。那如果要发送的数据大于这个 1460 会怎么进行发送呢?

      if (len > 0) {
    if (nc->flags & MG_F_UDP) {
      n = nc->iface->vtable->udp_send(nc, buf, len);
    } else {
      infof("tcp_send, len = %lu\n", len);
      n = nc->iface->vtable->tcp_send(nc, buf, len);
    }
    DBG(("%p -> %d bytes", nc, n));
  }

#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
  if (n > 0 && nc->mgr && nc->mgr->hexdump_file != NULL) {
    mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, n, MG_EV_SEND);
  }
#endif
  if (n < 0) {
    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
  } else if (n > 0) {
    nc->last_io_time = (time_t) mg_time();
    mbuf_remove(&nc->send_mbuf, n);
    mbuf_trim(&nc->send_mbuf);
  }

 真正向 socket  发送数据的接口是:nc->iface->vtable->tcp_send(nc, buf, len),返回值 n 为实际发送的字节数据,然后调整发送缓冲区的长度:

mbuf_remove(&nc->send_mbuf, n);

mbuf_trim(&nc->send_mbuf);

函数 mg_if_can_send_cb()执行完成一次,发送缓冲区里还有数据,则 poll()里会再次触发 write 事件,然后再循环调用 mg_if_can_send_cb()函数,直到把数据发送完毕。

最后知道数据发送快慢由两个因素决定:

1)每次发送数据的大小 ,这个大小就是 MG_TCP_IO_SIZE 定义的大小,具体能定义到多大是最优的还得看带宽及IO读写能力吧

2)多长时间发送一次,就直接

    while (1)
    {
        mg_mgr_poll(&mgr, 100);
    }

其实第二个参数就是select()函数的阻塞调用时间了,没有必要另外添加休眠操作了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值