您好,欢迎来到欧得旅游网。
搜索
您的当前位置:首页lwip中各种函数,标志位的总结

lwip中各种函数,标志位的总结

来源:欧得旅游网
lwip中各种函数,标志位的总结

mem_init( ) 内存堆的初始化函数,主要是告知内存堆的起止地址,以及初始化空闲表,由lwip 初始化时自己调用,该接口为内部私有接口,不对用户层开放

mem_malloc( ) 申请分配内存。将总共需要的字节数作为参数传递给该函数,返回值是指向最新分配的内存的指针,而如果内存没有分配好,则返回值是NULL

mem_calloc( ) 是对mem_malloc( )函数的简单包装,他有两个参数,分别为元素的数目和每个元素的大小,这两个参数的乘积就是要分配的内存空间的大小,与mem_malloc()不同的是它会把动态分配的内存清零。有经验的程序员更喜欢使用mem_ calloc (),memp_num:这个静态数组用于保存各种类型缓冲池的成员数目memp_sizes:这个静态数组用于保存各种类型缓冲池的结构大小memp_tab:这个指针数组用于指向各种类型缓冲池当前空闲节点

memp_init():内存池的初始化,主要是为每种内存池建立链表memp_tab,其链表是逆序的,此外,如果有统计功能使能的话,也把记录了各种内存池的数目。

memp_malloc():如果相应的memp_tab链表还有空闲的节点,则从中切出一个节点返回,

否则返回空。

memp_free()把释放的节点添加到相应的链表memp_tab头上。系统是调用内存堆分配函数mem_malloc进行内存分配的。分配

空间的大小包括pbuf结构头大小SIZEOF_STRUCT_PBUF,需要的数据存储空间大小length,还有一个offset系统是调用内存堆分配函数mem_malloc进行内存分配的。段区域的offset的大小,这段区域用来存储数据的包头,如TCP包头,IP包头等

pbuf_free(A)函数来删除pbuf结构

PBUF_POOL 类型和PBUF_ROM类型、PBUF_REF类型需要通过memp_free()函数删除,PBUF_RAM类型需要通过mem_free()函数

删除

memp_memory是缓冲池的起始地址,前面已有所讨论;MEMP_MAX是POOL 类型数; memp_tab 用于指向某类POOL 空闲链表的起始节点;memp_num表示各种类型POOL的个数;memp_sizes表示各种类型单个POOL的大小,对于MEMP_PBUF_POOL和MEMP_PBUF型的POOL,其大小是pbuf 头和pbuf可装载数据大小的总和。

网络接口

在LWIP中,是通过一个叫做netif的网络结构体来描述一个硬件网络接口的

struct netif {

struct netif *next; // 指向下一个netif结构的指针

struct ip_addr ip_addr; // IP 地址相关配置struct ip_addr netmask;

struct ip_addr gw;

err_t (* input)(struct pbuf *p, struct netif *inp); //调用这个函数可以从网卡上取得一个数据包

err_t (* output)(struct netif *netif, struct pbuf *p, // IP 层调用这个函数可以向网卡发送

structip_addr*ipaddr); //一个数据包

err_t (* linkoutput)(struct netif *netif, struct pbuf *p); // ARP模块调用这个函数向网卡发送一个数据包

void *state; // 用户可以独立发挥该指针,用于指向用户关心的网卡信息

u8_t hwaddr_len; // 硬件地址长度,对于以太网就是MAC地址长度,为6各字节

u8_t hwaddr[NETIF_MAX_HWADDR_LEN]; //MAC 地址 u16_t mtu; // 一次可以传送的最大字节数,对于以太网一般设为1500

u8_t flags; // 网卡状态信息标志位

char name[2]; // 网络接口使用的设备驱动类型的种类u8_t num; // 用来标示使用同种驱动类型的

不同网络接口 };

output字段向一个函数该函数的三个参数是pbuf类型、netif 类型和ip_addr类型,返回参数是err_t类型。其中pbuf代表要发送的数据包。ipaddr 代表网卡需要将该数据包发送到的地址,该地址应该是接收实际的链路层帧的主机的Ip地址

ethernetif_init底层接口初始化函数

tcpip_input 函数是向IP层递交数据包的函数

