Lwip实用总结

Posted 夏沫の浅雨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lwip实用总结相关的知识,希望对你有一定的参考价值。

写在前面:

本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。



一、断言处理

1、断言:LWIP_ASSERT("conn->write_offset < conn->current_msg->msg.w.len", conn->write_offset < conn->current_msg->msg.w.len);

定位:

  • V2.0.3 - - - [void lwip_netconn_do_write(void *m){}]
  • V1.4.1 - - - [static err_t do_writemore(struct netconn *conn){}]

原因:
netif_add(&xnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &ethernet_input);

解决:
netif_add(&xnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &tcpip_input);

解释:
http://savannah.nongnu.org/bugs/?func=detailitem&item_id=56531

在 os里面调用的 nosys的 ethernet_input函数

2、断言:LWIP_ASSERT("already writing or closing", msg->conn->current_msg == NULL && msg->conn->write_offset == 0);

定位:

  • V1.4.1 - - - [void do_write(struct api_msg_msg *msg){}]

原因:
内核 bug

解决:

  1. 根本问题:
    在部分写入分支中的 do_writemore函数添加
#if LWIP_SO_SNDTIMEO
  if ((conn->send_timeout != 0) &&
      ((s32_t)(sys_now() - conn->current_msg->msg.w.time_started) >= conn->send_timeout)) {
    write_finished = 1;
    if (conn->write_offset == 0) {
      /* nothing has been written */
      err = ERR_WOULDBLOCK;
      conn->current_msg->msg.w.len = 0;
    } else {
      /* partial write */
      err = ERR_OK;
      conn->current_msg->msg.w.len = conn->write_offset;
      conn->write_offset = 0; /* <= ADDED THIS */
    }
  } else
#endif /* LWIP_SO_SNDTIMEO */
  1. 版本修复:
(STABLE-2.0.0)
  ++ Bugfixes:
  2014-10-21: Simon Goldschmidt
  * api_msg.c: fixed bug #38219 Assert on TCP netconn_write with sndtimeout set

建议:升级版本至 v2.0.0及以上
官方答复:http://savannah.nongnu.org/bugs/?47666

解释:
http://savannah.nongnu.org/bugs/?38219


二、Lwip Socket API操作

1、socket默认是状态阻塞的。这就意味着当调用一个不能立即完成其任务的 socket API时,其进程将被阻塞,等待相应操作完成。

2、可能阻塞的套接字调用可分为以下四类:

  • 输入操作,包括 readreadvrecvrecvfromrecvmsg共 5个函数。
  • 输出操作,包括 writewritevsendsendtosendmsg共 5个函数。
  • 发起外出连接、及用于 TCP的 connect函数。
  • 接受外来连接,即 accept函数。

3、由于数据收发默认是阻塞模式,在阻塞模式下,最好设置超时机制,并根据相应的反馈状态作处理;否者当连接不正常,网络断开时会大概率事件永远等待(当 Lwip内核的网络检测定时器失效);

4、Lwip对数据的收 /发是全双工的,可也是线程不安全的;即它可以对同一个 Socket的收 /发分开为两个独立线程去处理(一个 Socket允许同时接收和发送),但在多线程中是不可以同时调用多个发送 /接收 Socket API函数(在对同一个 Socket上多收或多发,容易导致串包 /数据交插的问题或其他异常),最好的方式是利用消息邮箱去通知单独处理收 /发的线程;至于是否可以利用上锁来进行多线程收发,理论上是可以的,但是效果不太好。

类似问题:
https://www.zhihu.com/question/56899596、https://bbs.csdn.net/topics/110146134?page=2

5、定时处理:

  • NO_SYS模式:必需实现 sys_now();函数处理,RAW_Lwip处理(主要是低速定时任务 tcp_slowtmr();和快速定时任务 tcp_fasttmr();)都是基于该函数所获取的时间作相应处理,还包括 lwip协议栈中超时定时器的实现等。

  • RTOS模式:在 V1.4.x版本上,如果使用发送超时机制,则需要实现 sys_now();函数处理,否则可有可无;当在 V2.x.x版本上,则必需实现 sys_now();函数处理,因为此版本的 sys_timeout();需要 sys_now();提供计数时间,而在 V2.x.x之前的版本中,是不需要该部分的,由内部时间提供,现在为了统一处理,把 lwip协议栈中超时定时器的计数提交给外部接口 sys_now();来提供计数时间。

6、阻塞 /非阻塞读写区别:

  1. 假如 socket的文件描述符被设置为非阻塞方式:
    A、对于 read调用,如果接收缓冲区中有 20字节,请求读 100个字节,就会立即返回 20;如果接收缓冲区的数据大于请求读取得字节数,则立即返回请求读取的字节,剩下的数据等待下次读取;当接收缓冲区为空,返回的将是小于零的状态值,具体看 C点。
    B、对于 write调用,如果请求写入 100个字节,而发送缓冲区中只有 20个字节的空闲位置,那么 write会立即返回 20,即返回成功的数目;如果发送缓冲区还有足够空间容纳这个 write所指向的应用层 buffer的全部数据,那么当把这些数据从应用层的 buffer拷贝到内核的发送缓冲区后,会返回请求写入的字节;若 write所指向的应用层 buffer数据为空,返回的也将是小于零的状态值,具体看 C点。
    C、无论是 read /write调用,当收发处理空闲时,它们都会立即返回,由于并没有数据收发,那么返回的将是小于零的状态值,而对应的 ERRNO码一般为 EINTR、EWOULDBLOCK或 EAGAIN;另外由于是处于非阻塞状态,所以必须自己实现延时阻塞(或者是事件通知)以待其他任务可以得到执行。

  2. 假如 socket的文件描述符被设置为阻塞方式:
    A、对于 read调用,如果接收缓冲区中有 20字节,请求读 100个字节,就会立即返回 20;如果接收缓冲区的数据大于请求读取得字节数,则立即返回请求读取的字节,剩下的数据等待下次读取。
    B、对于 write调用,如果请求写 100个字节,而发送缓冲区中只有 20个字节的空闲位置,那么 write会阻塞,然后进程挂起,直到把 100个字节全部交给发送缓冲区才返回;如果发送缓冲区还有足够空间容纳这个 write所指示的应用层 buffer的全部数据,那么把这些数据从应用层的 buffer拷贝到内核的发送缓冲区,然后返回。
    C、无论是 read/write调用,当收发处理空闲时,如果没有设置超时机制,那么会一直挂起进程,直到消息通知处理才退出阻塞执行对应的收发处理。

  3. 补充
    http://nathanchen.github.io/14554141138605.html
    https://cloud.tencent.com/developer/article/1008483
    https://www.cnblogs.com/kex1n/p/7461124.html

以上是关于Lwip实用总结的主要内容,如果未能解决你的问题,请参考以下文章

BootStrap实用代码片段(持续总结)

回归 | js实用代码片段的封装与总结(持续更新中...)

国外大神总结的实用代码,30 秒学会!

LWIP总结

编程导航国外大神总结的实用代码,30 秒学会!

Android 实用代码片段