全部例程

TCP Server

W5500 其他标签

2025/02/17 更新

本篇文章,我们将详细介绍如何在W5500芯片上面实现TCP通信,只需进行简单的socket编程及寄存器读写,便可轻松实现TCP协议应用。接下来我们通过实战例程,为大家讲解如何使用W5500进行TCP Server模式的数据回环测试。

该例程用到的其他网络协议,例如DHCP,请参考相关章节。有关初始化过程,请参考Network Install章节,这里将不再赘述。

TCP协议简介

TCP (Transmission Control Protocol) 是一种面向连接的、可靠的传输层协议,它用于在网络中可靠地传输数据。TCP 是互联网协议族中的核心协议之一,通常与 IP 协议(Internet Protocol)一起使用,形成套接字通信。

TCP协议特点

  • 面向连接:在传输数据之前,TCP 需要建立一个连接,保证发送方与接收方能够彼此通信。通过三次握手(Three-Way Handshake)过程来建立连接,确保双方的通信是可靠的。
  • 可靠性:TCP 提供可靠的数据传输,确保数据完整并且按顺序到达接收端。如果数据丢失或出错,TCP 会自动重传丢失的数据包。
  • 流量控制:TCP 使用流量控制机制来调节数据的发送速度,防止接收方处理不过来导致数据丢失。常用的流量控制方法是滑动窗口(Sliding Window)。
  • 拥塞控制:TCP 可以动态调整传输速率,以避免网络拥塞。采用算法如慢启动、拥塞避免、快速重传等。
  • 全双工通信:在 TCP 连接建立后,数据可以在两个方向同时进行传输,支持双向通信。
  • 有序数据传输:TCP 会对数据包进行编号,确保数据按顺序传输,即使网络发生延迟,接收端也能按顺序接收到数据。
  • 字节流服务:TCP 传输的数据是字节流,不关心应用层数据的边界,应用层需要自己解析数据边界。

TCP 与 UDP 的区别

  • TCP是可靠的、面向连接的协议,适合需要数据完整性和顺序保证的应用,如网页浏览、文件传输等。
  • UDP(User Datagram Protocol)是无连接、不可靠的协议,适合对时效性要求较高且可以容忍丢包的应用,如视频流、在线游戏等。

TCP应用场景

接下来,我们了解下在W5500上,可以使用TCP协议完成哪些操作及应用呢?

  • 远程监控和数据采集:嵌入式设备通常用于采集传感器数据,并通过以太网连接上传到远程服务器,TCP协议确保数据传输的可靠性和完整性。
  • 设备远程控制:许多嵌入式系统需要通过网络接收控制指令(例如工业自动化中的PLC控制),TCP协议提供了可靠的通信通道。
  • 物联网(IoT):许多物联网设备使用TCP协议与云服务器或其他设备进行通信,传输数据、执行命令等。
  • 嵌入式Web服务器:一些嵌入式设备内置Web服务器(例如路由器、网关、传感器设备等),通过TCP协议提供网页接口给用户进行配置和监控。

使用TCP进行数据交互的流程

TCP 连接建立(三次握手)

在开始传输数据之前,TCP 会通过三次握手建立连接:

  1. 第一次握手:客户端向服务器发送一个带有 SYN 标志的数据包,表示请求建立连接。
  2. 第二次握手:服务器收到 SYN 数据包后,回复一个带有 SYN 和 ACK 标志的数据包,表示同意建立连接。
  3. 第三次握手:客户端收到服务器的 SYN+ACK 后,发送一个带有 ACK 标志的数据包,连接建立完成。


数据交互

TCP 连接断开(四次挥手)

当通信结束时,TCP 需要通过四次挥手来断开连接:

  1. 第一次挥手:客户端发送一个 FIN 数据包,表示数据发送完毕,准备关闭连接。
  2. 第二次挥手:服务器收到 FIN 数据包后,回复一个 ACK 数据包,表示同意关闭连接。
  3. 第三次挥手:服务器发送一个 FIN 数据包,表示数据发送完毕,准备关闭连接。
  4. 第四次挥手:客户端收到服务器的 FIN 数据包后,发送一个 ACK 数据包,连接正式关闭。
Blog Image
TCP 3次握手示意图
Blog Image
TCP 4次挥手示意图

TCP的ACK机制、重传机制和Keepalive机制

TCP的ACK机制

