本例以LWIP為例,學習LWIP的資料結構。
LWIP經過分層之後,每一層函數所需要使用的資料結構都是不同的。
在硬體通路層,最關鍵的結構體是struct netif。
struct netif {
/** pointer to next in linked list */
struct netif *next;
/** IP address configuration in network byte order */
ip_addr_t ip_addr;
ip_addr_t netmask;
ip_addr_t gw;
/** This function is called by the network device driver
* to pass a packet up the TCP/IP stack. */
netif_input_fn input;
/** This function is called by the IP module when it wants
* to send a packet on the interface. This function typically
* first resolves the hardware address, then sends the packet. */
netif_output_fn output;
/** This function is called by the ARP module when it wants
* to send a packet on the interface. This function outputs
* the pbuf as-is on the link medium. */
netif_linkoutput_fn linkoutput;
/** This field can be set by the device driver and could point
* to state information for the device. */
void *state;
/** maximum transfer unit (in bytes) */
u16_t mtu;
/** number of bytes used in hwaddr */
u8_t hwaddr_len;
/** link level hardware address of this interface */
u8_t hwaddr[NETIF_MAX_HWADDR_LEN];
/** flags (see NETIF_FLAG_ above) */
u8_t flags;
};
struct ip_addr {
u32_t addr;
};
typedef struct ip_addr ip_addr_t;
這個結構體NETIF,描述了一個網絡接口所需的硬體資訊,并提供了向下的硬體接口層的函數接口。input和output。
low_level_input會傳回一個PBUF的結構體。
struct pbuf {
/** next pbuf in singly linked pbuf chain */
struct pbuf *next;
/** pointer to the actual data in the buffer */
void *payload;
/**
* total length of this buffer and all next buffers in chain
* belonging to the same packet.
*
* For non-queue packet chains this is the invariant:
* p->tot_len == p->len + (p->next? p->next->tot_len: 0)
*/
u16_t tot_len;
/** length of this buffer */
u16_t len;
/** pbuf_type as u8_t instead of enum to save space */
u8_t /*pbuf_type*/ type;
/** misc flags */
u8_t flags;
/**
* the reference count always equals the number of pointers
* that refer to this pbuf. This can be pointers from an application,
* the stack itself, or pbuf->next pointers from a chain.
*/
u16_t ref;
};
可以看到,PBUF是一個單連結清單的鍊節(LinkNode),用來描述一個Package的一個Segment。
多個PBUF可以組成一個Package。
另外,PBUF的TYPE,可以表示Package是哪一層的資料包。這樣,協定棧的每一層,都用PBUF來傳遞Package,但是由于TYPE是不同的,是以不同的層能夠正确的處理PBUF。
#define IP_PCB \
/* ip addresses in network byte order */ \
ip_addr_t local_ip; \
ip_addr_t remote_ip; \
/* Socket options */ \
u8_t so_options; \
/* Type Of Service */ \
u8_t tos; \
/* Time To Live */ \
u8_t ttl \
/* link layer address resolution hint */ \
IP_PCB_ADDRHINT
struct ip_pcb {
/* Common members of all PCB types */
IP_PCB;
};
#define TCP_PCB_COMMON(type) \
type *next; /* for the linked list */ \
void *callback_arg; \
/* the accept callback for listen- and normal pcbs, if LWIP_CALLBACK_API */ \
DEF_ACCEPT_CALLBACK \
enum tcp_state state; /* TCP state */ \
u8_t prio; \
/* ports are in host byte order */ \
u16_t local_port
struct tcp_pcb_listen {
/* Common members of all PCB types */
IP_PCB;
/* Protocol specific PCB members */
TCP_PCB_COMMON(struct tcp_pcb_listen);
};
struct tcp_pcb {
/** common PCB members */
IP_PCB;
/** protocol specific PCB members */
TCP_PCB_COMMON(struct tcp_pcb);
/* ports are in host byte order */
u16_t remote_port;
u8_t flags;
/* Timers */
u8_t polltmr, pollinterval;
u8_t last_timer;
u32_t tmr;
/* receiver variables */
u32_t rcv_nxt; /* next seqno expected */
u16_t rcv_wnd; /* receiver window available */
u16_t rcv_ann_wnd; /* receiver window to announce */
u32_t rcv_ann_right_edge; /* announced right edge of window */
u16_t mss; /* maximum segment size */
/* sender variables */
u32_t snd_nxt; /* next new seqno to be sent */
u32_t snd_wl1, snd_wl2; /* Sequence and acknowledgement numbers of last
window update. */
u32_t snd_lbb; /* Sequence number of next byte to be buffered. */
u16_t snd_wnd; /* sender window */
u16_t snd_wnd_max; /* the maximum sender window announced by the remote host */
u16_t acked;
u16_t snd_buf; /* Available buffer space for sending (in bytes). */
u16_t snd_queuelen; /* Available buffer space for sending (in tcp_segs). */
/* These are ordered by sequence number: */
struct tcp_seg *unsent; /* Unsent (queued) segments. */
struct tcp_seg *unacked; /* Sent but unacknowledged segments. */
struct pbuf *refused_data; /* Data previously received but not yet taken by upper layer */
/* Function to be called when more send buffer space is available. */
tcp_sent_fn sent;
/* Function to be called when (in-sequence) data has arrived. */
tcp_recv_fn recv;
/* Function to be called when a connection has been set up. */
tcp_connected_fn connected;
/* Function which is called periodically. */
tcp_poll_fn poll;
/* Function to be called whenever a fatal error occurs. */
tcp_err_fn errf;
};
tcp_pcb_listen隻用作STUB,并不能作為控制塊使用。是以它隻具有資源描述的資料成員。
tcp_pcb是控制塊,具有控制TCP通信狀态的各種變量和FLAG。另外,它還具有向上的應用接口層的函數接口。例如,sent,recv,connected。
STUB中有一個Callback,accept,當監聽成功時,會malloc一個新的TCP_PCB,并将STUB中注冊的accept這個Callback填充給新建立的TCP_PCB。
建立的TCP_PCB,它的state被設定為SYN_RCVD。是以在tcp_process處理時,會調用accept這個Callback。
err_t accept_callback(void *arg, struct tcp_pcb *newpcb, err_t err)
{
static int connection = 1;
/* set the receive callback for this connection */
tcp_recv(newpcb, recv_callback);
/* just use an integer number indicating the connection id as the
callback argument */
tcp_arg(newpcb, (void*)(UINTPTR)connection);
/* increment for subsequent accepted connections */
connection++;
return ERR_OK;
}
通常在accept_callback中,為建立的TCP_PCB配置其他的Callback。例如recv。這樣,在進行TCP通信時,當tcp_input處理了TCP的封包,得到了載荷之後,就可以調用recv_callback,進行後續的載荷處理任務。
err_t recv_callback(void *arg, struct tcp_pcb *tpcb,
struct pbuf *p, err_t err)
{
/* do not read the packet if we are not in ESTABLISHED state */
if (!p) {
tcp_close(tpcb);
tcp_recv(tpcb, NULL);
return ERR_OK;
}
/* indicate that the packet has been received */
tcp_recved(tpcb, p->len);
/* echo back the payload */
/* in this case, we assume that the payload is < TCP_SND_BUF */
if (tcp_sndbuf(tpcb) > p->len) {
err = tcp_write(tpcb, p->payload, p->len, 1);
} else
xil_printf("no space in tcp_sndbuf\n\r");
/* free the received pbuf */
pbuf_free(p);
return ERR_OK;
}
在recv_callback中,做了簡單的ECHO處理,将LWIP送出給應用層的載荷,利用TCP_PCB,再發送出去。
tcp_write隻是負責把PBUF挂載到sendqueue中去,并不完成實際發送任務。
發送任務的執行,是在tcp_output中被執行的。