lwip socket探秘之socket創建


一個基本的socket建立順序是
Server端:
  • socket()
  • bind()
  • listen()
  • accept()
  • recv()
Client端:
  • socket()
  • connect()
  • send()
 
本文着重介紹Server端的socket()過程。
 
用戶使用socket時,首先會調用socket()函數創建一個socket。在lwip中實際調用的就是lwip_socket()函數。
代碼如下:
 1 int
 2 lwip_socket(int domain, int type, int protocol)  3 {  4   struct netconn *conn;  5   int i;  6 
 7  LWIP_UNUSED_ARG(domain);  8 
 9   /* create a netconn */
10   switch (type) {  // 根據用戶傳入的type區分TCP、UDP和RAW
11   case SOCK_RAW: 12     conn = netconn_new_with_proto_and_callback(NETCONN_RAW, (u8_t)protocol, event_callback); 13     LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%s, SOCK_RAW, %d) = ", 14                                  domain == PF_INET ? "PF_INET" : "UNKNOWN", protocol)); 15     break; 16   case SOCK_DGRAM: 17     conn = netconn_new_with_callback( (protocol == IPPROTO_UDPLITE) ?
18  NETCONN_UDPLITE : NETCONN_UDP, event_callback); 19     LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%s, SOCK_DGRAM, %d) = ", 20                                  domain == PF_INET ? "PF_INET" : "UNKNOWN", protocol)); 21     break; 22   case SOCK_STREAM: 23     conn = netconn_new_with_callback(NETCONN_TCP, event_callback); // 例如TCP在這個case里。這里新建一個netconn結構體。netconn是用戶可見的socket和協議棧內部的protocol control block之間的橋梁,這里下文會分析
24     LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%s, SOCK_STREAM, %d) = ", 25                                  domain == PF_INET ? "PF_INET" : "UNKNOWN", protocol)); 26     break; 27   default: 28     LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%d, %d/UNKNOWN, %d) = -1\n", 29  domain, type, protocol)); 30  set_errno(EINVAL); 31     return -1; 32  } 33 
34   if (!conn) { 35     LWIP_DEBUGF(SOCKETS_DEBUG, ("-1 / ENOBUFS (could not create netconn)\n")); 36  set_errno(ENOBUFS); 37     return -1; 38  } 39 
40   i = alloc_socket(conn); // 開辟一個socket,這個函數也很重要
41 
42   if (i == -1) { 43  netconn_delete(conn); 44  set_errno(ENFILE); 45     return -1; 46  } 47   conn->socket = i; 48   LWIP_DEBUGF(SOCKETS_DEBUG, ("%d\n", i)); 49   set_errno(0); 50   return i; 51 }

 

接下來我們分兩個部分,netconn_new_with_callback所創建的netconn結構體,以及alloc_socket所創建的socket。
 

1.創建netconn結構體

netconn_new_with_callback函數里只是一個簡單的調用。
netconn_new_with_callback
     =>netconn_new_with_proto_and_callback
 
看一下netconn_new_with_proto_and_callback()這個函數:
 1 /**  2 * Create a new netconn (of a specific type) that has a callback function.  3 * The corresponding pcb is also created.  4 *  5 * @param t the type of 'connection' to create (@see enum netconn_type)  6 * @param proto the IP protocol for RAW IP pcbs  7 * @param callback a function to call on status changes (RX available, TX'ed)  8 * @return a newly allocated struct netconn or  9 * NULL on memory error 10 */
11 struct netconn*
12 netconn_new_with_proto_and_callback(enum netconn_type t, u8_t proto, netconn_callback callback) 13 { 14   struct netconn *conn; 15   struct api_msg msg; 16 
17   conn = netconn_alloc(t, callback); // 開辟一個netconn
18   if (conn != NULL ) { 19     msg.function = do_newconn; // do_newconn這個函數以msg的形式送給tcpip_thread()去處理,我們隨后會分析。這里需要知道do_newconn會開辟一個pcb,並和已有的conn綁定。
20     msg.msg.msg.n.proto = proto; 21     msg.msg.conn = conn; 22     TCPIP_APIMSG(&msg); 23 
24     if (conn->err != ERR_OK) { 25       LWIP_ASSERT("freeing conn without freeing pcb", conn->pcb.tcp == NULL); 26       LWIP_ASSERT("conn has no op_completed", conn->op_completed != SYS_SEM_NULL); 27       LWIP_ASSERT("conn has no recvmbox", conn->recvmbox != SYS_MBOX_NULL); 28       LWIP_ASSERT("conn->acceptmbox shouldn't exist", conn->acceptmbox == SYS_MBOX_NULL); 29       sys_sem_free(conn->op_completed); 30       sys_mbox_free(conn->recvmbox); 31  memp_free(MEMP_NETCONN, conn); 32       return NULL; 33  } 34  } 35   return conn; 36 }

 

上述代碼中,有4行紅色的在我們分析socket中會經常看到。我們不妨先岔開話題,看一下這4行代碼。

api_msg做了什么

1     msg.function = do_newconn; 
2     msg.msg.msg.n.proto = proto;
3     msg.msg.conn = conn;
4     TCPIP_APIMSG(&msg); 

 

