lwip socket探秘之listen

本文详细解析了服务器端socket的listen过程,特别关注lwip实现中如何通过重新分配内存来设置socket进入监听状态,并介绍其核心函数tcp_listen_with_backlog的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一个基本的socket建立顺序是
Server端:
  • socket()
  • bind()
  • listen()
  • accept()
  • recv()
Client端:
  • socket()
  • connect()
  • send()
 
本文着重介绍Server端的listen()过程。
 
用户使用socket,调用listen()时,实际调用的是lwip里的lwip_listen()。代码如下
 1 /**
 2 * Set a socket into listen mode.
 3 * The socket may not have been used for another connection previously.
 4 *
 5 * @param s the socket to set to listening mode
 6 * @param backlog (ATTENTION: need TCP_LISTEN_BACKLOG=1)
 7 * @return 0 on success, non-zero on failure
 8 */
 9 int
10 lwip_listen(int s, int backlog)
11 {
12   struct lwip_socket *sock;
13   err_t err;
14 .............
15   sock = get_socket(s); // 根据socket号(面向用户的socket标识)得到lwip内部的socket descriptor
16   if (!sock)
17     return -1;
18 ...............
19   err = netconn_listen_with_backlog(sock->conn, backlog); // 接下来看这个函数
20 ...............
21   return 0;
22 }

netconn_listen_with_backlog本身内容很少,主要是向下一连串调用:

netconn_listen_with_backlog
     =>do_listen
          =>tcp_listen
               =>tcp_listen_with_backlog
 
tcp_listen_with_backlog这个函数才是真正做了重要工作的地方。
 1 /**
 2 * Set the state of the connection to be LISTEN, which means that it
 3 * is able to accept incoming connections. The protocol control block
 4 * is reallocated in order to consume less memory. Setting the
 5 * connection to LISTEN is an irreversible process.
 6 *
 7 * @param pcb the original tcp_pcb
 8 * @param backlog the incoming connections queue limit
 9 * @return tcp_pcb used for listening, consumes less memory.
10 *
11 * @note The original tcp_pcb is freed. This function therefore has to be
12 *       called like this:
13 *             tpcb = tcp_listen(tpcb);
14 */
15 struct tcp_pcb *
16 tcp_listen_with_backlog(struct tcp_pcb *pcb, u8_t backlog)
17 {
18   struct tcp_pcb_listen *lpcb;
19 
20   LWIP_UNUSED_ARG(backlog);
21   LWIP_ERROR("tcp_listen: pcb already connected", pcb->state == CLOSED, return NULL);
22 
23   /* already listening? */
24   if (pcb->state == LISTEN) {
25     return pcb;
26   }
27   lpcb = memp_malloc(MEMP_TCP_PCB_LISTEN); // 新建了一个pcb,后面会把原来的pcb free掉
28   if (lpcb == NULL) {
29     return NULL;
30   }
31   lpcb->callback_arg = pcb->callback_arg; // 新pcb继承旧pcb的一些内容
32   lpcb->local_port = pcb->local_port;
33   lpcb->state = LISTEN; // 因为用户调用了listen(),所以这个新pcb的状态是LISTEN
34   lpcb->so_options = pcb->so_options;
35   lpcb->so_options |= SOF_ACCEPTCONN;
36   lpcb->ttl = pcb->ttl;
37   lpcb->tos = pcb->tos;
38   ip_addr_set(&lpcb->local_ip, &pcb->local_ip);
39   TCP_RMV(&tcp_bound_pcbs, pcb);
40   memp_free(MEMP_TCP_PCB, pcb); // free掉旧的pcb
41 #if LWIP_CALLBACK_API
42   lpcb->accept = tcp_accept_null;
43 #endif /* LWIP_CALLBACK_API */
44 #if TCP_LISTEN_BACKLOG
45   lpcb->accepts_pending = 0;
46   lpcb->backlog = (backlog ? backlog : 1);
47 #endif /* TCP_LISTEN_BACKLOG */
48   TCP_REG(&tcp_listen_pcbs.listen_pcbs, lpcb); // 把新pcb挂到tcp_listen_pcbs这个链表里
49   return (struct tcp_pcb *)lpcb;
50 }
注意阅读函数上方的注释,这些注释简要的介绍了函数的作用,写的都非常有用。
tcp_listen_with_backlog这个函数主要是重新开辟了一个pcb代替旧的pcb(出于节省空间的考虑),将新pcb的状态设置为LISTEN,并将其挂在lwip里的tcp_listen_pcbs这个链表里。
 
 

转载于:https://www.cnblogs.com/codingfun/p/4186343.html

