学习笔记---lwip socket通信函数解析【tcp通信】

本文详细解析了在LWIP中基于socketAPI实现TCP服务器时涉及的关键结构如netconn和lwip_sock,包括socket创建、绑定、关闭、监听和数据接收的原理,以及数据如何从网卡传递到接收邮箱的过程。
摘要由CSDN通过智能技术生成
写在最前面

  • 在基于lwip socket api完成tcp服务器端代码过程中,因为个人对sokcet部分原理和函数了解不清楚,出现了很多问题,故本文想通过对socket涉及结构体和函数的仔细解析将soket通信了解清楚。
  • 在lwip中查看lwip-2.1.1/src/api/sockets.c 以及对应的sockets.h源码文件【不止这些,越看越多o(╥﹏╥)o】
  • 写的应该有不准确的地方,还只是个人粗浅(浅薄)的理解
  • 参考链接顶头上:https://blog.csdn.net/wuhenyouyuyouyu/article/details/89187386

一、函数与涉及数据结构说明


在sockets.h中通过重定义,将lwip_socket,lwip_recv,lwip_send等函数定义为socket, recv, send 等,因而在使用时采用socket(),recv(),send()等函数,实际实现查看lwip_socket(),lwip_recv()等。
在这里插入图片描述

1. socket 创建函数: socket(domain,type,protocol) <---->int lwip_socket(int domain, int type, int protocol)

  • 调用过程如下图,整个过程围绕着struct netconn *conn 结构体指针根据函数参数type的不同,完成结构体的字段的初始化
    在这里插入图片描述

  • 数据结构查看

    • netconn结构体内容如下,其包含了连接类型,连接状态,协议对应的PCB,以及接收数据存储邮箱,TCP建立连接对应的邮箱

      struct netconn {
         /** type of the netconn (TCP, UDP or RAW) */
         enum netconn_type type;
         /** current state of the netconn */
         enum netconn_state state;
         /** the lwIP internal protocol control block */
         union {
         struct ip_pcb  *ip;
         struct tcp_pcb *tcp;
         struct udp_pcb *udp;
         struct raw_pcb *raw;
         } pcb;
         /** the last asynchronous unreported error this netconn had */
         err_t pending_err;
         #if !LWIP_NETCONN_SEM_PER_THREAD
             /** sem that is used to synchronously execute functions in the core context */
             sys_sem_t op_completed;
         #endif
             /** mbox where received packets are stored until they are fetched
                 by the netconn application thread (can grow quite big) */
             sys_mbox_t recvmbox;//接收数据的数据包存储的邮箱
         #if LWIP_TCP
             /** mbox where new connections are stored until processed
                 by the application thread */
             sys_mbox_t acceptmbox;
         #endif /* LWIP_TCP */
      
         #if LWIP_SOCKET
             int socket;
         #endif /* LWIP_SOCKET */
             u8_t flags;
         #if LWIP_TCP
             struct api_msg *current_msg;
         #endif /* LWIP_TCP */
             netconn_callback callback;
         };
      
    • lwip_sock 结构体:因整个socket的实现是基于netconn的,socket相当于在netconn的基础上包了一层,故而,其结构体实际内涵为netonn结构体指针,另外包含socket的poll和select模式下的一些回调函数相关字段

      /** Contains all internal pointers and states used for a socket */
      struct lwip_sock {
        /** sockets currently are built on netconns, each socket has one netconn */
        struct netconn *conn;
        /** data that was left from the previous read */
        union lwip_sock_lastdata lastdata;
      #if LWIP_SOCKET_SELECT || LWIP_SOCKET_POLL
        /** number of times data was received, set by event_callback(),
            tested by the receive and select functions */
        s16_t rcvevent;
        /** number of times data was ACKed (free send buffer), set by event_callback(),
            tested by select */
        u16_t sendevent;
        /** error happened for this socket, set by event_callback(), tested by select */
        u16_t errevent;
        /** counter of how many threads are waiting for this socket using select */
        SELWAIT_T select_waiting;
      #endif /* LWIP_SOCKET_SELECT || LWIP_SOCKET_POLL */
      };
      
  • 函数返回值: 类型为int,实际含义为socket标记符,在lwip配置文件中,可配置允许的最大套接字个数,对应字段为MEMP_NUM_NETCONN。在alloc_socket()函数中,会从0->NUM_SOCKETS逐次分配socket,若当前第i个socket已经初始化,则分配第i+1个socket。具体分配从sockets.c的全局变量/** The global array of available sockets */static struct lwip_sock sockets[NUM_SOCKETS]数组中进行分配。

  • 参数

    • domain: ipv4(AF_INET)或ipv6(AF_INET6)
    • type: 三种,分别为:SOCK_STREAM<–>tcp,SOCK_DGRAM<–>udp,SOCK_RAW<–>raw
2. socket 关闭函数:socket(domain,type,protocol) <----> int lwip_close(int s)

  • 实际调用netconn_prepare_delete(sock->conn),该函数采用回调机制,调用lwip_netconn_do_delconn,根据udp和tcp断连的不同,udp直接采用udp_remove,而tcp需要四次挥手,调用lwip_netconn_do_close_internal(struct netconn *conn WRITE_DELAYED_PARAM)来实现tcp关闭连接过程
  • 等netconn释放结束后,通过free_socket(struct lwip_sock *sock, int is_tcp)来释放lwip_socket结构体
3. socket绑定ip,端口函数:bind(s,name,namelen) <----> lwip_bind(int s, const struct sockaddr *name, socklen_t namelen)

  • 实际调用:netconn_bind(sock->conn, &local_addr, local_port),最终是对socket->netconn>pcb的ip和port进行赋值操作
