LwIP协议栈源码详解——TCP/IP协议的实现

5星(超过95%的资源)
所需积分/C币:50 2017-07-24 22:22:01 1.45MB PDF
105
收藏 收藏
举报

《LwIP协议栈源码详解——TCP/IP协议的实现》,老衲五木
目录 栘植综述 2动态内存管理 3数据包 469 uI--------------------- 4pbuf释放-- 5网终接口结构-一- 16 6以太网数据接收-- ---2 7ARP表 8ARP表查询------- -26 9ARP层流程 10TP层输入 11IP分片重装1 -34 12IP分片重装2--- 37 13CMP处理-- 14TCP建立与断廾 15TCP状态转换 46 16TCP控制块- 49 17TCP建立流程 -53 18TCP状态机 19TCP输入输出函数1 60 20TCP输入输出函数2 21TCP滑动窗∏ 22TCP超时与重传一- 23TCP慢启动与拥塞避免 24TCP快速恢复重传和 Nagle算法-- 76 25TCP坚持与保活定时器 --80 26TCP定时器 ==--------------------------= ---------------- -84 27TCP终结与小结 88 28AI实现及相关数据结构 29APⅠ消息机制--- 30API函数及编程实例 --97 E-mailforrest@@foxmail.com 老神五木出品 1移植综述 如果你认为所谓的毅力是每分每秒的“艰苦忍耐”式的奋斗,那这是一和很不足的心理 状态。毅力是一和习惯,毅力是一种状态,毅力是一和生活。看了这么久的代码觉得是不是 该写点东西了,不然怎么对得起某人口中所说的科研人员这个光荣称号。初见这如山如海的 代码,着实看出了一身冷汗。现在想想其实也不是那么难,那么多革命先辈经过N长时间 才搞出米的东东怎么可能让你个毛小子几周之内搞懂。我见到的只是冰川的一小角,万里长 征的一小步,九头牛身上的一小毛..再用某人的话说,写吧,昏写,瞎写,胡写,乱写,写 写就懂了。 我想我很适合当个歌颂者,青春在风中飘着。你知道,就算大雨让这座城市颠倒,我 会给你怀抱:受不了,看见你背影来到,写下我度秒如年难捱的离骚;就算整个世界被寂寞 绑票,我也不会奔跑;逃不了,最后谁也都苍老,写下我,时间和琴声父错的城堡。我正在 听的歌。扯远了 正题,嵌入式产品连入 Internet网,这个MS是个愈演愈烈的趋势。想想,你可以足不 山广对你的产品进行配置,并获取你关心的薮据信息,多好。这也许也是物联网世界最基本 的雏形。当然,你的产品要有如此功能,那可不容易,至少它得有个日前很 Fashion的TCPP 协议栈。LWP是一套用于嵌入式系统的开放源代码TCP/P协议栈。在你的嵌入式处理器 不是很NB,内部Fash和Ram不是很强大的情况下,用它还是很合适滴 LwIP的设计者为像我这样的懒惰者提供了详细的移植说明文档,当然这还不够,他们 还尽可能的包揽了大部分工作,懒人们只要做很少的⊥作就功德圆满了。纵观整个移植过程, 使用者需要完成以下几个方面的东西 首先是Iw尠议內部使用的数据类型的定义,如u8_t,s8_t,u16t,u32t等等等等。 由」所移植平台处理器的不同和使用的编译器的不同,这些数据类型必须重新定义。想想, 一个int型数据在64位处理器上其长度为8个字节,在32位处理器上为4个字节,而在16 位处理器上就只有两个字节了。因此这部分需要使用者根据处理器位数和和使用的编译器的 特点来编写。所以在ARM7处理器上使用的 typedef unsigned int u32t移植语句用在64位处 理器的移植过程中那肯定是行不通的了 其次是实现与信号量和邮箱操作相关的函薮,比如健立、删除、等待、释放等。如果 在裸机上直接跑LWIP,这点实现起来比较麻烦,使用者必须自己去建立一套信号量和邮箱 相关的机制。一般情况下,在使用LwP的嵌入式系统中都会有操作系统的支持,而在操作 系统中信号量和邮箱往往是最基本的进程通信机制了εU)OS∏应该算是最简单的嵌入式操 作系统了吧,它也无例外的能够提供信号量和邮箱机制,只要我们将UCOS中的相关函 数做相应的封装,就可淸足LwP的需求。LWP使用邮箱和信号量来实现上层应用与协议 栈间、下层硬件驱动与协议栈间的信息交互。LWIP协议模拟了TCPP协议的分层思想, 表面上看LWIP也是有分层思想的,但从实现上看,LWIP只在一个进程内实现了各个层次 的所有工作。只体如下:LWIP完成相关初始化后,会阻塞在一个邮箱上,等待接收数据进 行处理。这个邮箱内的数据可能来自底层硬件驱动接收到的数据包,也可能来自应用程序。 当在该邮箱内取得数据后,LWIP会对数据进行解析,然后再依次调用协议栈内部上层相关 处理函数处理数据。处理结束后,LWIP继续阻塞在邮箱上等待下一批数据。当然LWIP还 有一大串的内存管理机制用以避免在各层间交互数据时大量的时间和内存开销,这将在后续 计解中慢慢道来。当然,但这样的设计使得代码理解难度加大,这一点让人头大。信号量乜 E-mailforrest@@foxmail.com 老神五木出品 可以用在应用程序与协议栈的互相通信中。比如,应用程序要发送数据了,它先把数据发到 LWIP阻塞的邮箱上,然后它挂起在一个信号量上;IWP从邮箱上取得数据处理后,释放 个信号量,告诉应用程序,你要发的数据我凵经搞定了;此后,应用程序得到信号量绻续 运行,而IWTP继续阻塞在邮箱上等待下一批处理数据。 其其次,就是与等待超时相关的函数。上面说到LwIP协议栈会阻塞在邮箱上等待接收 数据的到来。这种等待在外部看起来是一直进行的,但其实不然。一般在初始化LWIP进程 的时候,都会冋时的初始化一些超时事件,即当某些事件等待超时后,它们会自动调用一些 超时处理函数做相关处理,以满足TCPP协议栈的需求。这样看来,当LWP协议栈阻塞 等待邮箱之前,它会精明的计算到底应该等待多久,如果LWIP进程中没有初始化仟何超时 事件,那好,这种情况最简单了,永远的挂起进程就可以了,这时的等待就可以看倣是天长 地久的..有点嗳昧了。如果LWIP进程中有初始化的超时事件,这时就不能一直等了,因 为这样超时事件没有任何被执行的机会。IWP是这样做的,等待邮箱的时间设置为第一个 超吋事件的吋间长度,如果时间到了,还没等到数据,那好,直接跳出邮箱等待转而执行超 时事件,当执行完成超时事件后,再按照上述的方法继续阡塞邮箱。可以看岀,对一个LWIP 进程,需要用一个链表米管理这些超时事件。这个链表的大部分工作已经被LWHP的设计者 完成了,使用者只需要实现的仅有一个函数:该函数能够返回当前进程个超时事件链表的首 地址。IWIP內部协议要利用该首地址来査求完成相关超时事件。 其其其次,如果LW是建立在多线程操作系统之上的话,则要实现创建一个新线程的 函数。不支持多线程的操作系统,汗.,表示还没听过。不过UC/OS显然是支持多线程的, 地球人都知道。这样一个典型的LWP应用系统包括这样的三个进程:首先启动的是上层应 用程序进程,然后是LwP协议栈进程,最后是底层硬件数据包接收发送进程。通常LWIP 协议栈进程是在应用程序中调用LWIP协议栈初始化函数来创建的。注意LWIP协议栈进程 般具有最高的优先级,以便实时止桷的对数据进行响应。 其其其其次,其他一些细节之处。比如临界区保护函数,用于LWIP协议栈处理某些临 界区时使用,一般通过进临界区关中断、出临界区开中断的方式来实现;又如结构体定义时 用到的结构体封装宏,LWP的实现基」这样一种机制,即上层协议凵经明确知道了下层所 传上来的数据的结构特点,上层直接使用相关取地址计算得刭想要的数据,而避免了数据递 父时的复制与缓冲,所以定义结枃体封裝宏,禁止编译器的地址自动对齐是必须的;还有诸 如调试输出、测量记录方面的宏不做讲解。 最后,也是比较重要的地方。底层网络驱动函数的实现。这取决于你嵌入式硬件系统 所使用的网络接冂芯片,也就是网卡芯片,常见的有RIL8201BL、ENC28J60等等。不同的 接口芯片厂商都会提供丰富的驱动函数。我们只要将这些发送接收接口函数做相应的封装, 将接收到得数据包封装为LWP协议栈熟悉的数据结构、将发送的数据包分解为芯片熟悉的 数据结构就基本搞定了υ最起码的,发送一个数据包函数和接收一个数据包函数需要被实现 那就这样了吧,虽然写得草草,但终于在撤退之前搞定。好的开始是成功的一半,那 这暂且先算四分之吧。不晓得个月、两个月或者更多时间能写完否。预知后事如何,请 见下回分解。 E-mailforrest@@foxmail.com 老神五木出品 2动态内存管理 最近电力局很不给力啊,隰三差五的停电,害得我们老是痛苦的双扣斗地主,不带这样 的啊!今天还写吗?写,必须的。 昨天把LWP的移植工作框架说了一下,网上也有一大筐的关于移植细节的文档。有兴 趣的童靺不妨去找找。这里,我很想探究LwWP内部协议实现的细节,以及所有盘根错节的 问题的来龙去脉。以后的讨论研究将按照LWP英文说明文档《 Design and Implementation of the LWiP:TCP/ P Stack》的结构组织展开 这里讨论IWTP的动态内存管理机制。 总的来说,LwP的动态内存管理机制可以有三种:C运行吋厍自带的内存分配策略、 动态内存堆(HEAP分配策和动态内存池(POOL分配策略 动态内存堆分配策略和C运行时库自带的内存分配策略具有很大的相似性,这是LWP 模拟运行时库分配策实现的。这两种策略使用者只能从中选择一种,这通过头文件 Iwippools h中的宏定义 MEM LIBO_ MALLOC来实现的,当它被定义为1时则使用标准C 运行吋库自带的内存分配貪略,而为0时则使用LWIP自身的动态内存堆分配策咯。般情 况下,我们选择使用LWIP自身的动态内存堆分配策略,这里不对C运行时库自带的内存分 配策略进行讨论。 同时,动态内存堆分軋策略可以有两种实现方式,纠结..第一种就是如前所述的通过 开辟一个内存堆,然后通过模拟C运行时库的内有分配策略来实现。第二种就是通过动态 内行池的方式来实现,也即动态内存堆分配函数通过简单调用动态内存池(POOL分配函数 来完成其功能(太敷衍了事了),在这种情况下,用户需要在头文件 wippools h中定义宏 MEM USE POOLS和MEM_USE_( USTON_ POOLS为1,同时还要开辟一些额外的缓冲池 区,如下: LWIP MALLOC MEMPOOL START LWIP MALLOC MEMPOOL(20, 256) LWIP_ MALLOC_MEMPOOL(10, 512) LWIP_MALLOC_MEMPOOL(5, 1512) LWIP MALLOC MEMPOOL END 这几句摘自LWIP源码注释部分,表示为动态内存堆相关功能函数分配20个256字节 长度的内存块,10个512字节的内存块,5个1512字节的内存块。内存池管理会根据以 的宏自动在内存中静态定义一个大片内存用于内存池。在内存分配申请的时候,自动根据所 请求的大小,选择最适合他长度的池里面去申请,如果启用宏 MEM USE POOLS_ TRY BIGGER POOL,那么,如果上述的最适合长度的池中没有空间 可以用了,分配器将从更大长度的池中去中请,不过这样会浪费更多的内存。晕乎乎.….航 这样了,这种方式一般不会被用到。哎,就最后这句话给力 下血讨论动态内存堆分配策略的第一和实现方式,这也是一般情况下被使用的方式。这 部分讨论主要参照网 Olton' s blog,TA写得很好(但是也有一点小小的错误,所以一不 小心被我借用了。 动态内存堆分配策略原理就是在一个事先定义好大小的内存块中进行管理,其内存分配 的策略是采用最快合适( First fit)方式,只要找到一个比所请求的内存大的空闲块,就从 中切割出合适的块,并把剩余的部分返回到动态内存堆中。分配的内存块有个最小大小的限 E-mailforrest@@foxmail.com 老神五木出品 制,要求请求的分配大小不能小于 MIN SIZE,否则请求会被分配到 MIN SIZE大小的内存 穴间。一般MIN_SIEE为12字节,在这12个字节中前几个字节会存放内存分配器管理用 的私有数据,该数据区不能被用户程序修改,否则导致致命问题。内存释放的过程是相反的 过稈,但分配器会查看该节点前后相邻的内存块是否空闲,如果空闲则合并成一个人的内存 闲块。采用这种分配策唅,其优点就是内存浪费小,比较简单,适合用于小内存的管理 其缺点就是如果频繁的动态分配和释放,可能会造成严重的内存碎片,如果在碎片情况严重 的话,可能会导致內存分配不成功。对于动态內存的使用,比较推荐的方法就是分配->释放 分配-释放,这种使用方法能够减少内存碎片。卜面具体来看看LWIP是怎么来实现这些 网数的 mem init()内存堆的初始化函数,主要是告知内存堆的起止地址,以及初始化空闲列 表,由wp初始化时自己调用,该接口为内部私有接口,不对用户层开放。 mcm_ malloc()申请分配内存。将总共需要的字节数作为参数传递给该函数,返回值是 指向最新分配的内仔的指针,而如果内仔没有分配好,则返回值是NULL,分配的空间大小 会收到内存对齐的影响,可能会比申请的略人。返回的内存是“没有“初始化的。这块内存 可能包含任何随机的垃圾,你可以马上用有效数据或者至少是用零米初始化这块内存。内存 的分配和释放,不能在中断函数里面进行。内存堆是全局变量,因此内存的中请、释放操作 做了线程安全保护,如果有多个线程在同时进行内存申请和释放,那么可能会因为信号量的 等待而导致申请耗吋较长。 mem calloc()是对 mem malloc(函数的简单包装,他有两个参数,分别为元素的数日 和每个元素的大小,这两个参数的乘积就是要分配的内存空间的大小,与 mem mallock不 同的是它会把动态分酤的内存清零。有经验的程序员更喜欢使用 Inem calloc O,因为这样 的话新分配内存的内容就不会有什么问题,调用 mem calloc O肯定会清0,并目可以避免 调用 memseto。 休息 动态内存池(roOL分配策略可以说是一个比较笨的分配策略了,但其分配策略实现简 单,内存的分配、释放效率高,可以有效防止内存碎片的产生。不过,他的缺点是会浪费部 分内存 为什么叫POOL?这点很有趣,POOL有很多种,而这点依赖于用户配置LWP的方式 例如用户在头文件opth文件中定义 LWIP UDP为1,则在编译的时候与UDP类型内存池 就会被建立;定义 LWIP TCP为1,则在编译的时候与TCP类型内存池就会被建立。另外 还有很多其他类型的内存池,如专门存放网络包数据信息的PBUF_POOL、还有上面讲解动 态内存堆分配策略时提到的 CUSTOM_ POOLS等等等等。某种类型的POOL其单个人小是 固定的,而分配该类POOL的个数是可以用户配置的,用户应该根据协议栈实际使用状况 进行配置。把协议栈中所有的POOL挨个放到一起,并把它们放在一片连续的内存区域, 这呈现给用户的就是一个大的缓冲池。所以,所谓的缓冲池的内部组织应该是这样的:开始 处放了Δ类型的POO沺a个,紧接着放上B类型的POOL池b个,再接着放上¢类型的 POOL池¢个,直至最后N类型的PoOL跇n个。这一点很像UCOSⅡ中进程控制块和事 件控制块,先开辟一堆各种类型的放那,你要用直接米取就是了。注意,这里的分配必须是 以单个缓冲池为基本单位的,在这样的情况下,可能导致内存浪费的情况。这是很明显的啊, 不解释。 下面我来看看在LWIP实现中是怎么开辟出上面所论述的大大的缓冲池的(的这个字, 今天让我们一群人笑了很久)。基本上绝大部分人看到这部分代码都会被打得晕头转向,完 全不晓得作者是在干啥,但是仔细理解后,你不得不佩服作者超凡脱俗的代码写能力,差 点用了沉鱼落堆这个词,罪过。上代码: E-mailforrest@@foxmail.com 老神五木出品 static u8 t memp memory MEM ALIGNMENT-1 #define LWIP MEMPOOL (name, num, size, desc)+((num) ( MEMP SIZE MEMP ALIGN SiZE(size))) #include"lwip/memp std h ; 上面的代码定义了缓冲池所使用的内存缓冲区,很多人肯定会怀疑这到底是不是个数组的 定义。定义一个数组,里面居然还有 define和 include关键宇。解决问题的关键就在于头文 件 mcmp_std. h,它里面的东两可以被简化为诸多条 LWIP MEMPOOI( namc. num,sizc,dcc) 又由于用了 deline关键字将 LWIP MEMPOOL( name,num,sie,desc)定义为 +(mum)*( MEMP SIZE+ MEMP_ ALIGN_ SIZE(Size)),所以, memp_std.h被编译后就为一 条一条的+(),+(),+(),+()…所以最终的数组 memp_memory等价定义为 static u8_t memp memory MEM_ ALIGNMENT-1 ). 如果此刻你还没懂,只能说明我的表述能力有问题了。当然还有个小小的遗留问题,为什么 数组要比实际需要的大MEM_ ALIGNMENT-1?作者考虑的是编译器的字对齐问题,到此 打住,这个问题不能深究啊,以后慢慢讲。 复制上面的数组建立的方法,协议栈还建立了一些与缓冲池管理的全局变量 hemp nu:这个静态数组用于保仔各种类型缓冲池的成员数目 memp__sizes:这个静态数组用于保存各种类型缓冲池的结构大小 memp_tab:这个指针数组用于指向各种类型级冲池当前空闲节点 接卜来就是理所当然的实现函数了: memp_init():内存池的初始化,主要是为每种内存池建立链表 memp_tab,其链表是逆序 的,此外,如果有统计功能使能的话,也把记录了各种内存池的数目。 memp_malloc():如果相应的 memp_tab链表还有空闲的节点,则从中切出一个节点返回, 否则返回空。 memp_free():把释放的节点添加到相应的链表 memp_lab头上。 从上面的三个函数可以看出,动态内存池分配过程时相当的简洁直观啊。 HC:百度说是胡扯的意思。哈哈. E-mailforrest@@foxmail.com 老神五木出品 3数据包pbuf 高的地方,总是很冷。孤独,可以让人疯狂。没人能懂你!昨天讲过了LWIP的内存分 配机制。再来总之一下,IwP中常用到的内存分配策略有两种,一种是內存堆分配,一和 是内存池分配。前者可以说能随心所欲的分我们需要的合理大小的内存块(乂是‘的 缺点是当经过多次的分配释放后,内存堆中间会出现很多碎片,使得需要分配较人内存块时 分配失败;后者分配速度快,就是简单的链表操作,因为各种类型的POOL是我们事先建 立好的,但是采用POOL会有些情况下会浪费掉一定的内存空间。在LWIP中,将这两种分 配策略混合使用,达到了很好的內存使用效率 下面我们将来看看LWP中是怎样合理利用这两种分配策略的。这就顺利的过渡到了这 节要讨论的话题:LWIP的数据包缓冲的实现。 在协议栈中移动的数据包,最无疑的是整个内存管理中最重要的部分了。数据包的种类 和大小也可以说是五花八门,数数,首先从网卡上来的原始数据包,它可以是长达上千个字 节的TCP数据包,也可以是仅有几个字节的ICMP数据包;再从要发送的数据包看,上层 应用可能将自己要发送的千奇百怪形态各异的数据包递交给LWTP协议栈发送,这些数据可 能存在于应用程序管理的内存空间内,也可能存在于某个ROM上。注意,这里有个核心的 东西是当数据在各层之间传遊时,Lw极力禁止数据的拷贝工作,因为这样会耗费大量的 时间和内存。综上,LwP必须有个高效的数据包管理核心,它即能海纳百川似的兼容各种 类型的数据,又能避免在各层之间的复制数据的巨大开销。 数据包管理机构采用数据结构pbuf来描述数据包,其源码如下, struct pbuf i struct pbuf *next Void米 pay load; ul6 t tot cn. u16 t len u8 t type u8t flags u16 t ref 这个看似简单的数据结构,却够我讲一大歇的了!next字段指针指向下一个pbuf结构,因 为实际发送或接收的数据包可能很大,而每个pbuf能够管理的数据可能很少,所以,往往 需要多个 pbuf结构才能完全描述一个数据包。所以,所有的描述同一个数据包的pbuf结构 E-mailforrest@@foxmail.com 老神五木出品 需要链在一个链表上,这一点用next实现。 payload是数据指针,指向该pbuf管理的数据的 起始地址,这里,数据的起始地址可以是紧跟在pbuf结构之后的RAM,也可能是在ROM 上的某个地址,而决定这点的是当前pbuf是什么类型的,即uype字段的值,这在卜面将继 续讨论。len字段表示当前pbuf中的有效数据长度,而 tot len表示当前pbuf和其后所有pbuf 的有效数据的长度。显然, tot len字段是len字段与pbuf链中随后个 pbuf的 tot len字段 的和;pbu链中第一个pbuf的 tot len宇段表小整个数据包的长度,而最后一个pbuf的 tot len 字段必和len字段相等。typc字段表示 pbuf的类型,上要有四种类型,这点基本上涉及到 pbuf管理中最难的部分,将在下节仔细讨论。文档上说nags字段也表示pbuf的类型,不懂, type字段不是说明了pbuf的类型吗?不过在源代码里,初始化一个pbuf的时候,是将该字 段的值设为0,而在其他地方也没有用到该字段,所以,这里直接忽略掉。最后ref字段表 小该pbuf被引用的次数。这里又是一个纠结的地方咧。初始化一个pbuf的时候,ref字段值 被设置为1,当有其他phuf的next指针指向该pbuf时,该pbuf的ref字段值加一。所以 要朋除·个pbuf吋,ref的值必须为1才能刖除成功,否则删除失败。 buf的类型,令人很晕的东西。pbu有四类:PBUF_RAM、PBUF_ROM、 PBUF REF 和 PBUF POOL。下面,一个一个的米看看各种类型的特点 PBUF RAM类型的pu主要通过内存堆分配得到的。这种类型的pbu在协议栈中是 用得最多的。协议栈要发送的数据和应用程序要传递的数据一般都采用这个形式。申请 PBUF RAM类型时,协议栈会在内存堆中分配相应的大小,注意,这里的大小包括如前所 述的pbuf结构头大小和相应数据绥冲区,他们是在一片连续的内存区的。下面来看看源代 码是怎样申请 PBUF RAM型的。其中p是 pbuf型指针。 p=(struct pbuf*)men malloc(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_ PBUF +offseL) lWIP_ MEM_ ALIGN_ SIZEdength)) 可以看出,系统是调用内存堆分配函数 mem malloc进行内存分配的。分配空间的大小包括: pbuf结构头大小 SIZEOF STRUCT_PBUF,需要的数据存储空间大小 length,还有一个 offset 关于这个 offset,也有一大堆可以讨论的东西,不过先到此打住。总之,分配成功的 PBUF RAM类型的pbu如下图: nex七 y⊥a len tot len flads ref E-mailforrest@@foxmail.com 老神五木出品

...展开详情
试读 99P LwIP协议栈源码详解——TCP/IP协议的实现
立即下载
限时抽奖 低至0.43元/次
身份认证后 购VIP低至7折
一个资源只可评论一次,评论内容不能少于5个字
帝鴻龍曦 最近在學習,挺好的入門書籍
2017-10-18
回复
您会向同学/朋友/同事推荐我们的CSDN下载吗?
谢谢参与!您的真实评价是我们改进的动力~
  • 领英

  • GitHub

  • 签到新秀

关注 私信
上传资源赚钱or赚积分
最新推荐
LwIP协议栈源码详解——TCP/IP协议的实现 50积分/C币 立即下载
1/99
LwIP协议栈源码详解——TCP/IP协议的实现第1页
LwIP协议栈源码详解——TCP/IP协议的实现第2页
LwIP协议栈源码详解——TCP/IP协议的实现第3页
LwIP协议栈源码详解——TCP/IP协议的实现第4页
LwIP协议栈源码详解——TCP/IP协议的实现第5页
LwIP协议栈源码详解——TCP/IP协议的实现第6页
LwIP协议栈源码详解——TCP/IP协议的实现第7页
LwIP协议栈源码详解——TCP/IP协议的实现第8页
LwIP协议栈源码详解——TCP/IP协议的实现第9页
LwIP协议栈源码详解——TCP/IP协议的实现第10页
LwIP协议栈源码详解——TCP/IP协议的实现第11页
LwIP协议栈源码详解——TCP/IP协议的实现第12页
LwIP协议栈源码详解——TCP/IP协议的实现第13页
LwIP协议栈源码详解——TCP/IP协议的实现第14页
LwIP协议栈源码详解——TCP/IP协议的实现第15页
LwIP协议栈源码详解——TCP/IP协议的实现第16页
LwIP协议栈源码详解——TCP/IP协议的实现第17页
LwIP协议栈源码详解——TCP/IP协议的实现第18页
LwIP协议栈源码详解——TCP/IP协议的实现第19页
LwIP协议栈源码详解——TCP/IP协议的实现第20页

试读结束, 可继续阅读

50积分/C币 立即下载