### LwIP Socket 的使用方法及示例代码 LwIP 是一种轻量级 TCP/IP 协议栈,广泛应用于嵌入式系统中。它的 Socket 接口设计类似于 BSD Socket,但在功能上有所简化[^1]。以下是关于 LwIP Socket 的基本用法及其典型应用场景。 #### 1. 创建套接字 (Socket) 创建一个套接字用于通信,可以通过 `lwip_socket` 函数完成: ```c int sockfd = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sockfd < 0) { // 错误处理逻辑 } ``` 上述代码片段表示创建了一个面向流的 TCP 套接字。参数解释如下: - `AF_INET`: 表明使用的地址族为 IPv4。 - `SOCK_STREAM`: 指定套接字类型为流式传输(TCP)。 - `IPPROTO_TCP`: 明确指定协议为 TCP。 此部分对应于基础的 Socket 初始化过程[^3]。 --- #### 2. 绑定本地地址 (Bind) 绑定本地 IP 地址和端口号到已创建的套接字上: ```c struct sockaddr_in addr; addr.sin_family = AF_INET; // 设置地址家族 addr.sin_port = htons(PORT); // 主机字节序转换为目标字节序 addr.sin_addr.s_addr = INADDR_ANY; // 自动获取本机 IP 地址 if (lwip_bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) != 0) { // 处理错误情况 } ``` 这里需要注意的是,`INADDR_ANY` 可以让操作系统自动选择合适的网络接口进行绑定[^2]。 --- #### 3. 进行监听 (Listen) 对于服务端程序来说,需要调用 `listen` 来等待客户端连接请求: ```c if (lwip_listen(sockfd, SOMAXCONN) != 0) { // 监听失败后的处理逻辑 } ``` 其中 `SOMAXCONN` 定义了最大排队长度,具体数值由平台决定。 --- #### 4. 接受新连接 (Accept) 当有新的客户端尝试连接时,服务器会通过 `accept` 获取一个新的文件描述符来管理此次对话: ```c struct sockaddr_in clientAddr; socklen_t addrlen = sizeof(clientAddr); int new_sockfd = lwip_accept(sockfd, (struct sockaddr*)&clientAddr, &addrlen); if (new_sockfd < 0) { // 新连接接受失败 } else { printf("New connection from %s:%d\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port)); } ``` 这段代码展示了如何捕获并打印客户端的信息[^5]。 --- #### 5. 数据交互 (Read/Write) 无论是作为客户端还是服务端,都可以利用读写函数来进行数据交换: ```c // 发送数据给对方 char *msg = "Hello LWIP!"; ssize_t sent_bytes = lwip_send(new_sockfd, msg, strlen(msg), 0); if (sent_bytes <= 0) { // 发送失败 } // 接收来自对方的数据 char buffer[1024]; memset(buffer, 0, sizeof(buffer)); ssize_t recv_bytes = lwip_recv(new_sockfd, buffer, sizeof(buffer)-1, 0); if (recv_bytes > 0) { printf("Received message: %s\n", buffer); } ``` 这些操作分别代表向远端发送消息以及从远端接收消息的功能实现[^4]。 --- #### 6. 关闭连接 (Close) 最后一步是关闭不再需要的套接字资源: ```c lwip_close(new_sockfd); ``` 释放掉所有的内存和其他关联资源是非常重要的实践之一。 --- ### 示例代码:完整的 UDP Server 实现 下面给出一段基于 STM32 平台上的简单 UDP 服务器实例代码: ```c #include "lwip/sockets.h" #include "lwip/netdb.h" #define PORT 8080 void udp_server_task(void) { int sockfd; struct sockaddr_in addr; // 创建 UDP 套接字 sockfd = lwip_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sockfd < 0) { printf("Failed to create socket.\n"); return; } memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(PORT); addr.sin_addr.s_addr = INADDR_ANY; // 将套接字与特定端口绑定 if (lwip_bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) != 0) { printf("Binding failed.\n"); lwip_close(sockfd); return; } char buffer[1024]; while (1) { struct sockaddr_in cli_addr; socklen_t len = sizeof(cli_addr); ssize_t n = lwip_recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&cli_addr, &len); if (n > 0) { buffer[n] = '\0'; printf("Message received: %s\n", buffer); // 向客户端回传相同的消息 lwip_sendto(sockfd, buffer, n, 0, (struct sockaddr*)&cli_addr, len); } else { printf("Error receiving data.\n"); } } lwip_close(sockfd); } ``` --- ### 总结 通过对 LwIPSocket API 的学习可以看出,尽管它相较于标准 BSD Socket 功能有限制,但对于大多数嵌入式项目而言已经足够满足需求。同时掌握了以上提到的关键步骤之后就可以轻松构建自己的网络应用程序了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值