4. 监听函数 :listen(s,backlog) <----> int lwip_listen(int s, int backlog)

  • 实际调用:netconn_listen_with_backlog(sock->conn, (u8_t)backlog),再次调用回调函数void lwip_netconn_do_listen(void *m),最终调用tcp_listen_with_backlog_and_err(msg->conn->pcb.tcp, backlog, &err)函数创建lpcb,将lpcb赋值给conn中的tcp的pcb中的callback_arg,随后将状态改为listen状态
5. socket数据接收函数:recv(s,mem,len,flags) <----> lwip_recv(s,mem,len,flags)

  • 函数间调用关系如下图,蓝色虚线框内为netconn相关内容,从调用关系可以看出,整个接收函数从socket层调用到了netconn层,最终从conn的接收邮箱中将数据读出至pbuf中,将pbuf赋值给socket->lastdata.pbuf,用了socket通信后不好的地方在于返回值只有0, -1 以及接收的数据长度,不能很好的体现出接收数据的状态,还需要再好好看看有没有其他方式可以获取到当前连接的状态

    在这里插入图片描述

  • 那么数据又是怎么放入邮箱的呢

    • 全局搜索conn->recvmbox 可以追溯到了listen阶段:在lwip_netconn_do_listen()函数中,创建lpcb成功后,会删除掉旧的接收邮箱,创建接受连接的邮箱,同时注册accept的回调函数tcp_accept(msg->conn->pcb.tcp, accept_function),在accept_function中会设置TCP setup_tcp(newconn),在该函数中,注册了tcp的接收,发送,以及错误回调函数
    setup_tcp(struct netconn *conn)
    {
      struct tcp_pcb *pcb;
    
      pcb = conn->pcb.tcp;
      tcp_arg(pcb, conn);
      tcp_recv(pcb, recv_tcp);
      tcp_sent(pcb, sent_tcp);
      tcp_poll(pcb, poll_tcp, NETCONN_TCP_POLL_INTERVAL);
      tcp_err(pcb, err_tcp);
    } 
    
  • 查看tcp_recv(pcb, recv_tcp),可以看到就是赋值的过程,将recv_tcp()函数注册为pcb的接收回调函数

    tcp_recv(struct tcp_pcb *pcb, tcp_recv_fn recv)
    {
      LWIP_ASSERT_CORE_LOCKED();
      if (pcb != NULL) {
        LWIP_ASSERT("invalid socket state for recv callback", pcb->state != LISTEN);
        pcb->recv = recv;
      }
    }
    
  • 进一步查看静态函数 static err_t recv_tcp,可以看到将pbuf放入接收邮箱(recvmbox)的过程

     sys_mbox_trypost(&conn->recvmbox, msg)
    
  • 至此,我们还是没有解决,放入到邮箱的数据是怎么来的?
    查看野火写的《LWIP应用开发实战指南》可以看到下图,在书中为图9-1,描述了一个数据从网卡到内核的过程
    在这里插入图片描述

    与图中稍有不同的是,在tcpip_input函数的实现中,调用了tcpip_inpkt()函数,该函数通过查看LWIP_TCPIP_CORE_LOCKING_INPUT是否为1决定了是否向tcpip_mbox投递消息。源代码如下:

      err_t
      tcpip_inpkt(struct pbuf *p, struct netif *inp, netif_input_fn input_fn)
      {
      #if LWIP_TCPIP_CORE_LOCKING_INPUT
      err_t ret;
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_inpkt: PACKET %p/%p\n", (void *)p, (void *)inp));
      LOCK_TCPIP_CORE();
      ret = input_fn(p, inp);
      UNLOCK_TCPIP_CORE();
      return ret;
      #else /* LWIP_TCPIP_CORE_LOCKING_INPUT */
      struct tcpip_msg *msg;
    
      LWIP_ASSERT("Invalid mbox", sys_mbox_valid_val(tcpip_mbox));
    
      msg = (struct tcpip_msg *)memp_malloc(MEMP_TCPIP_MSG_INPKT);
      if (msg == NULL) {
          return ERR_MEM;
      }
    
      msg->type = TCPIP_MSG_INPKT;
      msg->msg.inp.p = p;
      msg->msg.inp.netif = inp;
      msg->msg.inp.input_fn = input_fn;
      if (sys_mbox_trypost(&tcpip_mbox, msg) != ERR_OK) {
          memp_free(MEMP_TCPIP_MSG_INPKT, msg);
          return ERR_MEM;
      }
      return ERR_OK;
      #endif /* LWIP_TCPIP_CORE_LOCKING_INPUT */
      }
    
    • 不管是否向tcpip_mbox的邮箱投递消息,殊途同归的是,最终都会调用ip4_input()->tcp_input()->tcp_process()->tcp_receive(),这部分的代码因为tcp接收的复杂性而比较长,看了又看,看会到tcp_input()函数,可以注意到在tcp_process()后,有一句

       /* Notify application that data has been received. */
       TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);
      

    其具体实现为,调用pcb的接收回调函数实现数据接收,这里便和socket(应用层)的setup_tcp()函数对应上了!!,也就是说这里会调用recv_tcp()函数,将pbuf放入到接收邮箱中!
    此处,必须放上参考链接:https://blog.csdn.net/wuhenyouyuyouyu/article/details/89187386

    #define TCP_EVENT_RECV(pcb,p,err,ret)                          \
      do {                                                         \
        if((pcb)->recv != NULL) {                                  \
          (ret) = (pcb)->recv((pcb)->callback_arg,(pcb),(p),(err));\
        } else {                                                   \
          (ret) = tcp_recv_null(NULL, (pcb), (p), (err));          \
        }                                                          \
      } while (0)
    

未完待续。。。

  • 23
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值