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,并把这三者绑定在了一条线上。

时间: 2024-10-25 00:14:00

lwip socket探秘之socket创建的相关文章

lwip socket探秘之bind

一个基本的socket建立顺序是 Server端: socket() bind() listen() accept() recv() Client端: socket() connect() send() 本文着重介绍Server端的bind()过程. 用户调用bind()时,实际调用的是lwip_bind(),我们从这个函数看起: 1 int 2 lwip_bind(int s, const struct sockaddr *name, socklen_t namelen) 3 { 4 ....

lwip socket探秘之accept

一个基本的socket建立顺序是 Server端: socket() bind() listen() accept() recv() Client端: socket() connect() send() 本文着重介绍Server端的accept()过程. 上一篇我们已经分析了listen()过程,listen()过程新建了pcb并把它放到了tcp_listen_pcbs这个链表里. 接下来,Client端通过Server绑定的地址和端口号(通过bind绑定),给Server发包.Server收到

socket系列之socket服务端与客户端如何通信

上面已经分别介绍了ServerSocket跟Socket的工作步骤,并且从应用层往系统底层剖析其运作原理,我们清楚了他们各自的一块,现在我们将把他们结合起来,看看他们是如何通信的,并详细讨论一下他们之间相互通信的一些细节. 借助图2-3-2-4,想象一下你正在大学课室上着电脑,你跟你另外两个朋友觉得老师讲得课很菜,没必要听,于是你们仨都各自打开浏览器冲浪,刚好你们访问了同一台服务器,假如你用的是浏览器A,那么整个流程为: ① 浏览器确认目标IP跟目标端口号(http默认使用80端口),当然如果你

1.socket编程:socket编程,网络字节序,函数介绍,IP地址转换函数,sockaddr数据结构,网络套接字函数,socket相关函数,TCP server和client

 1  Socket编程 socket这个词可以表示很多概念: 在TCP/IP协议中,"IP地址+TCP或UDP端口号"唯一标识网络通讯中的一个进程,"IP 地址+端口号"就称为socket. 在TCP协议中,建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接.socket本身有"插座"的意思,因此用来描述网络连 接的一对一关系. TCP/IP协议最早在BSD UNIX上实现,

Socket 初识 用Socket建立一个简易Web服务器

摘自<Asp.Net 本质论>作者:郝冠军 //在.Net中.system.Net命名空间提供了网络编程的大多数数据据类型以及常用操作,其中常用的类型如下:/*IPAddress 类表示一个IP地址* IPEndPoint类用来表示一个IP地址和一个端口号的组合,成为网络的端点.* System.Net.Sockets命名空间中提供了基于Socked编程的数据类型.* Socket类封装了Socked的操作.* 常见的操作:* Listen:设置基于连接通信的Socket进入监听状态,并设置等

socket 和serve socket

Socket服务端的创建<br>1.创建ServerSocket对象,绑定监听端口<br>2.通过accept()方法监听客户端去那个球3.连接建立后,通过输入流读取客户端发送的请求信息4.通过输出流向客户端发送响应信息5.关闭相关资源 Socket客户端的创建1.创建Socket,指定要连接的服务器的地址和端口号2.连接建立后,通过输出流向服务器端发送请求信息3.通过输入流获取服务器响应的信息4.关闭相应的资源

Socket简介(精通 Socket 养全家)

 0.     nc -lk 端口号 :始终监听本地计算机此端口的数据.             1.导入三个头文件     {         #import <sys/socket.h>         #import <netinet/in.h>         #import <arpa/inet.h>     }         2.Socket书写步骤     {         1.创建客户端Socket            socket(<#in

Java Socket 编程之Socket与ServerSocket的区别

http://www.cnblogs.com/hq-antoine/archive/2012/02/11/2346474.html 1.1 ServerSocket类    创建一个ServerSocket类,同时在运行该语句的计算机的指定端口处建立一个监听服务,如:     ServerSocket MyListener=new ServerSocket(600):     这里指定提供监听服务的端口是600,一台计算机可以同时提供多个服务,这些不同的服务之间通过端口号来区别,不同的端口号上提

Socket tips: 同意socket发送UDP Broadcast

假设创建一个UDP Socket: socketHandle = socket(serverAddr->ai_family, serverAddr->ai_socktype, serverAddr->ai_protocol); 如今就用它来发送Broadcast.一定会出错的,Socket API不同意这样做.那么怎样使它支持broadcast呢?须要继续做一些设置: int broadcastPermission = 1; setsockopt(socketHandle, SOL_SO