netif->next = netif_list; //将初始化后的节点插入链表netif_list netif_list = netif; // netif_list 指向链表头

low_level_init(netif); //底层硬件初始化函数 static void low_level_init(struct netif *netif) {

netif->hwaddr_len = ETHARP_HWADDR_LEN; //设置变量enc28j60的hwaddr_len字段

netif->hwaddr[0] = 'F'; //初始化变量enc28j60的MAC地址netif->hwaddr[1] = 'O'; //设什么地址用户自由发挥吧,但是不要与其他网络设备的MAC地址重复。

netif->hwaddr[2] = 'R'; netif->hwaddr[3] = 'E'; netif->hwaddr[4] = 'S'; netif->hwaddr[5] = 'T';

netif->mtu = 1500//最大允许传输单元 netif->flags = NETIF_FLAG_BROADCAST | \\ NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;

enc28j60_init(netif->hwaddr); //与底层驱动硬件驱动程序密切相关的硬件初始化函数

}

netif_set_default函数初始化缺省网络接口。协议栈除了 有个netif_list全局变量指向netif网络接口结构的链表,还有个全局变量netif_default全局变量指向缺省的网络接口结构。以太网的数据接收

LWIP使用了一个eth_hdr的数据结构来描述以太网数据包包头的14个字节

关于网卡数据包的接收和发送。LWIP 中实现了接收一个数据包和发送一个数据包函数的框架,这两个函数分别是low_level_input和low_level_outpu

PACK_STRUCT_xxx都是与编译器字对齐相关的宏定义 htons(ethhdr->type)函数的使用,htons函数的功能是将一个 半字长的数据从网络字节顺序转换到我们的处理器支持的字节顺序

任何网络的通信都是基于底层硬件链路的,底层的数据链路有着自己的一套寻址机制,在以太网中,往往是通过一个48位的MAC地址来标示不同的网络通信设备的。而TCP/IP协议的上层是使用IP地址作为各个主机间通信寻址机制的。ARP缓存表

