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);
}
相关推荐
Crossoads6 分钟前
【汇编语言】端口 —— 「从端口到时间:一文了解CMOS RAM与汇编指令的交汇」
android·java·汇编·深度学习·网络协议·机器学习·汇编语言
诸葛悠闲8 分钟前
SOME/IP 协议详解——信息格式
网络协议
gywl3 小时前
openEuler VM虚拟机操作(期末考试)
linux·服务器·网络·windows·http·centos
WTT00113 小时前
2024楚慧杯WP
大数据·运维·网络·安全·web安全·ctf
_oP_i4 小时前
HTTP 请求Media typetext/plain application/json text/json区别
网络协议·http·json
杨德杰4 小时前
QT网络(一):主机信息查询
网络·qt
007php0074 小时前
Go语言zero项目部署后启动失败问题分析与解决
java·服务器·网络·python·golang·php·ai编程
yang_shengy4 小时前
【JavaEE】网络(6)
服务器·网络·http·https
OkeyProxy5 小时前
HTTP、HTTPS和SOCKS5代理協議
网络协议·https·云计算·代理服务器·海外ip代理
zquwei5 小时前
SpringCloudGateway+Nacos注册与转发Netty+WebSocket
java·网络·分布式·后端·websocket·网络协议·spring