首页 > 代码库 > lwip socket探秘之socket创建

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和RAW11   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 error10 */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); // 开辟一个netconn18   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_t10 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 argument10 */11 static void12 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_NETCONN24     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_newconn27       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 void2 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   =http://www.mamicode.com/ 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,并把这三者绑定在了一条线上。 
 
 
 
 

lwip socket探秘之socket创建