LWIP中描述缓存表项的数据结构叫etharp_entry struct etharp_entry {

#if ARP_QUEUEING //ARP_QUEUEING是编译选项,表示是否允许缓存表项有数据包缓冲队列

struct etharp_q_entry *q; // 数据包缓冲队列指针#endif struct ip_addr ipaddr; // 目标IP地址 struct eth_addr ethaddr; // MAC 地址

enum etharp_state state; // 描述该entry的状态 u8_tctime; // 描述该entry的时间信息 struct netif *netif; // 相应网络接口信息 };

ipaddr 和ethaddr字段就是分别用于存储IP地址和MAC地址的,它们是ARP缓存表项的核心部分了

LWIP内核通过数组的方式来创建ARP缓存表,如下, static struct etharp_entry arp_table[ARP_TABLE_SIZE]; 要发往该表项中IP 地址处的数据包会被连接在表项对应的数据包缓冲队列上,当等到该表项稳定后,这些数据包才会被发送出去。这就是为什么每个表项需要有数据包缓冲队列指针了。

ctime字段记录表项处于某个状态的时间,当某表项的ctime 值大于规定的表项最大生存值时,该表项会被内核删除

目的地址为全 1 的特殊地址是广播地址。在ARP表项建立前,源主机只知道目的主机的IP 地址,并不知道其MAC地址,所以在数据链路上,源主机只有通过广播的方式将ARP 请求数据包发送出去。电缆上的所有以太网接口都要接收广播的数据包,并检测数据包是否是发给自己的,这点通过对照目的IP 地址来实现,如果是发给自己的,目的主机需要回复一个ARP应答数据包给源主机,以告诉源主机自己的MAC地址。

对于一个ARP请求来说,除目的端MAC地址外的所有其他的字段都有填充值。当目的主机收到一份给自己的ARP请求报文后,它就把自己的硬件地址填进去,然后将该请求数据包的源主机信息和目的主机信息交换位置,并把操作字段op 置为2,最后把该新构建的数据包发送回去,这就是ARP响应。

ARP搜索

find_entry,该函数主要功能是寻找一个匹配的ARP表项或者创建一个新的ARP表项,并返回该表项的索引号

LWIP中有个全局的变量etharp_cached_entry,它始终保存着上次用到的索引号

etharp_query,该函数的功能是向给定的IP地址发送一个数据包或者发送一个ARP请求如果给定的IP地址不在ARP表中,则一个新的ARP表项会被创建,此时该表项处于pending 状态,同时一个关于该IP地址的ARP请求包会被广播出去,再同时要发送的数据包会被挂接在该表项的数据缓冲指针上;如果IP地址在ARP表中有相应的表项存在,但该表项处于pending状态,则操作与前者相同,即发送一个

ARP请求和挂接数据包;如果IP地址在ARP表中有相应的表项存在,且表项处于stable状态,此时再来判断给定的数据包是否为空,不为空则直接将该数据包发送出去,为空则向该IP 地址发送一个ARP请求

update_arp_entry,该函数用于更新ARP缓存表中的表项或者在缓存表中插入一个新的表项。该函数会在收到一个IP数据包或ARP数据包后被调用

arp_table[i].q = q->next; // 缓冲链表表头指向下一个节点ipaddr 和ethaddr分别对应的ip地址和mac地址,

update_arp_entry的流程如下:先通过调用find_entry 找到对应ipaddr 对应的表项,并设置相应的arp表项的成员(主

要是state,netif,ethaddr,cttime)

LWIP利用netif.input指向的函数接收以太网数据包,通常这个函数是ethernet_input。ethernet_input 根据以太网首部的类型字段判断收到的数据包的类型,如果是IP 包,则将该包递交给etharp_ip_input,如果是

ARP

包,则将该包递交给

etharp_arp_input并不是说ethernet_input 直接与底层硬件交互接收数据包,而是更底层的函数接收到数据包后将数据包递交给ethernet_input,ethernet_input再对其进行处理ARP应答包,主要的工作就是更新arp表

etif.output指向的函数发送IP数据包,通常这个函数是etharp_output。注意,这里并不是说etharp_output直接与底层硬件交互发送数据包,而是将数据包做相应的处理,主要是将IP数据包打包成以太网帧数据,最终递交给netif.linkoutput函数来发送的。

IP层的输入

8位协议字段用来描述该IP 数据包是来自于上层的哪个协议,该值为1表示为ICMP协议,该值为2表示IGMP协议,该值为6表示TCP协议,该值为17表UDP协议

LWIP中是怎么样来描述这个IP数据报头的,使用的结构叫ip_hdr

从以太网底层进来的数据包经过ethernet_input函数分发给

IP模块或者ARP模块,分发给IP 模块是通过调用ip_input 函数完成的,当然在递交前,ethernet_input需要将数据包去掉以太网头

对IP 数据报头做校验,该工作是函数inet_chksum 完成的 ip_input 函数会遍历netif_list链表上的netif结构以找到匹配的IP地址,并记录该netif结构体变量,也即记录该网卡

这就是IP 分片数据包的重装,ip_input函数通过数据包的3位标志和13位片偏移字段判断发给自己的该IP包是不是分片包,如果是,则需要将该分片包暂存,等到接收完所有分片包后,统一将整个数据包递交给上层应用程序

ip_input函数根据IP数据包头内部的协议字段判断该数据包应该被递交给哪个上层协议,并调用相应的函数递交数据包。是UDP协议,则调用udp_input函数;是TCP协议,则调用tcp_input 函数;是ICMP协议,则调用icmp_input 函数;是IGMP协议,则调用igmp_input函数;如果都不是,则调用函数icmp_dest_unreach 返回一个协议不可达ICMP 数据包给源主机,同时删除数据包。

TCP的建立与断开

源端口号和目的端口号,用于标识发送端和接收端的应用进程。这两个值加上IP 首部中的源IP地址和目的IP地址就能

唯一确定一个TCP连接。一个IP地址和一个端口号也称为一个插口(socket)

32位序号字段用来标识从TCP发送端到TCP接收端的数据字节流,用它来标识这个文段中的第一个数据字节的序号。当建立一个新的连接时,SYN标志置1,序号字段包?由这个发送主机选择的该连接上的初始序号ISN(Initial Sequence Number)。该主机要发送数据的第一个字节序号为ISN+1

32 位确认序号只有ACK标志为1时才有效,它包?发送确认的一端所期望收到的下一个序号。因此,确认序号应当是上次已成功收到数据字节序号加1。当一个TCP连接被正确建立后,ACK字段总是被设置为1的。

在T C P首部中有6个标志比特。URG紧急指针(urgent pointer)

有效标识;ACK确认序号有效标识;PSH 接收方应该尽快将这个报文段交给应用层;RST重建连接;SYN同步序号,用来发起一个连接;FIN 发端完成发送任务

最长报文大小,又称为MSS PACK_STRUCT_BEGIN struct tcp_hdr {

PACK_STRUCT_FIELD(u16_t src); // 源端口 PACK_STRUCT_FIELD(u16_t dest); // 目的端口 PACK_STRUCT_FIELD(u32_t seqno); // 序号 PACK_STRUCT_FIELD(u32_t ackno); // 确认序号

PACK_STRUCT_FIELD(u16_t _hdrlen_rsvd_flags); // 首部长度+保留位+标志位

PACK_STRUCT_FIELD(u16_t wnd); // 窗口大小 PACK_STRUCT_FIELD(u16_t chksum); // 校验和 PACK_STRUCT_FIELD(u16_t PACK_STRUCT_STRUCT;

PACK_STRUCT_END

它首先打开某个端口,进入LISTEN状态以侦?客户端的连接请求。当服务器收到客户端的SYN连接请求,则进入SYN_REV 状态,并向客户端返回一个ACK及自身的SYN包,此后,服务器等待客户端返回一个确认包,收到该ACK包后,服务器进入ESTABLISHED状态,并可以和服务器进行稳定的数据交换过程。

当服务器收到客户端发送的一个断开数据包FIN时,则进入CLOSE_WAIT状态,并向上层应用程序通告这个消息,同时向客户端返回一个ACK包,此时客户端到服务器方向的连接断开成功;此后,当服务器上层应用处理完毕相关信息后会向客户端发送一个FIN包,并进入LASK_ACK状态,等待客户端返回一个ACK包,当收到返回的ACK包后,此时服务器到客户端方向的连接断开成功,服务器端至此进入初始的CLOSED状态

TCP控制块

urgp);

//

紧急指针}