首先來看TCPIP_APIMSG這個宏做了什么:
1 #define TCPIP_APIMSG(m)       tcpip_apimsg(m)

 

 1 /**  2 * Call the lower part of a netconn_* function  3 * This function is then running in the thread context  4 * of tcpip_thread and has exclusive access to lwIP core code.  5 *  6 * @param apimsg a struct containing the function to call and its parameters  7 * @return ERR_OK if the function was called, another err_t if not  8 */
 9 err_t 10 tcpip_apimsg(struct api_msg *apimsg) 11 { 12   struct tcpip_msg msg; 13  
14   if (mbox != SYS_MBOX_NULL) { 15     msg.type = TCPIP_MSG_API; // 隨后在tcpip_thread()里解析這個msg時需要根據這個type確定走哪個分支
16     msg.msg.apimsg = apimsg; 17     sys_mbox_post(mbox, &msg); // mbox是一個全局mailbox,實際上是一個數組,元素是void*型指針,在tcpip_init里被初始化。這里把msg地址放到mbox里
18     sys_arch_sem_wait(apimsg->msg.conn->op_completed, 0); 19     return ERR_OK; 20  } 21   return ERR_VAL; 22 }

 

至此,一個TCPIP_MSG_API type的msg被放到了mbox這個mailbox里,接下來tcpip_thread要從這個mailbox里取msg並對其進行處理,主要就是調用msg里的function。如下:

 

 1 /**  2 * The main lwIP thread. This thread has exclusive access to lwIP core functions  3 * (unless access to them is not locked). Other threads communicate with this  4 * thread using message boxes.  5 *  6 * It also starts all the timers to make sure they are running in the right  7 * thread context.  8 *  9 * @param arg unused argument 10 */
11 static void
12 tcpip_thread(void *arg) 13 { 14   struct tcpip_msg *msg; 15  LWIP_UNUSED_ARG(arg); 16 
17 ...................... 18 
19  LOCK_TCPIP_CORE(); 20   while (1) {                          /* MAIN Loop */
21     sys_mbox_fetch(mbox, (void *)&msg); 22     switch (msg->type) { 23 #if LWIP_NETCONN
24     case TCPIP_MSG_API: 25       LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: API message %p\n", (void *)msg)); 26       msg->msg.apimsg->function(&(msg->msg.apimsg->msg)); // 這個function就是netconn_write()函數里賦值的do_newconn
27       break; 28 #endif /* LWIP_NETCONN */
29 
30 .............. 31 
32     default: 33       break; 34  } 35  } 36 }

 

注意tcpip_thread()函數在tcpip.c(component\common\network\lwip\lwip_v1.3.2\src\api)里,可以認為是lwip api層的函數,只不過雖然名字叫api,但應用層並不是直接調用,應用層實際上是借mailbox與其交互的。當然用戶並不知道mailbox的存在,應用層只需要直接調用send()這個lwip api,后續放入mailbox以及tcpip_thread從mailbox取走,都是lwip自己完成的。
 
題外話結束。
至此,我們知道了do_newconn是怎么被調用到的了。現在我們看一下do_newconn的內容。
1 void
2 do_newconn(struct api_msg_msg *msg) 3 { 4    if(msg->conn->pcb.tcp == NULL) { 5  pcb_new(msg); 6  } 7 .......... 8 }

 

 1 static err_t  2 pcb_new(struct api_msg_msg *msg)  3 {  4 ....................  5    /* Allocate a PCB for this connection */
 6    switch(NETCONNTYPE_GROUP(msg->conn->type)) {  7 ...................  8 #if LWIP_TCP
 9    case NETCONN_TCP: 10      msg->conn->pcb.tcp = tcp_new();  // 新建一個tcp_pcb結構體,並把這個pcb與conn綁定起來
11      if(msg->conn->pcb.tcp == NULL) { 12        msg->conn->err = ERR_MEM; 13        break; 14  } 15      setup_tcp(msg->conn); 16      break; 17 #endif /* LWIP_TCP */
18 ................. 19  } 20 ................ 21 }

 

 
原來如此,do_newconn主要是在開辟了一個conn之后,接着開辟一個pcb並與這個conn綁定。

 

2.創建socket

lwip_socket()接下來通過alloc_socket()創建了socket。
代碼如下:
 1 /**  2 * Allocate a new socket for a given netconn.  3 *  4 * @param newconn the netconn for which to allocate a socket  5 * @return the index of the new socket; -1 on error  6 */
 7 static int
 8 alloc_socket(struct netconn *newconn)  9 { 10   int i; 11 
12   /* Protect socket array */
13  sys_sem_wait(socksem); 14 
15   /* allocate a new socket identifier */
16   for (i = 0; i < NUM_SOCKETS; ++i) { 17     if (!sockets[i].conn) { // 從系統socket列表:sockets[]里尋找還沒有被使用的
18       sockets[i].conn       = newconn; // 找出一個未被使用的socket結構體,作為用戶調用socket() API申請到的socket結構體,並把它和新建的netconn綁定。
19       sockets[i].lastdata   = NULL; 20       sockets[i].lastoffset = 0; 21       sockets[i].rcvevent   = 0; 22       sockets[i].sendevent  = 1; /* TCP send buf is empty */
23       sockets[i].flags      = 0; 24       sockets[i].err        = 0; 25  sys_sem_signal(socksem); 26       return i; // 僅返回一個int型的i,即用戶看不到這個socket結構體,只能socket結構體在socket列表里的index值,用戶能使用的也就是這個int值
27  } 28  } 29  sys_sem_signal(socksem); 30   return -1; 31 }

 

sockets[]是一個全局變量,存有系統所有的socket,注意它的類型是系統內部維護的socket結構體,不是用戶看到的int型。如下:
1 /** The global array of available sockets */
2 static struct lwip_socket sockets[NUM_SOCKETS];
 
至此,lwip_socket()新建了netconn、pcb和socket,並把這三者綁定在了一條線上。 
 
 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM