目錄
2.8 netbuf_next()與netbuf_first()
1. netbuf 結構體
LwIP 爲了更好描述應用線程發送與接收的數據,並且爲了更好管理這些數據的緩衝區,LwIP 定義了一個netbuf 結構體,它是基於pbuf 上更高一層的封裝,記錄了主機的IP 地址與端口號,在這裏再提醒一下大家,端口號對應的其實就是應用線程。在接收的時候,應用程序肯定需要知道到底是誰發數據給自己,而在發送的時候,應用程序需要將自己的端口號與IP 地址填充到netbuf 結構體對應字段中。
// netbuf.h
/** "Network buffer" - contains data and addressing info */
struct netbuf {
struct pbuf *p, *ptr;
ip_addr_t addr;
u16_t port;
#if LWIP_NETBUF_RECVINFO || LWIP_CHECKSUM_ON_COPY
u8_t flags;
u16_t toport_chksum;
#if LWIP_NETBUF_RECVINFO
ip_addr_t toaddr;
#endif /* LWIP_NETBUF_RECVINFO */
#endif /* LWIP_NETBUF_RECVINFO || LWIP_CHECKSUM_ON_COPY */
};
1):netbuf 的p 字段的指針指向pbuf 鏈表,這是基於pbuf 上封裝的結構體,因此,ptr 字段的指針也是指向pbuf,但是它與p 字段的指針有一點不一樣,因爲它可以指向任意的pbuf,由netbuf_next()與netbuf_first()函數來控制。
2): addr 字段記錄了數據發送方的IP 地址
3):port 記錄了數據發送方的端口號
netbuf 結構體指向示意圖具體見圖,虛線表示ptr 指針的指向位置是不固定的,它是由netbuf_next()函數與netbuf_first()函數來調整的
指向不同類型的pbuf 鏈表
指向相同類型pbuf 鏈表
2. netbuf 相關函數說明
netbuf 是LwIP 描述用戶數據很重要的一個結構體,因爲LwIP 是不可能讓我們直接操作pbuf 的,因爲分層的思想,應用數據必然是由用戶操作的,因此LwIP 會提供很多函數接口讓用戶對netbuf 進行操作,無論是UDP 報文還是TCP 報文段,其本質都是數據,要發送出去的數據都會封裝在netbuf 中,然後通過郵箱發送給內核線程(tcpip_thread 線程),然後經過內核的一系列處理,放入發送隊列中,然後調用底層網卡發送函數進行發送,反之,應用線程接收到數據,也是通過netbuf 進行管理,下面一起來看看LwIP 提供給我們操作netbuf 的相關函數。
2.1 netbuf_new()
函數的功能是申請一個新的netbuf 結構體內存空間,通過memp 內存池進行申請,大小爲MEMP_NETBUF,並且將netbuf 結構體全部初始化爲0,並且返回一個指向netbuf結構體的指針,此時的netbuf 結構體的p 與ptr 字段不指向任何的pbuf.
struct
netbuf *netbuf_new(void)
{
struct netbuf *buf;
buf = (struct netbuf *)memp_malloc(MEMP_NETBUF);
if (buf != NULL) {
memset(buf, 0, sizeof(struct netbuf));
}
return buf;
}
2.2 netbuf_delete()
與netbuf_new()函數相反,釋放一個netbuf 結構體內存空間,如果netbuf 結構體的p或者ptr 字段指向的pbuf 是擁有數據的,那麼對應的pbuf 也會被釋放掉
void
netbuf_delete(struct netbuf *buf)
{
if (buf != NULL) {
if (buf->p != NULL) {
pbuf_free(buf->p);
buf->p = buf->ptr = NULL;
}
memp_free(MEMP_NETBUF, buf);
}
}
2.3 netbuf_alloc()
爲netbuf 結構體中的p 字段指向的數據區域分配指定大小的內存空間,簡單來說就是申請pbuf 內存空間,由於這個函數是在應用層調用的,因此這個內存會包含鏈路層首部、IP 層首部與傳輸層首部大小,當然,這些空間是附加上去的,用戶指定的是數據區域大小,當然還有很重要的一點就是,如果當前netbuf 中已經存在數據區域了,那麼這個數據區域會被釋放掉,然後重新申請用戶指定大小的數據區域,而函數的返回是一個指向數據區域起始地址的指針(即pbuf 的payload 指針)
void *
netbuf_alloc(struct netbuf *buf, u16_t size)
{
LWIP_ERROR("netbuf_alloc: invalid buf", (buf != NULL), return NULL;);
/* Deallocate any previously allocated memory. */
if (buf->p != NULL) {
pbuf_free(buf->p);
}
buf->p = pbuf_alloc(PBUF_TRANSPORT, size, PBUF_RAM);
if (buf->p == NULL) {
return NULL;
}
LWIP_ASSERT("check that first pbuf can hold size",
(buf->p->len >= size));
buf->ptr = buf->p;
return buf->p->payload;
}
2.4 netbuf_free()
直接釋放netbuf 結構體指向的pbuf 內存空間,如果結構體中指向pbuf 的內容爲空,則不做任何釋放操作,直接將p 與ptr 字段的指針設置爲NULL
void
netbuf_free(struct netbuf *buf)
{
LWIP_ERROR("netbuf_free: invalid buf", (buf != NULL), return;);
if (buf->p != NULL) {
pbuf_free(buf->p);
}
buf->p = buf->ptr = NULL;
#if LWIP_CHECKSUM_ON_COPY
buf->flags = 0;
buf->toport_chksum = 0;
#endif /* LWIP_CHECKSUM_ON_COPY */
}
2.5 netbuf_ref()
函數與netbuf_alloc()函數很像,都是申請內存空間,但是,有一個很大的不同,netbuf_ref()函數只申請pbuf 首部的內存空間,包含鏈路層首部、IP 層首部與傳輸層首部,而不會申請數據區域內存空間,然後把pbuf 的payload 指針指向用戶指定的數據區域起始地址dataptr,這種申請經常在發送靜態數據的時候用到,因爲數據保存的地址是固定的,而不用動態申請,如果netbuf 的p 或者ptr 字段已經指向了pbuf,那麼這些pbuf 將被釋放掉.
注意:在使用該函數的時候用戶需要傳遞有效的靜態數據區域起始地址,比如某個靜態字符串的起始地址。
err_t
netbuf_ref(struct netbuf *buf, const void *dataptr, u16_t size)
{
LWIP_ERROR("netbuf_ref: invalid buf", (buf != NULL), return ERR_ARG;);
if (buf->p != NULL) {
pbuf_free(buf->p);
}
buf->p = pbuf_alloc(PBUF_TRANSPORT, 0, PBUF_REF);
if (buf->p == NULL) {
buf->ptr = NULL;
return ERR_MEM;
}
((struct pbuf_rom *)buf->p)->payload = dataptr;
buf->p->len = buf->p->tot_len = size;
buf->ptr = buf->p;
return ERR_OK;
}
2.6 netbuf_chain()
netbuf_chain()函數是將tail 中的pbuf 數據連接到head 中的pbuf 後面,形成一個pbuf鏈表,在調用此函數之後,會將tail 結構刪除
void
netbuf_chain(struct netbuf *head, struct netbuf *tail)
{
LWIP_ERROR("netbuf_chain: invalid head", (head != NULL), return;);
LWIP_ERROR("netbuf_chain: invalid tail", (tail != NULL), return;);
pbuf_cat(head->p, tail->p);
head->ptr = head->p;
memp_free(MEMP_NETBUF, tail);
}
2.7 netbuf_data()
獲取netbuf中的數據指針和數據長度。
err_t
netbuf_data(struct netbuf *buf, void **dataptr, u16_t *len)
{
LWIP_ERROR("netbuf_data: invalid buf", (buf != NULL), return ERR_ARG;);
LWIP_ERROR("netbuf_data: invalid dataptr", (dataptr != NULL), return ERR_ARG;);
LWIP_ERROR("netbuf_data: invalid len", (len != NULL), return ERR_ARG;);
if (buf->ptr == NULL) {
return ERR_BUF;
}
*dataptr = buf->ptr->payload;
*len = buf->ptr->len;
return ERR_OK;
}
2.8 netbuf_next()與netbuf_first()
netbuf_next()用於移動netbuf 的ptr 數據指針,使ptr 指針指向pbuf 鏈表的下一個pbuf。同樣的netbuf_first()函數可以將ptr 指針指向pbuf 鏈表的第一個pbuf。這兩個函數是很有用的,比如netbuf 中p 字段的指針指向一個pbuf 鏈表,並且pbuf 鏈表中擁有多個pbuf,那麼需要配合netbuf_data()函數將鏈表中的所有的pbuf 讀取並且處理;如果netbuf_next()函數的返回值爲0,表示調整成功,而如果返回值小於0 時,則表示調整失敗
s8_t
netbuf_next(struct netbuf *buf)
{
LWIP_ERROR("netbuf_next: invalid buf", (buf != NULL), return -1;);
if (buf->ptr->next == NULL) {
return -1;
}
buf->ptr = buf->ptr->next;
if (buf->ptr->next == NULL) {
return 1;
}
return 0;
}
void
netbuf_first(struct netbuf *buf)
{
LWIP_ERROR("netbuf_first: invalid buf", (buf != NULL), return;);
buf->ptr = buf->p;
}
2.9 netbuf_copy()
這個函數用於將netbuf 結構體數據區域pbuf 中的所有數據拷貝到dataptr 指針指向的存儲區,即使pbuf(鏈表)中的數據被保存在多個pbuf 中,它也會完全拷貝出來,len 參數指定要拷貝數據的最大長度,如果netbuf 的數據區域空間小於len 指定的大小,那麼內核只會拷貝netbuf 數據區域大小的數據,此外,該函數本質是一個宏定義,真正實現的函數在pbuf.c
#define netbuf_copy_partial(buf, dataptr, len, offset) \
pbuf_copy_partial((buf)->p, (dataptr), (len), (offset))
#define netbuf_copy(buf,dataptr,len) netbuf_copy_partial(buf, dataptr, len, 0)
2.10 netbuf_take()
函數用於將用戶指定區域的數據dataptr 拷貝到netbuf 結構體數據區域pbuf 中,可能用戶數據太多,一個pbuf 存儲不下用戶的數據,那麼內核將對數據進行切割處理,使用多個pbuf 存儲,len 參數指定要拷貝數據的長度
#define netbuf_take(buf, dataptr, len) pbuf_take((buf)->p, dataptr, len)
/**
* @ingroup pbuf
* Copy application supplied data into a pbuf.
* This function can only be used to copy the equivalent of buf->tot_len data.
*
* @param buf pbuf to fill with data
* @param dataptr application supplied data buffer
* @param len length of the application supplied data buffer
*
* @return ERR_OK if successful, ERR_MEM if the pbuf is not big enough
*/
err_t
pbuf_take(struct pbuf *buf, const void *dataptr, u16_t len);
#define netbuf_len(buf) ((buf)->p->tot_len)
#define netbuf_fromaddr(buf) (&((buf)->addr))
#define netbuf_set_fromaddr(buf, fromaddr) ip_addr_set(&((buf)->addr), fromaddr)
#define netbuf_fromport(buf) ((buf)->port)
3. netconn 結構體
在LwIP 中,如TCP 連接,UDP 通信,都是需要提供一個編程接口給用戶使用的,那麼爲了描述這樣子的一個接口,LwIP 抽象出來一個nettonn 結構體,它能描述一個連接,供應用程序使用,同時內核的NETCONN API 接口也對各種連接操作函數進行了統一的封裝,這樣子,用戶程序可以很方便使netconn 和編程函數,我們暫且將netconn 稱之爲連接結構體。
一個連接結構體中包含的成員變量很多,如描述連接的類型,連接的狀態(主要是在TCP 連接中使用),對應的控制塊(如UDP 控制塊、TCP 控制塊等等),還有對應線程的消息郵箱以及一些記錄的信息.
api.h
/** A netconn descriptor */
struct netconn {
/** 類型netconn (TCP, UDP or RAW) */
enum netconn_type type;
/** 當前狀態 the netconn */
enum netconn_state state;
/** the lwIP internal protocol control block */
union {
struct ip_pcb *ip;
struct tcp_pcb *tcp;
struct udp_pcb *udp;
struct raw_pcb *raw;
} pcb;
/** 這個netconn 最後一個異步未報告的錯誤 */
err_t pending_err;
#if !LWIP_NETCONN_SEM_PER_THREAD
/** 信號量 that is used to synchronously execute functions in the core context */
sys_sem_t op_completed;
#endif
/** 消息郵箱 where received packets are stored until they are fetched
by the netconn application thread (can grow quite big) */
sys_mbox_t recvmbox;
#if LWIP_TCP
/** mbox where new connections are stored until processed
by the application thread */
sys_mbox_t acceptmbox;
#endif /* LWIP_TCP */
#if LWIP_NETCONN_FULLDUPLEX
/** number of threads waiting on an mbox. This is required to unblock
all threads when closing while threads are waiting. */
int mbox_threads_waiting;
#endif
/** only used for socket layer */
#if LWIP_SOCKET
int socket;
#endif /* LWIP_SOCKET */
#if LWIP_SO_SNDTIMEO
/** timeout to wait for sending data (which means enqueueing data for sending
in internal buffers) in milliseconds */
s32_t send_timeout;
#endif /* LWIP_SO_RCVTIMEO */
#if LWIP_SO_RCVTIMEO
/** timeout in milliseconds to wait for new data to be received
(or connections to arrive for listening netconns) */
u32_t recv_timeout;
#endif /* LWIP_SO_RCVTIMEO */
#if LWIP_SO_RCVBUF
/** maximum amount of bytes queued in recvmbox
not used for TCP: adjust TCP_WND instead! */
int recv_bufsize;
/** number of bytes currently in recvmbox to be received,
tested against recv_bufsize to limit bytes on recvmbox
for UDP and RAW, used for FIONREAD */
int recv_avail;
#endif /* LWIP_SO_RCVBUF */
#if LWIP_SO_LINGER
/** values <0 mean linger is disabled, values > 0 are seconds to linger */
s16_t linger;
#endif /* LWIP_SO_LINGER */
/** flags holding more netconn-internal state, see NETCONN_FLAG_* defines */
u8_t flags;
#if LWIP_TCP
/** TCP: when data passed to netconn_write doesn't fit into the send buffer,
this temporarily stores the message.
Also used during connect and close. */
struct api_msg *current_msg;
#endif /* LWIP_TCP */
/** A callback function that is informed about events for this netconn */
netconn_callback callback;
};
enum netconn_type {
NETCONN_INVALID = 0,
/** TCP IPv4 */
NETCONN_TCP = 0x10,
#if LWIP_IPV6
/** TCP IPv6 */
NETCONN_TCP_IPV6 = NETCONN_TCP | NETCONN_TYPE_IPV6 /* 0x18 */,
#endif /* LWIP_IPV6 */
/** UDP IPv4 */
NETCONN_UDP = 0x20,
/** UDP IPv4 lite */
NETCONN_UDPLITE = 0x21,
/** UDP IPv4 no checksum */
NETCONN_UDPNOCHKSUM = 0x22,
#if LWIP_IPV6
/** UDP IPv6 (dual-stack by default, unless you call @ref netconn_set_ipv6only) */
NETCONN_UDP_IPV6 = NETCONN_UDP | NETCONN_TYPE_IPV6 /* 0x28 */,
/** UDP IPv6 lite (dual-stack by default, unless you call @ref netconn_set_ipv6only) */
NETCONN_UDPLITE_IPV6 = NETCONN_UDPLITE | NETCONN_TYPE_IPV6 /* 0x29 */,
/** UDP IPv6 no checksum (dual-stack by default, unless you call @ref netconn_set_ipv6only) */
NETCONN_UDPNOCHKSUM_IPV6 = NETCONN_UDPNOCHKSUM | NETCONN_TYPE_IPV6 /* 0x2a */,
#endif /* LWIP_IPV6 */
/** Raw connection IPv4 */
NETCONN_RAW = 0x40
#if LWIP_IPV6
/** Raw connection IPv6 (dual-stack by default, unless you call @ref netconn_set_ipv6only) */
, NETCONN_RAW_IPV6 = NETCONN_RAW | NETCONN_TYPE_IPV6 /* 0x48 */
#endif /* LWIP_IPV6 */
};
/** Current state of the netconn. Non-TCP netconns are always
* in state NETCONN_NONE! */
enum netconn_state {
NETCONN_NONE,
NETCONN_WRITE,
NETCONN_LISTEN,
NETCONN_CONNECT,
NETCONN_CLOSE
};
4. netconn 函數接口說明
下面這些函數都在api_lib.c 文件中實現,在api.h 頭文件中聲明.
4.1 netconn_new()
函數 netconn_new ()本質上是一個宏定義,它用來創建一個新的連接結構,連接結構的類型可以選擇爲 TCP 或 UDP 等,參數 type 描述了連接的類型,可以爲 NETCONN_TCP或NETCONN_UDP 等,在這個函數被調用時,會初始化相關的字段,而並不會創建連接.
#define netconn_new(t) netconn_new_with_proto_and_callback(t, 0, NULL)
/**
* Create a new netconn (of a specific type) that has a callback function.
* The corresponding pcb is also created.
*
* @param t the type of 'connection' to create (@see enum netconn_type)
* @param proto the IP protocol for RAW IP pcbs
* @param callback a function to call on status changes (RX available, TX'ed)
* @return a newly allocated struct netconn or
* NULL on memory error
*/
struct netconn *
netconn_new_with_proto_and_callback(enum netconn_type t, u8_t proto, netconn_callback callback)
{
struct netconn *conn;
API_MSG_VAR_DECLARE(msg);
API_MSG_VAR_ALLOC_RETURN_NULL(msg);
conn = netconn_alloc(t, callback);
if (conn != NULL) {
err_t err;
API_MSG_VAR_REF(msg).msg.n.proto = proto;
API_MSG_VAR_REF(msg).conn = conn;
err = netconn_apimsg(lwip_netconn_do_newconn, &API_MSG_VAR_REF(msg));
if (err != ERR_OK) {
LWIP_ASSERT("freeing conn without freeing pcb", conn->pcb.tcp == NULL);
LWIP_ASSERT("conn has no recvmbox", sys_mbox_valid(&conn->recvmbox));
#if LWIP_TCP
LWIP_ASSERT("conn->acceptmbox shouldn't exist", !sys_mbox_valid(&conn->acceptmbox));
#endif /* LWIP_TCP */
#if !LWIP_NETCONN_SEM_PER_THREAD
LWIP_ASSERT("conn has no op_completed", sys_sem_valid(&conn->op_completed));
sys_sem_free(&conn->op_completed);
#endif /* !LWIP_NETCONN_SEM_PER_THREAD */
sys_mbox_free(&conn->recvmbox);
memp_free(MEMP_NETCONN, conn);
API_MSG_VAR_FREE(msg);
return NULL;
}
}
API_MSG_VAR_FREE(msg);
return conn;
}
4.2 netconn_delete()
個函數的功能與netconn_new()函數剛好是相反的,它用於刪除一個netconn 連接結構,對於TCP 連接,如果此時是處於連接狀態的,在調用該函數後,將請求內核執行終止連接操作,此時應用線程是無需理會到底是怎麼運作的,因爲LwIP 內核將會完成所有的揮手過程,需要注意的是此時的TCP 控制塊還是不會立即被刪除的,因爲需要完成真正的斷開揮手操作,這些狀態可以參考TCP 協議狀態轉移圖。而對於UDP 協議,UDP 控制塊將被刪除,終止通信.
/**
* @ingroup netconn_common
* Close a netconn 'connection' and free its resources.
* UDP and RAW connection are completely closed, TCP pcbs might still be in a waitstate
* after this returns.
*
* @param conn the netconn to delete
* @return ERR_OK if the connection was deleted
*/
err_t
netconn_delete(struct netconn *conn)
{
err_t err;
/* No ASSERT here because possible to get a (conn == NULL) if we got an accept error */
if (conn == NULL) {
return ERR_OK;
}
#if LWIP_NETCONN_FULLDUPLEX
if (conn->flags & NETCONN_FLAG_MBOXINVALID) {
/* Already called netconn_prepare_delete() before */
err = ERR_OK;
} else
#endif /* LWIP_NETCONN_FULLDUPLEX */
{
err = netconn_prepare_delete(conn);
}
if (err == ERR_OK) {
netconn_free(conn);
}
return err;
}
真正處理的函數是netconn_prepare_delete(),它同樣是調用netconn_apimsg()函數先構造一個API 消息,然後投遞到系統郵箱,請求LwIP 內核線程去執行lwip_netconn_do_delconn()函數,這個函數會將對應的netconn 連接結構刪除,在執行完畢之後,通過信號量進行同步,應用線程得以繼續執行.
4.3 netconn_getaddr()
獲取一個netconn 連接結構的源IP 地址、端口號與目標IP 地址、端口號等信息,並且IP 地址保存在addr 中,端口號保存在port 中,而local 指定需要獲取的信息是本地IP 地址(源IP 地址)還是遠端IP 地址(目標IP 地址),如果是1 則表示獲取本地IP 地址與端口號,如果爲0 表示遠端IP 地址與端口號。同樣的,該函數會調用netconn_apimsg()函數構造一個API 消息,並且請求內核執行lwip_netconn_do_getaddr()函數,然後通過netconn 連接結構的信號量進行同步.
/**
* Get the local or remote IP address and port of a netconn.
* For RAW netconns, this returns the protocol instead of a port!
*
* @param conn the netconn to query
* @param addr a pointer to which to save the IP address
* @param port a pointer to which to save the port (or protocol for RAW)
* @param local 1 to get the local IP address, 0 to get the remote one
* @return ERR_CONN for invalid connections
* ERR_OK if the information was retrieved
*/
err_t
netconn_getaddr(struct netconn *conn, ip_addr_t *addr, u16_t *port, u8_t local)
{
API_MSG_VAR_DECLARE(msg);
err_t err;
LWIP_ERROR("netconn_getaddr: invalid conn", (conn != NULL), return ERR_ARG;);
LWIP_ERROR("netconn_getaddr: invalid addr", (addr != NULL), return ERR_ARG;);
LWIP_ERROR("netconn_getaddr: invalid port", (port != NULL), return ERR_ARG;);
API_MSG_VAR_ALLOC(msg);
API_MSG_VAR_REF(msg).conn = conn;
API_MSG_VAR_REF(msg).msg.ad.local = local;
#if LWIP_MPU_COMPATIBLE
err = netconn_apimsg(lwip_netconn_do_getaddr, &API_MSG_VAR_REF(msg));
*addr = msg->msg.ad.ipaddr;
*port = msg->msg.ad.port;
#else /* LWIP_MPU_COMPATIBLE */
msg.msg.ad.ipaddr = addr;
msg.msg.ad.port = port;
err = netconn_apimsg(lwip_netconn_do_getaddr, &msg);
#endif /* LWIP_MPU_COMPATIBLE */
API_MSG_VAR_FREE(msg);
return err;
}
4.4 netconn_bind()
將一個 IP 地址及端口號與netconn 連接結構進行綁定,如果作爲服務器端,這一步操作是必然需要的,同樣的,該函數會調用netconn_apimsg()函數構造一個API 消息,並且請求內核執行lwip_netconn_do_bind()函數,然後通過netconn 連接結構的信號量進行同步,事實上內核線程的處理也是通過函數調用xxx_bind(xxx_bind 可以是udp_bind、tcp_bind、raw_bind,具體是哪個函數內核是根據netconn 的類型決定的)完成相應控制塊的綁定工作.
/**
* @ingroup netconn_common
* Bind a netconn to a specific local IP address and port.
* Binding one netconn twice might not always be checked correctly!
*
* @param conn the netconn to bind
* @param addr the local IP address to bind the netconn to
* (use IP4_ADDR_ANY/IP6_ADDR_ANY to bind to all addresses)
* @param port the local port to bind the netconn to (not used for RAW)
* @return ERR_OK if bound, any other err_t on failure
*/
err_t
netconn_bind(struct netconn *conn, const ip_addr_t *addr, u16_t port)
{
API_MSG_VAR_DECLARE(msg);
err_t err;
LWIP_ERROR("netconn_bind: invalid conn", (conn != NULL), return ERR_ARG;);
#if LWIP_IPV4
/* Don't propagate NULL pointer (IP_ADDR_ANY alias) to subsequent functions */
if (addr == NULL) {
addr = IP4_ADDR_ANY;
}
#endif /* LWIP_IPV4 */
#if LWIP_IPV4 && LWIP_IPV6
/* "Socket API like" dual-stack support: If IP to bind to is IP6_ADDR_ANY,
* and NETCONN_FLAG_IPV6_V6ONLY is 0, use IP_ANY_TYPE to bind
*/
if ((netconn_get_ipv6only(conn) == 0) &&
ip_addr_cmp(addr, IP6_ADDR_ANY)) {
addr = IP_ANY_TYPE;
}
#endif /* LWIP_IPV4 && LWIP_IPV6 */
API_MSG_VAR_ALLOC(msg);
API_MSG_VAR_REF(msg).conn = conn;
API_MSG_VAR_REF(msg).msg.bc.ipaddr = API_MSG_VAR_REF(addr);
API_MSG_VAR_REF(msg).msg.bc.port = port;
err = netconn_apimsg(lwip_netconn_do_bind, &API_MSG_VAR_REF(msg));
API_MSG_VAR_FREE(msg);
return err;
}
4.5 netconn_connect()
netconn_connect()函數是一個主動建立連接的函數,它一般在客戶端中調用,將服務器端的 IP 地址和端口號與本地的netconn 連接結構綁定,當TCP 協議使用該函數的時候就是進行握手的過程,調用的應用線程將阻塞至握手完成;而對於UDP 協議來說,調用該函數只是設置UDP 控制塊的目標IP 地址與目標端口號,其實這個函數也是通過調用netconn_apimsg()函數構造一個API 消息,並且請求內核執行lwip_netconn_do_connect()函數,然後通過netconn 連接結構的信號量進行同步,lwip_netconn_do_connect()函數中,根據netconn 的類型不同,調用對應的xxx_connect()函數進行對應的處理,如果是TCP 連接,將調用tcp_connect();如果是UDP 協議,將調用udp_connect();如果是RAW,將調用raw_connect()函數處理.
/**
* @ingroup netconn_common
* Connect a netconn to a specific remote IP address and port.
*
* @param conn the netconn to connect
* @param addr the remote IP address to connect to
* @param port the remote port to connect to (no used for RAW)
* @return ERR_OK if connected, return value of tcp_/udp_/raw_connect otherwise
*/
err_t
netconn_connect(struct netconn *conn, const ip_addr_t *addr, u16_t port)
{
API_MSG_VAR_DECLARE(msg);
err_t err;
LWIP_ERROR("netconn_connect: invalid conn", (conn != NULL), return ERR_ARG;);
#if LWIP_IPV4
/* Don't propagate NULL pointer (IP_ADDR_ANY alias) to subsequent functions */
if (addr == NULL) {
addr = IP4_ADDR_ANY;
}
#endif /* LWIP_IPV4 */
API_MSG_VAR_ALLOC(msg);
API_MSG_VAR_REF(msg).conn = conn;
API_MSG_VAR_REF(msg).msg.bc.ipaddr = API_MSG_VAR_REF(addr);
API_MSG_VAR_REF(msg).msg.bc.port = port;
err = netconn_apimsg(lwip_netconn_do_connect, &API_MSG_VAR_REF(msg));
API_MSG_VAR_FREE(msg);
return err;
}
4.6 netconn_disconnect()
該函數是用於終止一個UDP 協議的通信,注意,是UDP 協議,而不是TCP 協議,因爲這個函數只能用於UDP 協議,簡單來說就是將UDP 控制塊的目標IP 地址與目標端口號清除,不過麻雀雖小,但五臟俱全,同樣的該函數也是構造API 消息請求內核執行lwip_netconn_do_disconnect()函數
/**
* @ingroup netconn_udp
* Disconnect a netconn from its current peer (only valid for UDP netconns).
*
* @param conn the netconn to disconnect
* @return See @ref err_t
*/
err_t
netconn_disconnect(struct netconn *conn)
{
API_MSG_VAR_DECLARE(msg);
err_t err;
LWIP_ERROR("netconn_disconnect: invalid conn", (conn != NULL), return ERR_ARG;);
API_MSG_VAR_ALLOC(msg);
API_MSG_VAR_REF(msg).conn = conn;
err = netconn_apimsg(lwip_netconn_do_disconnect, &API_MSG_VAR_REF(msg));
API_MSG_VAR_FREE(msg);
return err;
}
4.7 netconn_listen()
netconn_listen()函數的本質是一個帶參宏,其真正調用的函數是netconn_listen_with_backlog(),只適用於TCP 服務器中調用,它的作用是讓netconn 連接結構處於監聽狀態,同時讓TCP 控制塊的狀態處於LISTEN 狀態,以便客戶端連接,同樣的,它通過netconn_apimsg()函數請求內核執行lwip_netconn_do_listen(),這個函數纔是真正處理TCP 連接的監聽狀態,並且在這個函數中會創建一個連接郵箱——acceptmbox 郵箱在netconn 連接結構中,然後在TCP 控制塊中註冊連接回調函數——accept_function(),當有客戶端連接的時候,這個回調函數被執行,並且向acceptmbox 郵箱發送一個消息,通知應用程序有一個新的客戶端連接,以便用戶去處理這個連接。當然,在lwip_netconn_do_listen()函數處理完成的時候會釋放一個信號量,以進行線程間的同步.
/** @ingroup netconn_tcp */
#define netconn_listen(conn) netconn_listen_with_backlog(conn, TCP_DEFAULT_LISTEN_BACKLOG)
/**
* @ingroup netconn_tcp
* Set a TCP netconn into listen mode
*
* @param conn the tcp netconn to set to listen mode
* @param backlog the listen backlog, only used if TCP_LISTEN_BACKLOG==1
* @return ERR_OK if the netconn was set to listen (UDP and RAW netconns
* don't return any error (yet?))
*/
err_t
netconn_listen_with_backlog(struct netconn *conn, u8_t backlog)
{
#if LWIP_TCP
API_MSG_VAR_DECLARE(msg);
err_t err;
/* This does no harm. If TCP_LISTEN_BACKLOG is off, backlog is unused. */
LWIP_UNUSED_ARG(backlog);
LWIP_ERROR("netconn_listen: invalid conn", (conn != NULL), return ERR_ARG;);
API_MSG_VAR_ALLOC(msg);
API_MSG_VAR_REF(msg).conn = conn;
#if TCP_LISTEN_BACKLOG
API_MSG_VAR_REF(msg).msg.lb.backlog = backlog;
#endif /* TCP_LISTEN_BACKLOG */
err = netconn_apimsg(lwip_netconn_do_listen, &API_MSG_VAR_REF(msg));
API_MSG_VAR_FREE(msg);
return err;
#else /* LWIP_TCP */
LWIP_UNUSED_ARG(conn);
LWIP_UNUSED_ARG(backlog);
return ERR_ARG;
#endif /* LWIP_TCP */
}
4.8 netconn_accept()
函數用於TCP 服務器中,接受遠端主機的連接,內核會在acceptmbox 郵箱中獲取一個連接請求,如果郵箱中沒有連接請求,將阻塞應用程序,直到接收到從遠端主機發出的連接請求。調用這個函數的應用程序必須處於監聽(LISTEN)狀態,因此在調用netconn_accept()函數之前必須調用netconn_listen()函數進入監聽狀態,在與遠程主機的連接建立後,函數返回一個連接結構netconn;該函數在並不會構造一個API 消息,而是直接獲取acceptmbox 郵箱中的連接請求,如果沒有連接請求,將一直阻塞,當接收到遠端主機的連接請求後,它會觸發一個連接事件的回調函數(netconn 結構體中的回調函數字段),連接的信息由accept_function()函數完成。可能沒發現這個回調函數啊,其實在LwIP 在將TCP 服務器進入監聽狀態的時候就已經註冊了這個回調函數,在有連接的時候,就直接進行連接。在lwip_netconn_do_listen() 函數中調用 tcp_accept()函數進行註冊連接時候的回調函數.
/**
* @ingroup netconn_tcp
* Accept a new connection on a TCP listening netconn.
*
* @param conn the TCP listen netconn
* @param new_conn pointer where the new connection is stored
* @return ERR_OK if a new connection has been received or an error
* code otherwise
*/
err_t
netconn_accept(struct netconn *conn, struct netconn **new_conn)
{
#if LWIP_TCP
err_t err;
void *accept_ptr;
struct netconn *newconn;
#if TCP_LISTEN_BACKLOG
API_MSG_VAR_DECLARE(msg);
#endif /* TCP_LISTEN_BACKLOG */
LWIP_ERROR("netconn_accept: invalid pointer", (new_conn != NULL), return ERR_ARG;);
*new_conn = NULL;
LWIP_ERROR("netconn_accept: invalid conn", (conn != NULL), return ERR_ARG;);
/* NOTE: Although the opengroup spec says a pending error shall be returned to
send/recv/getsockopt(SO_ERROR) only, we return it for listening
connections also, to handle embedded-system errors */
err = netconn_err(conn);
if (err != ERR_OK) {
/* return pending error */
return err;
}
if (!NETCONN_ACCEPTMBOX_WAITABLE(conn)) {
/* don't accept if closed: this might block the application task
waiting on acceptmbox forever! */
return ERR_CLSD;
}
API_MSG_VAR_ALLOC_ACCEPT(msg);
NETCONN_MBOX_WAITING_INC(conn);
if (netconn_is_nonblocking(conn)) {
if (sys_arch_mbox_tryfetch(&conn->acceptmbox, &accept_ptr) == SYS_ARCH_TIMEOUT) {
API_MSG_VAR_FREE_ACCEPT(msg);
NETCONN_MBOX_WAITING_DEC(conn);
return ERR_WOULDBLOCK;
}
} else {
#if LWIP_SO_RCVTIMEO
if (sys_arch_mbox_fetch(&conn->acceptmbox, &accept_ptr, conn->recv_timeout) == SYS_ARCH_TIMEOUT) {
API_MSG_VAR_FREE_ACCEPT(msg);
NETCONN_MBOX_WAITING_DEC(conn);
return ERR_TIMEOUT;
}
#else
sys_arch_mbox_fetch(&conn->acceptmbox, &accept_ptr, 0);
#endif /* LWIP_SO_RCVTIMEO*/
}
NETCONN_MBOX_WAITING_DEC(conn);
#if LWIP_NETCONN_FULLDUPLEX
if (conn->flags & NETCONN_FLAG_MBOXINVALID) {
if (lwip_netconn_is_deallocated_msg(accept_ptr)) {
/* the netconn has been closed from another thread */
API_MSG_VAR_FREE_ACCEPT(msg);
return ERR_CONN;
}
}
#endif
/* Register event with callback */
API_EVENT(conn, NETCONN_EVT_RCVMINUS, 0);
if (lwip_netconn_is_err_msg(accept_ptr, &err)) {
/* a connection has been aborted: e.g. out of pcbs or out of netconns during accept */
API_MSG_VAR_FREE_ACCEPT(msg);
return err;
}
if (accept_ptr == NULL) {
/* connection has been aborted */
API_MSG_VAR_FREE_ACCEPT(msg);
return ERR_CLSD;
}
newconn = (struct netconn *)accept_ptr;
#if TCP_LISTEN_BACKLOG
/* Let the stack know that we have accepted the connection. */
API_MSG_VAR_REF(msg).conn = newconn;
/* don't care for the return value of lwip_netconn_do_recv */
netconn_apimsg(lwip_netconn_do_accepted, &API_MSG_VAR_REF(msg));
API_MSG_VAR_FREE(msg);
#endif /* TCP_LISTEN_BACKLOG */
*new_conn = newconn;
/* don't set conn->last_err: it's only ERR_OK, anyway */
return ERR_OK;
#else /* LWIP_TCP */
LWIP_UNUSED_ARG(conn);
LWIP_UNUSED_ARG(new_conn);
return ERR_ARG;
#endif /* LWIP_TCP */
}
4.9 netconn_recv()
這個函數可能是我們在寫代碼中遇到最多的函數了,它可以接收一個UDP 或者TCP的數據包,從recvmbox 郵箱中獲取數據包,如果該郵箱中沒有數據包,那麼線程調用這個函數將會進入阻塞狀態以等待消息的到來,如果在等待TCP 連接上的數據時,遠端主機終止連接,將返回一個終止連接的錯誤代碼(ERR_CLSD),應用程序可以根據錯誤的類型進行不一樣的處理。
對應TCP 連接,netconn_recv()函數將調用netconn_recv_data_tcp()函數去獲取TCP 連接上的數據,在獲取數據的過程中,調用netconn_recv_data()函數從recvmbox 郵箱獲取pbuf,然後通過netconn_tcp_recvd_msg()->netconn_apimsg()函數構造一個API 消息投遞給系統郵箱,請求內核執行lwip_netconn_do_recv()函數,該函數將調用tcp_recved()函數去更新TCP 接收窗口,同時netconn_recv()函數將完成pbuf 數據包封裝在netbuf 中,返回個應用程序;而對於UDP 協議、RAW連接,將簡單多了,將直接調用netconn_recv_data()函數獲取數據,完成pbuf 封裝在netbuf 中,返回給應用程序.
/**
* @ingroup netconn_common
* Receive data (in form of a netbuf containing a packet buffer) from a netconn
*
* @param conn the netconn from which to receive data
* @param new_buf pointer where a new netbuf is stored when received data
* @return ERR_OK if data has been received, an error code otherwise (timeout,
* memory error or another error)
*/
err_t
netconn_recv(struct netconn *conn, struct netbuf **new_buf)
{
#if LWIP_TCP
struct netbuf *buf = NULL;
err_t err;
#endif /* LWIP_TCP */
LWIP_ERROR("netconn_recv: invalid pointer", (new_buf != NULL), return ERR_ARG;);
*new_buf = NULL;
LWIP_ERROR("netconn_recv: invalid conn", (conn != NULL), return ERR_ARG;);
#if LWIP_TCP
#if (LWIP_UDP || LWIP_RAW)
if (NETCONNTYPE_GROUP(conn->type) == NETCONN_TCP)
#endif /* (LWIP_UDP || LWIP_RAW) */
{
struct pbuf *p = NULL;
/* This is not a listening netconn, since recvmbox is set */
buf = (struct netbuf *)memp_malloc(MEMP_NETBUF);
if (buf == NULL) {
return ERR_MEM;
}
err = netconn_recv_data_tcp(conn, &p, 0);
if (err != ERR_OK) {
memp_free(MEMP_NETBUF, buf);
return err;
}
LWIP_ASSERT("p != NULL", p != NULL);
buf->p = p;
buf->ptr = p;
buf->port = 0;
ip_addr_set_zero(&buf->addr);
*new_buf = buf;
/* don't set conn->last_err: it's only ERR_OK, anyway */
return ERR_OK;
}
#endif /* LWIP_TCP */
#if LWIP_TCP && (LWIP_UDP || LWIP_RAW)
else
#endif /* LWIP_TCP && (LWIP_UDP || LWIP_RAW) */
{
#if (LWIP_UDP || LWIP_RAW)
return netconn_recv_data(conn, (void **)new_buf, 0);
#endif /* (LWIP_UDP || LWIP_RAW) */
}
}
接收數據運作的示意圖
4.10 netconn_send()
整個數據發送函數我們在實際中使用的也是非常多的,它用於UDP 協議、RAW連接發送數據,通過參數conn 選擇指定的UDP 或者RAW控制塊發送參數buf 中的數據,UDP/RAW 控制塊中已經記錄了目標IP 地址與目標端口號了。這些數據被封裝在netbuf 中,如果沒有使用IP 數據報分片功能,那麼這些數據不能太大,數據長度不能大於網卡最大傳輸單元MTU,因爲這個API 目前還沒有提供直接獲取底層網卡最大傳輸單元MTU 數值的函數,這就需要採用其它的途徑來避免超過MTU 值,所以規定了一個上限,即netbuf 中包含的數據不能大於1000 個字節,這就需要我們自己在發送數據的時候要注意,當然,使用了IP 數據報分片功能的話,就不用管這些限制了。該函數會調用netconn_apimsg()函數構造一個API 消息,並且請求內核執行lwip_netconn_do_send()函數,這個函數會通過消息得到目標IP 地址與端口號以及pbuf 數據報等信息,然後調用raw_send()/udp_send()等函數發送數據,最後通過netconn 連接結構的信號量進行同步
/**
* @ingroup netconn_udp
* Send data over a UDP or RAW netconn (that is already connected).
*
* @param conn the UDP or RAW netconn over which to send data
* @param buf a netbuf containing the data to send
* @return ERR_OK if data was sent, any other err_t on error
*/
err_t
netconn_send(struct netconn *conn, struct netbuf *buf)
{
API_MSG_VAR_DECLARE(msg);
err_t err;
LWIP_ERROR("netconn_send: invalid conn", (conn != NULL), return ERR_ARG;);
LWIP_DEBUGF(API_LIB_DEBUG, ("netconn_send: sending %"U16_F" bytes\n", buf->p->tot_len));
API_MSG_VAR_ALLOC(msg);
API_MSG_VAR_REF(msg).conn = conn;
API_MSG_VAR_REF(msg).msg.b = buf;
err = netconn_apimsg(lwip_netconn_do_send, &API_MSG_VAR_REF(msg));
API_MSG_VAR_FREE(msg);
return err;
}
4.11 netconn_sendto()
函數與netconn_send()函數是一樣的功能,只不過參數中直接指出目標IP 地址與目標端口號,並且填寫在pbuf 中.
/**
* @ingroup netconn_udp
* Send data (in form of a netbuf) to a specific remote IP address and port.
* Only to be used for UDP and RAW netconns (not TCP).
*
* @param conn the netconn over which to send data
* @param buf a netbuf containing the data to send
* @param addr the remote IP address to which to send the data
* @param port the remote port to which to send the data
* @return ERR_OK if data was sent, any other err_t on error
*/
err_t
netconn_sendto(struct netconn *conn, struct netbuf *buf, const ip_addr_t *addr, u16_t port)
{
if (buf != NULL) {
ip_addr_set(&buf->addr, addr);
buf->port = port;
return netconn_send(conn, buf);
}
return ERR_VAL;
}
4.12 netconn_write()
etconn_write()函數的本質是一個宏,用於處於穩定連接狀態的TCP 協議發送數據,我們也知道,TCP 協議的數據是以流的方式傳輸的,只需要指出發送數據的起始地址與長度即可,LwIP 內核會幫我們直接處理這些數據,將這些數據按字節流進行編號,讓它們按照TCP 協議的方式進行傳輸,這樣子就無需我們理會怎麼傳輸了,對於數據的長度也沒限制,內核會直接處理,使得它們變成最適的方式發送出去。
/** @ingroup netconn_tcp */
#define netconn_write(conn, dataptr, size, apiflags) \
netconn_write_partly(conn, dataptr, size, apiflags, NULL)
/**
* @ingroup netconn_tcp
* Send data over a TCP netconn.
*
* @param conn the TCP netconn over which to send data
* @param dataptr pointer to the application buffer that contains the data to send
* @param size size of the application data to send
* @param apiflags combination of following flags :
* - NETCONN_COPY: data will be copied into memory belonging to the stack
* - NETCONN_MORE: for TCP connection, PSH flag will be set on last segment sent
* - NETCONN_DONTBLOCK: only write the data if all data can be written at once
* @param bytes_written pointer to a location that receives the number of written bytes
* @return ERR_OK if data was sent, any other err_t on error
*/
err_t
netconn_write_partly(struct netconn *conn, const void *dataptr, size_t size,
u8_t apiflags, size_t *bytes_written)
{
struct netvector vector;
vector.ptr = dataptr;
vector.len = size;
return netconn_write_vectors_partly(conn, &vector, 1, apiflags, bytes_written);
}
/**
* Send vectorized data atomically over a TCP netconn.
*
* @param conn the TCP netconn over which to send data
* @param vectors array of vectors containing data to send
* @param vectorcnt number of vectors in the array
* @param apiflags combination of following flags :
* - NETCONN_COPY: data will be copied into memory belonging to the stack
* - NETCONN_MORE: for TCP connection, PSH flag will be set on last segment sent
* - NETCONN_DONTBLOCK: only write the data if all data can be written at once
* @param bytes_written pointer to a location that receives the number of written bytes
* @return ERR_OK if data was sent, any other err_t on error
*/
err_t
netconn_write_vectors_partly(struct netconn *conn, struct netvector *vectors, u16_t vectorcnt,
u8_t apiflags, size_t *bytes_written)
{
API_MSG_VAR_DECLARE(msg);
err_t err;
u8_t dontblock;
size_t size;
int i;
LWIP_ERROR("netconn_write: invalid conn", (conn != NULL), return ERR_ARG;);
LWIP_ERROR("netconn_write: invalid conn->type", (NETCONNTYPE_GROUP(conn->type) == NETCONN_TCP), return ERR_VAL;);
dontblock = netconn_is_nonblocking(conn) || (apiflags & NETCONN_DONTBLOCK);
#if LWIP_SO_SNDTIMEO
if (conn->send_timeout != 0) {
dontblock = 1;
}
#endif /* LWIP_SO_SNDTIMEO */
if (dontblock && !bytes_written) {
/* This implies netconn_write() cannot be used for non-blocking send, since
it has no way to return the number of bytes written. */
return ERR_VAL;
}
/* sum up the total size */
size = 0;
for (i = 0; i < vectorcnt; i++) {
size += vectors[i].len;
if (size < vectors[i].len) {
/* overflow */
return ERR_VAL;
}
}
if (size == 0) {
return ERR_OK;
} else if (size > SSIZE_MAX) {
ssize_t limited;
/* this is required by the socket layer (cannot send full size_t range) */
if (!bytes_written) {
return ERR_VAL;
}
/* limit the amount of data to send */
limited = SSIZE_MAX;
size = (size_t)limited;
}
API_MSG_VAR_ALLOC(msg);
/* non-blocking write sends as much */
API_MSG_VAR_REF(msg).conn = conn;
API_MSG_VAR_REF(msg).msg.w.vector = vectors;
API_MSG_VAR_REF(msg).msg.w.vector_cnt = vectorcnt;
API_MSG_VAR_REF(msg).msg.w.vector_off = 0;
API_MSG_VAR_REF(msg).msg.w.apiflags = apiflags;
API_MSG_VAR_REF(msg).msg.w.len = size;
API_MSG_VAR_REF(msg).msg.w.offset = 0;
#if LWIP_SO_SNDTIMEO
if (conn->send_timeout != 0) {
/* get the time we started, which is later compared to
sys_now() + conn->send_timeout */
API_MSG_VAR_REF(msg).msg.w.time_started = sys_now();
} else {
API_MSG_VAR_REF(msg).msg.w.time_started = 0;
}
#endif /* LWIP_SO_SNDTIMEO */
/* For locking the core: this _can_ be delayed on low memory/low send buffer,
but if it is, this is done inside api_msg.c:do_write(), so we can use the
non-blocking version here. */
err = netconn_apimsg(lwip_netconn_do_write, &API_MSG_VAR_REF(msg));
if (err == ERR_OK) {
if (bytes_written != NULL) {
*bytes_written = API_MSG_VAR_REF(msg).msg.w.offset;
}
/* for blocking, check all requested bytes were written, NOTE: send_timeout is
treated as dontblock (see dontblock assignment above) */
if (!dontblock) {
LWIP_ASSERT("do_write failed to write all bytes", API_MSG_VAR_REF(msg).msg.w.offset == size);
}
}
API_MSG_VAR_FREE(msg);
return err;
}
apiflags 參數
/* Flags for netconn_write (u8_t) */
// 沒有標誌位(默認標誌位)
#define NETCONN_NOFLAG 0x00
// 不拷貝數據到內核線程
#define NETCONN_NOCOPY 0x00 /* Only for source code compatibility */
// 拷貝數據到內核線程
#define NETCONN_COPY 0x01
// 儘快遞交給上層應用
#define NETCONN_MORE 0x02
// 當內核緩衝區滿時,不會被阻塞,而是直接返回
#define NETCONN_DONTBLOCK 0x04
// 不自動更新接收窗口,需要調用netconn_tcp_recvd()函數完成
#define NETCONN_NOAUTORCVD 0x08 /* prevent netconn_recv_data_tcp() from updating the tcp window - must be done manually via netconn_tcp_recvd() */
// 上層已經收到數據,將FIN 保留在隊列中直到再次調用
#define NETCONN_NOFIN 0x10 /* upper layer already received data, leave FIN in queue until called again */
當apiflags 的值爲NETCONN_COPY 時, dataptr 指針指向的數據將會被拷貝到爲這些數據分配的內部緩衝區,這樣的話,在調用本函數之後可以直接對這些數據進行修改而不會影響數據,但是拷貝的過程是需要消耗系統資源的,CPU 需要參與數據的拷貝,而且還會佔用新的內存空間。
如果apiflags 值爲NETCONN_NOCOPY,數據不會被拷貝而是直接使用dataptr 指針來引用。但是這些數據在函數調用後不能立即被修改,因爲這些數據可能會被放在當前TCP連接的重傳隊列中,以防對方未收到數據進行重傳,而這段時間是不確定的。但是如果用戶需要發送的數據在ROM 中(靜態數據),這樣子就無需拷貝數據,直接引用數據即可。
如果apiflags 值爲NETCONN_MORE,那麼接收端在組裝這些TCP 報文段的時候,會將報文段首部的PSH 標誌置一,這樣子,這些數據完成組裝的時候,將會被立即遞交給上層應用。
如果apiflags 值爲NETCONN_DONTBLOCK,表示在內核發送緩衝區滿的時候,再調用netconn_write()函數將不會被阻塞,而是會直接返回一個錯誤代碼ERR_VAL 告訴應用程序發送數據失敗,應用程序可以自行處理這些數據,在適當的時候進行重傳操作。
如果apiflags 值爲NETCONN_NOAUTORCVD,表示在TCP 協議接收到數據的時候,調用netconn_recv_data_tcp()函數的時候不會去更新接收窗口,只能由用戶自己調用netconn_tcp_recvd()函數完成接收窗口的更新操作
5. 示例例程
5.1 TCP Client
#include "client.h"
#include "lwip/opt.h"
#include "lwip/sys.h"
#include "lwip/api.h"
static void client(void *thread_param)
{
struct netconn *conn;
int ret;
ip4_addr_t ipaddr;
uint8_t send_buf[]= "This is a TCP Client test...\n";
while(1)
{
conn = netconn_new(NETCONN_TCP);
if (conn == NULL)
{
printf("create conn failed!\n");
vTaskDelay(10);
continue;
}
IP4_ADDR(&ipaddr,192,168,0,181);
ret = netconn_connect(conn,&ipaddr,5001);
if (ret == -1)
{
printf("Connect failed!\n");
netconn_close(conn);
vTaskDelay(10);
continue;
}
printf("Connect to iperf server successful!\n");
while (1)
{
ret = netconn_write(conn,send_buf,sizeof(send_buf),0);
vTaskDelay(1000);
}
}
}
void
client_init(void)
{
sys_thread_new("client", client, NULL, 512, 4);
}
5.2 TCP Server
#include "tcpecho.h"
#include "lwip/opt.h"
#if LWIP_NETCONN
#include "lwip/sys.h"
#include "lwip/api.h"
/*-----------------------------------------------------------------------------------*/
static void
tcpecho_thread(void *arg)
{
struct netconn *conn, *newconn;
err_t err;
LWIP_UNUSED_ARG(arg);
/* Create a new connection identifier. */
/* Bind connection to well known port number 7. */
#if LWIP_IPV6
conn = netconn_new(NETCONN_TCP_IPV6);
netconn_bind(conn, IP6_ADDR_ANY, 5001);
#else /* LWIP_IPV6 */
conn = netconn_new(NETCONN_TCP);
netconn_bind(conn, IP_ADDR_ANY, 5001);
#endif /* LWIP_IPV6 */
LWIP_ERROR("tcpecho: invalid conn", (conn != NULL), return;);
/* Tell connection to go into listening mode. */
netconn_listen(conn);
while (1) {
/* Grab new connection. */
err = netconn_accept(conn, &newconn);
/*printf("accepted new connection %p\n", newconn);*/
/* Process the new connection. */
if (err == ERR_OK) {
struct netbuf *buf;
void *data;
u16_t len;
while ((err = netconn_recv(newconn, &buf)) == ERR_OK) {
/*printf("Recved\n");*/
// 接收newconn 客戶端發來的數據
do {
netbuf_data(buf, &data, &len);
err = netconn_write(newconn, data, len, NETCONN_COPY);
#if 0
if (err != ERR_OK) {
printf("tcpecho: netconn_write: error \"%s\"\n", lwip_strerr(err));
}
#endif
/* 可能客戶端發送的數據很多,可能netbuf 中還有數據,那就調用netbuf_next()函數移動ptr 指針,指向下一個pbuf。*/
} while (netbuf_next(buf) >= 0);
netbuf_delete(buf); // 釋放這些數據區域空間
}
/*printf("Got EOF, looping\n");*/
/* Close connection and discard connection identifier. */
netconn_close(newconn); // 主動關閉與客戶端的連接
netconn_delete(newconn); // 釋放newconn 的空間
}
}
}
/*-----------------------------------------------------------------------------------*/
void
tcpecho_init(void)
{
sys_thread_new("tcpecho_thread", tcpecho_thread, NULL, 512, 4);
}
/*-----------------------------------------------------------------------------------*/
#endif /* LWIP_NETCONN */
5.3 UDP
#include "udpecho.h"
#include "lwip/opt.h"
#if LWIP_NETCONN
#include "lwip/api.h"
#include "lwip/sys.h"
/*-----------------------------------------------------------------------------------*/
static void
udpecho_thread(void *arg)
{
struct netconn *conn;
struct netbuf *buf;
char buffer[4096];
err_t err;
LWIP_UNUSED_ARG(arg);
#if LWIP_IPV6
conn = netconn_new(NETCONN_UDP_IPV6);
netconn_bind(conn, IP6_ADDR_ANY, 5001);
#else /* LWIP_IPV6 */
conn = netconn_new(NETCONN_UDP);
netconn_bind(conn, IP_ADDR_ANY, 5001);
#endif /* LWIP_IPV6 */
LWIP_ERROR("udpecho: invalid conn", (conn != NULL), return;);
while (1) {
err = netconn_recv(conn, &buf);
if (err == ERR_OK) {
/* no need netconn_connect here, since the netbuf contains the address */
if(netbuf_copy(buf, buffer, sizeof(buffer)) != buf->p->tot_len) {
LWIP_DEBUGF(LWIP_DBG_ON, ("netbuf_copy failed\n"));
} else {
buffer[buf->p->tot_len] = '\0';
err = netconn_send(conn, buf);
if(err != ERR_OK) {
LWIP_DEBUGF(LWIP_DBG_ON, ("netconn_send failed: %d\n", (int)err));
} else {
LWIP_DEBUGF(LWIP_DBG_ON, ("got %s\n", buffer));
}
}
netbuf_delete(buf);
}
}
}
/*-----------------------------------------------------------------------------------*/
void
udpecho_init(void)
{
sys_thread_new("udpecho_thread", udpecho_thread, NULL, 2048, 4);
}
#endif /* LWIP_NETCONN */