struct tcp_pcb {

IP_PCB; //这是一个宏,描述了连接的IP相关信息,包括双方IP地址,TTL等信息

struct tcp_pcb *next; //用于连接各个TCP控制块的链表指针 enum tcp_state state; //TCP 连接的状态,即为状态图中描述的那些状态

u8_t prio; //该控制块的优先级 void *callback_arg;// u16_t local_port; //?地端口 u16_t remote_port; //远程端口

u8_t flags;// 附加状态信息,如连接是快速恢复、一个被延迟的ACK 是否被发送等

#define TF_ACK_DELAY (u8_t)0x01U /* Delayed ACK. *///这些宏定义是为flags字段

#define TF_ACK_NOW (u8_t)0x02U /* Immediate ACK. *///定义的掩码

#define TF_INFR (u8_t)0x04U /* In fast recovery. */ #define TF_RESET (u8_t)0x08U /* Connection was reset. */ #define TF_CLOSED (u8_t)0x10U /* Connection was sucessfully closed. */

#define TF_GOT_FIN (u8_t)0x20U /* Connection was closed by the remote end. */

#define TF_NODELAY (u8_t)0x40U /* Disable Nagle algorithm */

// 接收相关字段

u32_t rcv_nxt; //期望接收的下一个字节,即它向发送端ACK的序号

u16_t rcv_wnd; //接收窗口

u16_t rcv_ann_wnd; //通告窗口大小,较低版?中无该字段 u32_t tmr; // 该字段记录该PCB被创建的时刻

u8_t polltmr, pollinterval; // 三个定时器,后续讲解

u16_t rtime; //重传定时,该值随时间增加,当大于rto的值时则重传发生

u16_t mss; //最大数据段大小 //RTT估计相关的参数

u32_t rttest; //估计得到的500ms滴答数 u32_t rtseq; //用于测试RTT的包的序号 s16_t sa, sv; //RTT 估计出的平均值及其时间差

u16_t rto; // 重发超时时间,利用前面的几个值计算出来 u8_t nrtx; // 重发的次数,该字段在数据包多次超时时被使用到,与设置rto的值

相关// 快速重传/恢复相关的参数u32_t

lastack; // 最大的确认序号,该字段不解u8_t dupacks; // 上面这个序号被重传的次数

// 阻塞控制相关参数

u16_t cwnd; //连接的当前阻塞窗口 u16_t ssthresh; // 慢速启动阈值 // 发送相关字段

u32_t snd_nxt, // 下一个将要发送的字节序号 snd_max, // 最高的发 送字节序号

snd_wnd, // 发送窗口

snd_wl1, snd_wl2, // 上次窗口更新时的数据序号和确认序号 snd_lbb; // 发送队列中最后一个字节的序号 u16_t acked; //

u16_t snd_buf; // 可用的发送缓冲字节数 u8_t snd_queuelen; // 可用的发送包数 struct tcp_seg *unsent; // ?发送的数据段队列

struct tcp_seg *unacked; // 发送了?收到确认的数据队列 struct tcp_seg *ooseq; // 接收到序列以外的数据包队列

#if LWIP_CALLBACK_API // 回调函数,部分函数在较低版?没定义

err_t (* sent)(void *arg, struct tcp_pcb *pcb, u16_t space); err_t (* recv)(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err); // 数据包接收回调函数

err_t (* connected)(void *arg, struct tcp_pcb *pcb, err_t err); err_t (* accept)(void *arg, struct tcp_pcb *newpcb, err_t err); err_t (* poll)(void *arg, struct tcp_pcb *pcb); void (* errf)(void *arg, err_t err); #endif /* LWIP_CALLBACK_API */ //

剩下的所有字段在较低版?中均?定义,用到时再讲解u32_t keep_idle;

#if LWIP_TCP_KEEPALIVE

u32_t keep_intvl; // 保活定时器,用于检测空闲连接的另一端是否崩溃

u32_t keep_cnt;

#endif /* LWIP_TCP_KEEPALIVE */

u32_t persist_cnt; // 这两个字段可以使窗口大小信息保持不断流动

u8_t persist_backoff; u8_t keep_cnt_sent; };

