lwipsocket探秘之socket创建
recv函数⼀个基本的socket建⽴顺序是
Server端:
socket()
bind()
listen()
accept()
recv()
Client端:
socket()
connect()
send()
本⽂着重介绍Server端的socket()过程。
⽤户使⽤socket时,⾸先会调⽤socket()函数创建⼀个socket。在lwip中实际调⽤的就是lwip_socket()函数。
代码如下:
1int
2 lwip_socket(int domain, int type, int protocol)
3 {
4struct netconn *conn;
5int i;
6
7 LWIP_UNUSED_ARG(domain);
8
9/* create a netconn */
10switch (type) { // 根据⽤户传⼊的type区分TCP、UDP和RAW
11case 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));
15break;
16case 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));
21break;
22case 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));
26break;
27default:
28 LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%d, %d/UNKNOWN, %d) = -1\n",
29 domain, type, protocol));
30 set_errno(EINVAL);
31return -1;
32 }
33
34if (!conn) {
35 LWIP_DEBUGF(SOCKETS_DEBUG, ("-1 / ENOBUFS (could not create netconn)\n"));
36 set_errno(ENOBUFS);
37return -1;
38 }
39
40 i = alloc_socket(conn); // 开辟⼀个socket,这个函数也很重要
41
42if (i == -1) {
43 netconn_delete(conn);
44 set_errno(ENFILE);
45return -1;
46 }
47 conn->socket = i;
48 LWIP_DEBUGF(SOCKETS_DEBUG, ("%d\n", i));
49 set_errno(0);
50return 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*/
11struct netconn*
12 netconn_new_with_proto_and_callback(enum netconn_type t, u8_t proto, netconn_callback callback)
13 {
14struct netconn *conn;
15struct api_msg msg;
16
17 conn = netconn_alloc(t, callback); // 开辟⼀个netconn
18if (conn != NULL ) {
19msg.function = do_newconn;// do_newconn这个函数以msg的形式送给tcpip_thread()去处理,我们随后会分析。这⾥需要知道do_newconn会开辟⼀个pcb,并和已有的conn绑定。
20msg.proto = proto;
= conn;
22TCPIP_APIMSG(&msg);
23
24if (conn->err != ERR_OK) {
25 LWIP_ASSERT("freeing conn without freeing pcb", conn-&p == 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);
32return NULL;
33 }
34 }
35return conn;
36 }
上述代码中,有4⾏紅⾊的在我们分析socket中会经常看到。我们不妨先岔开话题,看⼀下这4⾏代码。
api_msg做了什么
1 msg.function = do_newconn;
2 msg.proto = proto;
3 = 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 {
12struct tcpip_msg msg;
13
14if (mbox != SYS_MBOX_NULL) {
15 pe = 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-&->op_completed, 0);
19return ERR_OK;
20 }
21return 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*/
11static void
12 tcpip_thread(void *arg)
13 {
14struct tcpip_msg *msg;
15 LWIP_UNUSED_ARG(arg);
16
17 ......................
18
19 LOCK_TCPIP_CORE();
20while (1) { /* MAIN Loop */
21 sys_mbox_fetch(mbox, (void *)&msg);
22switch (msg->type) {
23#if LWIP_NETCONN
24case 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
27break;
28#endif /* LWIP_NETCONN */
29
30 ..............
31
32default:
33break;
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的内容。
1void
2 do_newconn(struct api_msg_msg *msg)
3 {
4if(msg->conn-&p == NULL) {
5 pcb_new(msg);
6 }
7 ..........
8 }
1static err_t
2 pcb_new(struct api_msg_msg *msg)
3 {
4 ....................
5/* Allocate a PCB for this connection */
6switch(NETCONNTYPE_GROUP(msg->conn->type)) {
7 ...................
8#if LWIP_TCP
9case NETCONN_TCP:
10 msg->conn-&p = tcp_new(); // 新建⼀个tcp_pcb结构体,并把这个pcb与conn绑定起来
11if(msg->conn-&p == NULL) {
12 msg->conn->err = ERR_MEM;
13break;
14 }
15 setup_tcp(msg->conn);
16break;
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*/
7static int
8 alloc_socket(struct netconn *newconn)
9 {
10int i;
11
12/* Protect socket array */
13 sys_sem_wait(socksem);
14
15/* allocate a new socket identifier */
16for (i = 0; i < NUM_SOCKETS; ++i) {
17if (!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);
26return i; // 仅返回⼀个int型的i,即⽤户看不到这个socket结构体,只能socket结构体在socket列表⾥的index值,⽤户能使⽤的也就是这个int值
27 }
28 }
29 sys_sem_signal(socksem);
30return -1;
31 }
sockets[]是⼀个全局变量,存有系统所有的socket,注意它的类型是系统内部维护的socket结构体,不是⽤户看到的int型。如下:
1/** The global array of available sockets */
2static struct lwip_socket sockets[NUM_SOCKETS];
⾄此,lwip_socket()新建了netconn、pcb和socket,并把这三者绑定在了⼀条线上。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论