目录

UIP协议栈 TCP Server Client通信成功案例

文章目录

UIP协议栈是属于极简版本的TCP/IP协议栈,比LWIP协议栈还精简,本文章只提供关键部分的参考历程还有很多东西,需要你自己去看文章去学习的,不过有个前提你得懂TCP/IP协议栈和计算机网络知识,比如学过LWIP啥的,要不然你看不懂的

这里边有相当好的 [UIP 文档资料,文档位置在仓库的UIP/uip doc ,括号内是仓库地址(https://gitee.com/free-people-in-time-and-space/net-work-learn-note.git

TCP Server

1.main循环里做的事

c 复制代码
        uip_polling(); // 处理uip事件,必须插入到用户程序的循环体中
        eth0_receive();

        sprintf(eth_txbuf, "HHH send data value WLS:%d ", i++);
        eth0_send(eth_txbuf);
        memset(eth_txbuf, 0, 64);
        i %= 127;

2.以下是main循环里相关函数的实现

c 复制代码
/**
 * @file uip_app.c
 * @author Ge (ufo281@outlook.com)
 * 
 * @brief uip app example(For TCP Server)
 * 
 * @version 0.1
 * @date 2024-10-09
 * 
 * @copyright Copyright (c) 2024
 * 
 */
#include "uip_app.h"



/*对比服务端的状态*/
unsigned char tcp_server_test_staus = 0XFF; 


/**
 * @brief form uip rx data
 * 
 */
void eth0_receive()
{

    /*客户端收到数据,并在这里处理一些数据*/
    if (tcp_server_test_staus != tcp_server_state) // TCP Client状态改变
    {
        if (tcp_server_state & (1 << 6)) // 收到新数据
        {
            /*在tcp_server_demo_appcall函数中 用strcpy()函数从uip_appdata复制过来的*/
            // uip_log("[eth0_receive]: TCP Clinet data:");
            UART1_Send_String("[eth0_receive TCP]:");
            UART1_Send_String(tcp_server_databuf);
            UART1_Send_String("\r\n");



            tcp_server_state &= ~(1 << 6); // 标记数据已经被处理
        }
        tcp_server_test_staus = tcp_server_state;
    }

    // printf("uip_timer: %d",uip_timer);
}


/**
 * @brief tx data to uip
 * 
 * @param eth0_string data
 */
void eth0_send(unsigned char *eth0_string)
{
    if (tcp_server_state & (1 << 7)) // 连接还存在
    {
        sprintf((char *)tcp_server_databuf, eth0_string);

        // 标记有数据需要发送
        tcp_server_state |= 1 << 5;
    }
    else
    {
        // UART1_Send_String("[eth0_send]:TCP Server NOT Coneceted!! \r\n");
    }
    
}



/**
 * @brief uip事件处理函数
 * 必须将该函数插入用户主循环,循环调用.
 */
void uip_polling(void)
{
    unsigned char i;
    static struct timer periodic_timer, arp_timer;
    static unsigned char timer_ok = 0;
    if (timer_ok == 0) // 仅初始化一次
    {
        timer_ok = 1;
        timer_set(&periodic_timer, CLOCK_SECOND / 2); // 创建1个0.5秒的定时器
        timer_set(&arp_timer, CLOCK_SECOND * 10);     // 创建1个10秒的定时器
    }

    uip_len = tapdev_read(); // 从网络设备读取一个IP包,得到数据长度.uip_len在uip.c中定义
    if (uip_len > 0)         // 有数据
    {
        // 处理IP数据包(只有校验通过的IP包才会被接收)
        if (BUF->type == htons(UIP_ETHTYPE_IP)) // 是否是IP包?
        {
            uip_arp_ipin(); // 去除以太网头结构,更新ARP表
            uip_input();    // IP包处理
            // 当上面的函数执行后,如果需要发送数据,则全局变量 uip_len > 0
            // 需要发送的数据在uip_buf, 长度是uip_len  (这是2个全局变量)
            if (uip_len > 0) // 需要回应数据
            {
                uip_arp_out(); // 加以太网头结构,在主动连接时可能要构造ARP请求
                tapdev_send(); // 发送数据到以太网
            }
        }
        else if (BUF->type == htons(UIP_ETHTYPE_ARP)) // 处理arp报文,是否是ARP请求包?
        {
            uip_arp_arpin();
            // 当上面的函数执行后,如果需要发送数据,则全局变量uip_len>0
            // 需要发送的数据在uip_buf, 长度是uip_len(这是2个全局变量)
            if (uip_len > 0)
                tapdev_send(); // 需要发送数据,则通过tapdev_send发送
        }
    }
    else if (timer_expired(&periodic_timer)) // 0.5秒定时器超时
    {
        timer_reset(&periodic_timer); // 复位0.5秒定时器
        // 轮流处理每个TCP连接, UIP_CONNS缺省是40个
        for (i = 0; i < UIP_CONNS; i++)
        {
            uip_periodic(i); // 处理TCP通信事件
            // 当上面的函数执行后,如果需要发送数据,则全局变量uip_len>0
            // 需要发送的数据在uip_buf, 长度是uip_len (这是2个全局变量)
            if (uip_len > 0)
            {
                uip_arp_out(); // 加以太网头结构,在主动连接时可能要构造ARP请求
                tapdev_send(); // 发送数据到以太网
            }
        }
#if UIP_UDP // UIP_UDP
        // 轮流处理每个UDP连接, UIP_UDP_CONNS缺省是10个
        for (i = 0; i < UIP_UDP_CONNS; i++)
        {
            uip_udp_periodic(i); // 处理UDP通信事件
            // 当上面的函数执行后,如果需要发送数据,则全局变量uip_len>0
            // 需要发送的数据在uip_buf, 长度是uip_len (这是2个全局变量)
            if (uip_len > 0)
            {
                uip_arp_out(); // 加以太网头结构,在主动连接时可能要构造ARP请求
                tapdev_send(); // 发送数据到以太网
            }
        }
#endif
        // 每隔10秒调用1次ARP定时器函数 用于定期ARP处理,ARP表10秒更新一次,旧的条目会被抛弃
        if (timer_expired(&arp_timer))
        {
            timer_reset(&arp_timer);
            uip_arp_timer();
        }
    }
}

/****************************************************IAP接收到程序后写入Flash************************************************************/

/**
 * @brief uip config
 *      set ip netmask router's IP address. mac addr and config ethernet mac
 *
 */
void uip_config(void)
{

    uip_ipaddr_t ipaddr, remote_ip;
    
    while (tapdev_init()) 
    {
        UART1_Send_String("MX6800 Ehthernet MAC Init Error! \r\n");

        mdelay(200);
    }

    // uIP初始化
    uip_init();

    /* ARP table initialize. */
    uip_arp_init();

    // 设置本地设置IP地址
    uip_ipaddr(ipaddr, 192, 168, 1, 137);
    uip_sethostaddr(ipaddr);

    // 设置网关IP地址(其实就是你路由器的IP地址)
    uip_ipaddr(ipaddr, 192, 168, 1, 1);
    uip_setdraddr(ipaddr);

    // 设置网络掩码
    uip_ipaddr(ipaddr, 255, 255, 255, 0);
    uip_setnetmask(ipaddr);

    // 设置 远程客户端IP为192.168.1.100
    uip_ipaddr(&remote_ip, 192, 168, 1, 100); 
    uip_connect(&remote_ip, htons(7090)); /*连接到远程客户端的通信端口为7090 */

    /* We start to listen for connections on TCP port 8090. */
    uip_listen(HTONS(8090));

}

3.还有个很重要的就是UIP用户使用的用户程序 UIP_APPCALL,进行处理收发数据的

c 复制代码
/**
 * @file tcp_demo.c
 * @author GeShuHan (ufo281@outlook.com)
 * 
 * @brief Processing of UIP protocol stack TX&&RX data
 * 
 * @version 0.1
 * @date 2024-10-09
 * 
 * @copyright Copyright (c) 2024
 * 
 */
#include "uip.h"
#include "tcp_demo.h"
#include "obc_uart.h"


unsigned char tcp_server_databuf[1500]; // 发送数据缓存


/**
 * @brief 服务端状态
 *  bit0:
 *  bit1:
 *  bit2:
 *  bit3:
 *  bit4:
 *  bit5: 0:无数据发送,有数据要发送
 *  bit6: 0:未收到数据,1:收到客户端的数据
 *  bit7: 0:无连接,1:连接成功
 */
unsigned char tcp_server_state;


/**
 * @brief 这是一个TCP 服务器应用回调函数。
 *  该函数通过UIP_APPCALL(tcp_demo_appcall)调用,实现Web Server的功能.
 * 当uip事件发生时,UIP_APPCALL函数会被调用,根据所属端口(1200),确定是否执行该函数。
 * 例如 : 当一个TCP连接被创建时、有新的数据到达、数据已经被应答、数据需要重发等事件
 */
void tcp_server_demo_appcall(void)
{
    struct tcp_demo_appstate *s = (struct tcp_demo_appstate *)&uip_conn->appstate;

    // 连接终止
    if (uip_aborted()) 
    {
        tcp_server_state &= ~(1 << 7);      // 标志没有连接
        uip_log("TCP_Server Aborted!\r\n"); // 打印log
    }

    // 连接超时
    if (uip_timedout()) 
    {
        tcp_server_state &= ~(1 << 7);      // 标志没有连接
        uip_log("TCP_Server Timeout!\r\n"); // 打印log
    }

    // 连接关闭
    if (uip_closed()) 
    {
        tcp_server_state &= ~(1 << 7);     // 标志没有连接
        uip_log("TCP_server CLOSED!\r\n"); // 打印log
    }

    // 连接成功
    if (uip_connected()) 
    {
        struct tcp_demo_appstate *s = (struct tcp_demo_appstate *)&uip_conn->appstate;
        // uip_conn结构体有一个"appstate"字段指向应用程序自定义的结构体。
        // 声明一个s指针,是为了便于使用。
        // 不需要再单独为每个uip_conn分配内存,这个已经在uip中分配好了。
        // 在uip.c 中 的相关代码如下:
        //		struct uip_conn *uip_conn;
        //		struct uip_conn uip_conns[UIP_CONNS]; //UIP_CONNS缺省=10
        // 定义了1个连接的数组,支持同时创建几个连接。
        // uip_conn是一个全局的指针,指向当前的tcp或udp连接。
        tcp_server_state |= 1 << 7;           // 标志连接成功
        uip_log("TCP_Server Connected OK!\r\n"); // 打印log
        s->state = STATE_CMD;                 // 指令状态
        s->textlen = 0;
        s->textptr = "WLS TCP_client Connected Successfully!\r\n"; // 回应消息
        // s->textptr = "Connect to ALIENTEK STM32 Board Successfully!\r\n";
        s->textlen = strlen((char *)s->textptr);
    }

    // 发送的数据成功送达
    if (uip_acked()) 
    {
        struct tcp_demo_appstate *s = (struct tcp_demo_appstate *)&uip_conn->appstate;
        
        // 发送清零
        s->textlen = 0; 
    }
    else
    {
        /*数据发送失败*/
        uip_log("TCP_Server NOT ACK!\r\n"); 
        
    }
    

    /* 接收到一个新的TCP数据包,收到客户端发过来的数据*/
    if (uip_newdata())
    {
        // 还未收到数据
        if ((tcp_server_state & (1 << 6)) == 0)
        {
            // if (uip_len > 199)
            // {
            //     ((unsigned char *)uip_appdata)[199] = 0;
            // }
            memset(tcp_server_databuf, 0, 1500);
            strcpy((char *)tcp_server_databuf, uip_appdata);

            // 表示收到客户端数据
            tcp_server_state |= 1 << 6; 
        }
    }
    else if (tcp_server_state & (1 << 5)) // 有数据需要发送
    {
        s->textptr = tcp_server_databuf;
        s->textlen = strlen((const char *)tcp_server_databuf);
        memset(tcp_server_databuf, 0, 1500);
        tcp_server_state &= ~(1 << 5); // 清除标记
    }

    // 当需要重发、新数据到达、数据包送达、连接建立时,通知uip发送数据
    if (uip_rexmit() || uip_newdata() || uip_acked() || uip_connected() || uip_poll())
    {        
        struct tcp_demo_appstate *s = (struct tcp_demo_appstate *)&uip_conn->appstate;
        // s->textptr : 发送的数据包缓冲区指针
        // s->textlen :数据包的大小(单位字节)
        if (s->textlen > 0)
            uip_send(s->textptr, s->textlen); // 发送TCP数据包
    }
}




/**
 * @brief TCP应用接口函数(UIP_APPCALL)
 *  完成TCP服务(包括server和client)
 */
void tcp_demo_appcall(void)
{

    switch (uip_conn->lport) // 本地监听端口80和1200
    {
        case HTONS(8090):
            tcp_server_demo_appcall();
            // uip_log("tcp_demo_appcall Local 8080 TCP Server!! \r\n");

    }

#ifdef TCP_Client

    /*tcp client use */
    switch (uip_conn->rport) // 远程连接8080端口
    {
        case HTONS(8080): // 一旦有来自远程主机的信息,就调用此函数
            // tcp_client_demo_appcall();
            // uip_log("tcp_demo_appcall Remote 8080 TCP Client!! \r\n");
            break;

    }

#endif

}

/**
 * @brief 打印日志用
 *
 * @param m data
 */
void uip_log(char *m)
{
    UART1_Send_String("[UIP_LOG]:");
    UART1_Send_String(m);
}

4.下边是ethernet mac 数据收发和UIP的连接处

c 复制代码
/*
 * Copyright (c) 2001, Swedish Institute of Computer Science.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Author: Adam Dunkels <adam@sics.se>
 *
 * $Id: tapdev.c,v 1.8 2006/06/07 08:39:58 adam Exp $
 */
#include "tapdev.h"
#include "uip.h"

/*STM32 Ethernet MAC RX TX BUF*/
__attribute__((aligned(4))) char ob_eth_rx_buf[1518];
__attribute__((aligned(4))) char ob_eth_tx_buf[1518];

/*MX6800 Ethernet Variable*/
struct eth_device *ob_ethernet_dev = NULL;



/**
 * @brief config ethernet mac
 *
 * @return unsigned char 0:OK
 *                       1:Failed!
 */
unsigned char tapdev_init(void)
{

    unsigned char mac_addr[6] = {0xB8, 0xAE, 0x1D, 0x00, 0x01, 0x00};

    /*config UIP MAC ADDR*/
    for (unsigned char i = 0; i < 6; i++)
        uip_ethaddr.addr[i] = mac_addr[i];

    /*---------------------------Ethernet MAC + PHY chip Init-------------------------*/

    eth_init();
    ob_ethernet_dev = eth_get_dev();
    if (!eth_is_active(ob_ethernet_dev))
    {
        UART1_Send_String("[tapdev_init] eth gmac not initiated\r\n");
        return 1;
    }

    /*set Ethernet mac address*/
    if (eth_write_hwaddr(ob_ethernet_dev, mac_addr) == -1)
    {
        UART1_Send_String("[tapdev_init] eth_write_hwaddr set mac addr ob_ethernet_dev failed! \r\n");
    }

    /*---------------------------Ethernet MAC + PHY chip Init-------------------------*/

    return 0;
}



/**
 * @brief from STM32 Ethernet MAC read data
 *
 * @return uint16_t data
 */
uint16_t tapdev_read(void)
{
    int eth_rxlen = 0;

    eth_rxlen = eth_recv(ob_ethernet_dev, ob_eth_rx_buf); /* by runsheng */
    memcpy(uip_buf, ob_eth_rx_buf, eth_rxlen);
    memset(ob_eth_rx_buf, 0, 1518);

    return eth_rxlen;
}



/**
 * @brief from STM32 Ethernet MAC send data
 *
 */
void tapdev_send(void)
{
    eth_send(ob_ethernet_dev, (void *)uip_buf, uip_len);
}
本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
小李小李快乐不已16 分钟前
3.3.2 应用层协议设计protobuf(二进制序列化协议)
linux·c++·后端·网络协议·信息与通信
Achou.Wang1 小时前
TCP 和 UDP 的区别
网络协议·tcp/ip·udp
左灯右行的爱情2 小时前
计算机网络-TCP流量控制与拥塞控制
网络·tcp/ip·计算机网络
落——枫3 小时前
网络互连与互联网
网络
codingandsleeping8 小时前
OPTIONS 预检请求
前端·网络协议·浏览器
rainFFrain9 小时前
网络基础2
服务器·网络
小南家的青蛙9 小时前
Linux的网络配置的资料
linux·运维·网络
兴达易控10 小时前
Profibus DP主站转ModbusTCP网关通讯秘籍
网络
海滩上的那乌克丽丽10 小时前
通过websocket给服务端发送订单催单提醒消息
网络·websocket·网络协议
codingandsleeping11 小时前
DNS 解析规则
网络协议·浏览器·dns