17 TCP建立流程

函数会初始化新的tcp_pcb 的内容,源 码如下:

if (pcb != NULL) {

memset(pcb, 0, sizeof(struct tcp_pcb)); // 清0所有字段的值 pcb->prio = TCP_PRIO_NORMAL; // 设置PCB的优先级为64,优先级在1~127之间

pcb->snd_buf = TCP_SND_BUF; // TCP 发送数据缓冲区剩余大小

pcb->snd_queuelen = 0; // 发送缓冲中的数据包pbuf个数 pcb->rcv_wnd = TCP_WND; // 接收窗口大小 pcb->rcv_ann_wnd = TCP_WND; // 通告窗口大小 pcb->tos = 0; // IP 报头部TOS字段 pcb->ttl = TCP_TTL; // IP 报头部TTL字段

pcb->mss = (TCP_MSS > 536) ? 536 : TCP_MSS; // 设置最大段大小,不能超过536字节

pcb->rto = 3000 / TCP_SLOW_INTERVAL;// 初始超时时间值,为6s

pcb->sa = 0; // 估计出的RTT平均值??

pcb->sv = 3000 / TCP_SLOW_INTERVAL; // 估计出的RTT方差??

pcb->rtime = -1; //重传定时器,当该值大于rto 时则重传发生 pcb->cwnd = 1; // 阻塞窗口

iss = tcp_next_iss(); // iss 为一个临时变量,保存该连接的初始数据序列号

pcb->snd_wl2 = iss; // 上一个窗口更新时收到的ACK号 pcb->snd_nxt = iss; // 下一个将要发送的数据编号 pcb->snd_max = iss; // 发送了的最大数据编号 pcb->lastack = iss; // 上一个ACK编号

pcb->snd_lbb = iss; // 下一个将要缓冲的数据编号

pcb->tmr = tcp_ticks; // tcp_ticks是一个全局变量,记录了当前协议时钟滴答

pcb->polltmr = 0; // ?解 #if LWIP_CALLBACK_API

pcb->recv = tcp_recv_null; // 注册默认的接收回调函数

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- ovod.cn 版权所有

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务