ACK 是 TCP 用于确认已成功接收到数据包的机制。在 TCP 通信中,每个数据包都包含一个序列号,接收方用 ACK 来告诉发送方已经成功收到的字节序列。

  1. ACK 字段:ACK 包含在 TCP 报文头中,表示接收方期望接收的下一个字节的序列号。
  2. 累积确认:TCP 使用累积确认方式,表示接收方已经连续收到所有数据,直到某个序列号为止。
  3. 超时重传:如果发送方在超时时间内未收到 ACK,就会重传该数据包。

TCP 的重传机制

TCP 的重传机制保证了数据的可靠传输。以下是常见的重传机制:

  1. 超时重传:
    • 发送方设置一个定时器,当发送的数据包在规定时间内未收到 ACK,则触发重传。
    • 超时时间是动态调整的,由 TCP 的往返时间(RTT, Round Trip Time)估算得出。
  2. 快速重传:
    • 当接收方发现数据包丢失时,发送重复的 ACK(称为冗余 ACK),提醒发送方某个数据包未到达。
    • 如果发送方连续收到 3 个重复的 ACK,就会立即重传对应的数据包,而不必等待超时。
  3. 选择性重传(Selective Repeat, SACK):
    • 在累积确认的基础上,TCP 还可以通过 SACK 选项告诉发送方哪些特定的块已收到,哪些未收到。
    • 这可以减少不必要的重传,提高效率。

TCP Keepalive 机制

TCP Keepalive 是 TCP 协议的一种可选机制,用于检测长时间空闲的连接是否仍然有效。它的主要作用是:

  1. 维护连接状态:检测对方主机是否仍在线,避免资源被长期占用。
  2. 释放死连接:如果连接已经失效(如网络中断或对方主机崩溃),Keepalive 可以及时释放资源。
  3. 防止中间设备超时关闭连接:一些 NAT、路由器或防火墙可能会在连接长时间不活动时自动关闭,Keepalive 可防止这种情况。

用法:需要在W5500的Sn_KPALVTR寄存器中设置对应socket的Keepalive时间,然后在该socket成功连接服务器后发送一条数据来激活Keepalive。

实现过程

接下来,我们一起来看看如何在W5500上实现TCP服务器模式,监听端口并进行回环测试。

步骤1:开启TCP Keepalive功能(避免出现假连接情况)

在W5500中,KeepAlive的时间单元为5秒,这里我们设置6个单元,则W5500会30秒发送1次KeepAlive报文给服务器进行保活:


/* Enable keepalive,Parameter 2 is the keep alive time, with a unit of 5 seconds */
setSn_KPALVTR(SOCKET_ID, 6); // 30s keepalive

步骤2:在主循环中运行TCP Server回环测试程序


while (1)
{
    loopback_tcps(SOCKET_ID, ethernet_buf, local_port);
}

loopback_tcps()函数的三个传参分别为,SOCKET ID,交互的缓存数组,本地端口号。

示例的本地端口号为:8080。

loopback_tcpc()函数内容如下:


/**
* @brief   tcp server loopback test
* @param   sn:    socket number
* @param   buf:   Data sending and receiving cache
* @param   port:  Listen port
* @return  value for SOCK_ERRORs,return 1:no error
*/
int32_t loopback_tcps(uint8_t sn, uint8_t *buf, uint16_t port)
{
    int32_t  ret;
    uint16_t size = 0, sentsize = 0;

#ifdef _LOOPBACK_DEBUG_
    uint8_t  destip[4];
    uint16_t destport;
#endif

    switch (getSn_SR(sn))
    {
    case SOCK_ESTABLISHED:
        if (getSn_IR(sn) & Sn_IR_CON)
        {
#ifdef _LOOPBACK_DEBUG_
            getSn_DIPR(sn, destip);
            destport = getSn_DPORT(sn);

            printf("%d:Connected - %d.%d.%d.%d : %d\r\n", sn, destip[0], destip[1], destip[2], destip[3], destport);
#endif
#if KEEPALIVE_ENABLE == 1
            // We need to send a packet of data to activate keepalive
            ret = send(sn, (uint8_t *)"", 1); // Data send process
            if (ret < 0)                      // Send Error occurred (sent data length < 0)
            {
                close(sn);                    // socket close
                return ret;
            }
#endif
            setSn_IR(sn, Sn_IR_CON);
        }
        if ((size = getSn_RX_RSR(sn)) > 0) // Don't need to check SOCKERR_BUSY because it doesn't not occur.
        {
            if (size > DATA_BUF_SIZE)
                size = DATA_BUF_SIZE;
            ret = recv(sn, buf, size);

            if (ret <= 0)
                return ret; // check SOCKERR_BUSY & SOCKERR_XXX. For showing the occurrence of SOCKERR_BUSY.
            size      = (uint16_t)ret;
            sentsize  = 0;
            buf[size] = 0x00;
            printf("rece data:%s\r\n", buf);
            while (size != sentsize)
            {
                ret = send(sn, buf + sentsize, size - sentsize);
                if (ret < 0)
                {
                    close(sn);
                    return ret;
                }
                sentsize += ret; // Don't care SOCKERR_BUSY, because it is zero.
            }
        }
        break;
    case SOCK_CLOSE_WAIT:
#ifdef _LOOPBACK_DEBUG_
        printf("%d:CloseWait\r\n", sn);
#endif
        if ((ret = disconnect(sn)) != SOCK_OK)
            return ret;
#ifdef _LOOPBACK_DEBUG_
        printf("%d:Socket Closed\r\n", sn);
#endif
        break;
    case SOCK_INIT:
#ifdef _LOOPBACK_DEBUG_
        printf("%d:Listen, TCP server loopback, port [%d]\r\n", sn, port);
#endif
        if ((ret = listen(sn)) != SOCK_OK)
            return ret;
        break;
    case SOCK_CLOSED:
#ifdef _LOOPBACK_DEBUG_
        printf("%d:TCP server loopback start\r\n", sn);
#endif
        if ((ret = socket(sn, Sn_MR_TCP, port, 0x00)) != sn)
            return ret;
#ifdef _LOOPBACK_DEBUG_
        printf("%d:Socket opened\r\n", sn);
#endif
        break;
    default:
        break;
    }
    return 1;
}

在这个程序中,会运行TCP Server状态机,基于SOCKET不同的状态执行对应的操作,SOCKET的状态变化如下图所示:

Blog Image
  • SOCK_CLOSED:当前SOCKET未打开,配置连接服务器及连接端口号后打开SOCKET,打开成功后SOCKET会进入SOCK_INIT状态。
    在W5500异常断开服务器时,服务器并不知道我们已经掉线了,所以继续使用上一次连接的端口进行连接时会被服务器拒绝连接,所以这里在连接失败后会将连接端口自动加1。如果是在一些特定的场景下,服务器只允许客户端使用固定端口连接,这里就不能使用连接端口自动加1的操作。
  • SOCK_INIT:SOCKET打开成功,开始监听端口,当有客户端进行连接时,SOCKET状态改为SOCK_ESTABLISHED。
  • SOCK_ESTABLISHED:首先清除连接成功中断,并发送1包数据激活KeepAlive,然后读取Sn_RX_RSR(空闲接收缓存寄存器)寄存器值,当收到服务器数据时,Sn_RX_RSR寄存器的值会大于0,此时我们将接收到的数据打印并将数据回环发送。
  • SOCK_CLOSE_WAIT:当客户端主动断开连接时,SOCKET状态改为SOCK_CLOSE_WAIT状态,这是一个半关闭状态,可以进行关闭前最后的数据传输。使用disconnect()函数彻底断开连接时,SOCKET状态将改为SOCK_CLOSED状态。

运行结果

请注意:

测试实例需要PC端和W5500处于同一网段。

烧录例程运行后,首先进行了PHY链路检测,然后是通过DHCP获取网络地址并打印网络地址信息,最后则是进行TCP数据回环测试,当客户端未连接时,会一直监听,等待客户端连接。

Blog Image

接下来我们打开SocketTester网络调试工具,设置为TCP Client模式,输入W5500的IP地址和端口后进行连接,然后就能看到W5500打印客户端连接信息了,最后用SocketTester向W5500发送数据进行回环测试

Blog Image

总结

本文介绍在 W5500 芯片上实现 TCP 服务器模式进行数据回环测试的方法。先回顾 TCP 协议相关知识,接着展示实现过程,包括开启 Keepalive 功能,在主循环运行测试程序。程序依据 SOCKET 不同状态执行操作,状态从关闭、初始化、监听,到连接建立、关闭等待。烧录例程后经 PHY 链路检测、获取网络地址,借助网络调试工具完成测试。

下一篇将讲解在该芯片上实现 UDP 通信及数据回环测试,介绍 UDP 相关原理和实现步骤。敬请期待!

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板