lwIP TCP/IP 協議棧筆記之十六: NETCONN 接口編程

目錄

1. netbuf 結構體

2. netbuf 相關函數說明

2.1 netbuf_new()

2.2 netbuf_delete()

2.3 netbuf_alloc()

2.4 netbuf_free()

2.5 netbuf_ref()

2.6 netbuf_chain()

2.7 netbuf_data()

2.8 netbuf_next()與netbuf_first()

2.9 netbuf_copy()

2.10 netbuf_take()

3. netconn 結構體

4. netconn 函數接口說明

4.1 netconn_new()

4.2 netconn_delete()

4.3 netconn_getaddr()

4.4 netconn_bind()

4.5 netconn_connect()

4.6 netconn_disconnect()

4.7 netconn_listen()

4.8 netconn_accept()

4.9 netconn_recv()

4.10 netconn_send()

4.11 netconn_sendto()

4.12 netconn_write()

5. 示例例程

5.1 TCP Client

5.2 TCP Server

5.3 UDP


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 */

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章