TaterLi's LazyBlog

自言自语,不喜绕路,科学上网,远离天国.

@TaterLi3周前

01/27
11:15
技术控

LwIP 代码分析(入门) – 第一集

代码都在 https://github.com/nickfox-taterli/LwIP_STM32F4-Discovery

LwIP是非常复杂的玩意,Lw指的是Light Weight(轻量级),不知道为什么有人会翻译成乐维?

需要移植的文件就两个:

这些基于非常底层的一些操作了.还有底层到寄存器的,在GitHub上我已经注释了(中文),现在大致说下框架.

用户要用LwIP,要调用MX_LWIP_Init这个总入口,他按顺序执行了网络接口的添加,初始化底层,DHCP,然后LwIP就可以用了,发包用的是netif->output,收包用的是netif->input,在netif->input事先就是收到包中断,调用low_level_input把包数据接回来,给netif->input处理,而这个netif->input实际上就是tcpip_input,他就是解包的,发包则是由netif->input交由etharp_output制作数据包,调用low_level_output发出去.

学过USB的都知道,传输的控制数据虽然是Byte的,但是其实每个Bit或者每个Byte含义不同,后续的功能也不同,以太网是一样的,根据IP头解释后面的数据是什么东西,再跟,然后深入解释TCP头里面又是什么东西.所谓的7层OSI模型,头晕.

偷了一张图,用最大白话说.

最下面,就是我们的网卡,连接层也就是我们的MCU,MCU有MAC和LLC,有些还具备PTP(比如STM32F4就具备),这些都是硬件,实际存在的,而LwIP包含的是传输层和网络层,收到网络层的包,就可以进一步解释是不是有传输层协议,比如ICMP他可以携带数据,但是他不是传输层协议.LwIP只做到TCP,UDP就结束了,后面是用户应用,如果开发过Linux的都知道,这其实就是socket,什么服务器软件都是基于socket的嘛.(甚至是个邮件服务器,KMS服务器也可以运行在MCU上.)

分析代码两种方法,自上而下和自下而上,因为很多人(包括我)不知道一个协议包可能都有什么,不如趁机学习协议,就自上而下学习下.LwIP支持两种通信,netconn和socket方式,一开始我也不知道哪个好,好像传递裸数据的,用netconn比较多,那我就用netconn吧.

下一集开始分析这段代码.

void udpecho_thread(void *arg)
{
    struct netconn *udpconn;
    struct netbuf *udpbuf;
    struct netbuf *recv_udpbuf;
    ip_addr_t udpaddr;
    uint8_t udpdemo_buf[5] = {0xAA, 0x55, 0xFF, 0x5A, 0xA5};
    err_t err;
    err_t recv_err;

    /* 新建一个连接块 */
    udpconn = netconn_new(NETCONN_UDP);
    udpconn->recv_timeout = 1000; /* 1000毫秒收不到东西,接收函数也不会堵塞. */
    if (udpconn != NULL) /* 间接申请了内存 */
    {
        /* 绑定本地所有地址(开发板是本地) */
        err = netconn_bind(udpconn, IP_ADDR_ANY, 7001);
        /* 写目标地址 */
        IP4_ADDR(&udpaddr, 10, 0, 1, 35);
        /* 连接目标端口,UDP是无状态协议,肯定能连接成功的. */
        netconn_connect(udpconn, &udpaddr, 7800);
        if (err == ERR_OK)
        {
            while (1)
            {

                recv_err = netconn_recv(udpconn, &recv_udpbuf);
                if (recv_err == ERR_OK)
                {
                    if(recv_udpbuf->p->len >= 1)  udpdemo_buf[4] = ((uint8_t *)recv_udpbuf->p->payload)[0];
                    netbuf_delete(recv_udpbuf);
                }

                /* UDP 缓冲区申请 */
                udpbuf = netbuf_new();
                /* 申请内存 */
                netbuf_alloc(udpbuf, strlen((char *)udpdemo_buf));
                /* 把数据复制到payload里去. */
                memcpy(udpbuf->p->payload, (void *)udpdemo_buf, strlen((const char *)udpdemo_buf));
                /* payload 其实也可以直接修改. */
                ((uint32_t *)udpbuf->p->payload)[0] = xTaskGetTickCount();
                /* 这一步把数据发送出去. */
                err = netconn_send(udpconn, udpbuf);
                /* UDP总会发成功的. */
                netbuf_delete(udpbuf);
                /* 延迟等下一次再发. */
                vTaskDelay(1000);
            }
        }
        else
        {
            /* 如果本地地址绑定不了,那么失败. */
            netconn_delete(udpconn);
        }
    }
    vTaskDelete(NULL);
}

 

LwIP 代码分析(入门) – 第一集