☰
W5500 TCP服务器状态机:核心原理与工程陷阱
2026/10/2 1:19:10 网站建设 项目流程

前阵子一个朋友调试W5500做TCP服务器,折腾了两天:板子初始化看起来一切正常,用网线连上路由器,电脑上的网络调试助手一发连接就报错;偶尔能连上,过几分钟也就断了。我让他把Sn_SR的状态值打印出来,结果问题很简单——LISTEN命令发出后,他根本没等状态变成SOCK_LISTEN就开始跑后面的业务逻辑,硬件状态机早就乱套了。

W5500是WIZnet推出的一款集成硬件TCP/IP协议栈的以太网控制器,MCU通过SPI就能让它独立完成TCP/UDP通信。它一共提供8个独立的Socket,每个Socket内部都跑着一个硬件状态机,自动处理SYN、ACK、FIN这些协议细节。对MCU来说,你只需要看清Sn_SR状态寄存器、下发Sn_CR命令,就能操控整个连接生命周期。这篇不是数据手册翻译,而是把TCP服务器模式下从SOCK_CLOSED到SOCK_ESTABLISHED这条主线拆开,把每个状态跃迁背后的时机、命令、中断标志都讲透,顺便把我踩过的一些坑一并交代。

如果你只是照着例程调通了一个TCP客户端,想改成TCP服务器却各种不顺,或者你已经在用W5500做服务器,但对“为什么这个状态会出现在这里”始终有点模糊,这篇文章应该能帮到你。

1. W5500的Socket状态机到底在替你干什么

1.1 硬件状态机和软件协议栈的状态机有什么不同

用过lwIP这类软件协议栈的人都知道,TCP连接管理的每个细节都要MCU参与:收到SYN要手动分配PCB、构造SYN-ACK、维护重传定时器、处理超时。代码量一大,状态一多,bug就跟着来。

W5500的思路完全不同。它把整个TCP/IP协议栈做进了芯片内部,用硬件逻辑实现状态机。MCU这边看不到协议栈内部在干什么,只能通过两个寄存器感知一切:一个是命令寄存器Sn_CR,你给它写命令;一个是状态寄存器Sn_SR,它告诉你当前处于什么状态。这就好比自动驾驶:你只管设定目的地(下发命令)、观察仪表盘(读取状态),中间踩油门、打方向盘、换挡全由车辆自己完成。

这个区别决定了你写代码的方式。软件协议栈里,你要“实现”状态机;W5500里,你只需要“配合”状态机。配合得好,连接管理非常省心;配合不好,状态机和你的认知就会脱节,表现出来就是连接不上、无故断开、接收数据错乱。

1.2 状态寄存器和命令寄存器:一对互相配合的齿轮

先搞清楚Sn_CR和Sn_SR的关系。Sn_CR(Socket n Command Register)是8位命令寄存器,常用命令包括:

命令值用途
OPEN0x01打开Socket,按Sn_MR模式初始化
LISTEN0x02进入TCP服务器监听模式
CONNECT0x04TCP客户端发起连接
DISCON0x08TCP优雅断开,发送FIN
CLOSE0x10直接关闭Socket
SEND0x20发送TX缓冲区数据
RECV0x40确认接收数据处理完毕,释放RX缓冲区

Sn_SR(Socket n Status Register)则是状态寄存器,TCP模式下的状态值如下:

状态值含义
SOCK_CLOSED0x00Socket已关闭
SOCK_INIT0x13已打开,等待进一步命令
SOCK_LISTEN0x14正在监听
SOCK_SYNRECV0x15已收到SYN,等待ACK
SOCK_ESTABLISHED0x17连接已建立,数据可收发
SOCK_CLOSE_WAIT0x1C收到对端FIN,等待本地关闭

这两个寄存器像一对互相配合的齿轮:你给Sn_CR写一个命令,硬件开始执行,执行过程中Sn_SR会从一个状态变成另一个状态。问题是,这个转化不是瞬时的,SPI写入命令之后到状态寄存器更新之间,存在一个时间窗口。很多人把Sn_CR写成OPEN,紧接着读Sn_SR,发现还是SOCK_CLOSED,就以为命令没生效,又写了一遍——这才是真正的Bug根源。

1.3 与TCP服务器场景强相关的几个状态

TCP服务器模式,核心关注这6个状态:SOCK_CLOSED是起点也是终点;SOCK_INIT是OPEN成功后的中间态,很多新手以为到了这里就能收数据,其实还远着;SOCK_LISTEN是真正的“服务器就绪”状态;SOCK_SYNRECV是握手过程中一闪而过的过渡态;SOCK_ESTABLISHED是数据收发的核心工作态;SOCK_CLOSE_WAIT则是对端断开后必须及时处理的半关闭状态。

2. 服务器初始化链路:从SOCK_CLOSED到SOCK_LISTEN的每一小步

2.1 OPEN命令之前:模式、端口和基础网络参数的设置顺序

服务器初始化的第一步,不是发OPEN。很多人上来就写Sn_MR、Sn_PORT、Sn_CR,结果芯片始终进不了SOCK_INIT。原因往往是:基础网络参数根本没配。

W5500的通用寄存器里,SHAR(MAC地址,偏移0x0009)、SIPR(本机IP,偏移0x000F)、GAR(网关地址,偏移0x0015)、SUBR(子网掩码,偏移0x001B)这些是网络通信的地基。状态机本身不检查这些值是否配置过,但TCP报文能不能发出去、能不能收回来,全靠它们。如果MAC地址是全零、IP没设置,即使Socket状态机跑到了SOCK_ESTABLISHED,数据也交换不了。

基础参数确认之后,再进行Socket相关配置,推荐顺序是:

  1. 写Sn_MR为TCP模式(0x01)。
  2. 写Sn_PORT,设置服务器监听端口。注意Sn_PORT是16位寄存器,需要按高低字节分别写入。
  3. 写Sn_CR为OPEN。
  4. 轮询等待Sn_SR变为SOCK_INIT。

Sn_MR要写在OPEN之前,因为OPEN命令执行时硬件会读取当前模式并初始化相关内部逻辑。如果先把Socket打开,再改模式,模式不会生效,Socket可能以错误的协议类型运行。

2.2 OPEN到SOCK_INIT:命令的“异步生效”问题

写完OPEN命令之后,Socket从SOCK_CLOSED向SOCK_INIT迁移。这个迁移过程很快,但毕竟不是瞬间完成。很多示例代码里会给一个短延时或者直接死等,背后的逻辑都一样:必须确认状态到位,再执行后续操作。

等待状态的代码,要注意两点。第一,不能不加保护地死循环等待,万一SPI通信异常或者芯片没工作,系统就卡死了。第二,等待过程中要检查Sn_IR中的TIMEOUT标志,一旦置位说明这次命令执行失败,赶紧退出重试,而不是无限等下去。

uint8_t w5500_tcp_server_init(uint8_t sn, uint16_t port) { uint8_t timeout = 0; // 关闭残留连接,确保从CLOSED开始 w5500_write_reg(sn, Sn_CR, Sn_CR_CLOSE); delay_ms(10); // 配置TCP模式与本地端口 w5500_write_reg(sn, Sn_MR, Sn_MR_TCP); // 0x01 = TCP w5500_write_reg(sn, Sn_PORT1, port >> 8); w5500_write_reg(sn, Sn_PORT0, port & 0xFF); // OPEN命令 w5500_write_reg(sn, Sn_CR, Sn_CR_OPEN); // 等待进入SOCK_INIT,带超时 while (w5500_read_reg(sn, Sn_SR) != SOCK_INIT) { if (w5500_read_reg(sn, Sn_IR) & Sn_IR_TIMEOUT) { return W5500_TIMEOUT; } if (++timeout > 200) { return W5500_ERROR; } delay_ms(1); } return W5500_OK; }

我见过一些项目为了省事,OPEN之后直接delay几个毫秒再发LISTEN,大多数时候也能跑通,但偶然会在上电瞬间出问题。原因就是延时长度取决于芯片内部状态和SPI时钟,存在不确定性。轮询状态才是可靠做法,延时防的还是那个“状态未到位”的窗口。

2.3 LISTEN之后:硬件开始监听的那一刻发生了什么

确认到SOCK_INIT后,就可以写LISTEN命令,让Socket进入监听态。LISTEN命令同样有异步生效的问题,所以初始化函数后半段也要等Sn_SR变成SOCK_LISTEN。

LISTEN命令让硬件把Socket挂到指定端口的监听队列上,之后的SYN包会被协议栈自动识别、响应。此时程序的主循环可以安心干其他事情,不再需要持续盯状态机,只需要周期性检查Sn_IR和Sn_SR。

服务器初始化完成后,如果Sn_SR一直停在SOCK_LISTEN上,说明没有任何客户端发起连接,这是完全正常的状态。很多人刚开始做服务器,看到状态不是SOCK_ESTABLISHED就紧张,其实LISTEN就是服务器的空闲态,只要客户端一握手,状态自己就会跳走。

3. 三次握手在W5500内部的完整演绎

3.1 收到SYN:硬件代收SYN-ACK

TCP三次握手在W5500里是这样发生的:客户端发出SYN包,W5500的硬件协议栈收到后,自动构造并回复SYN-ACK。这一瞬间,Socket状态从SOCK_LISTEN迁移到SOCK_SYNRECV。整个过程MCU完全不知情,也不需要知情。

很多第一次接触W5500的人在这个阶段会疑惑:我写的服务器代码里,根本没有处理SYN包的代码,为什么连接能建立?因为协议处理电路已经帮你干完了。你写服务器逻辑,关心的不是“怎么回SYN-ACK”,而是“什么时候该知道连接已经建立”。这就是硬件协议栈和软件协议栈在编程模型上的核心差异。

3.2 SOCK_SYNRECV:一个可能被你错过的过渡状态

SOCK_SYNRECV这个状态,在正常网络环境下停留时间极短——客户端收到SYN-ACK后立即回ACK,状态马上跳到SOCK_ESTABLISHED。毫秒级甚至微秒级的时间内,程序几乎不可能在主循环里读到它。

但这不是说它不重要。如果网络质量差、或者对端TCP栈处理慢,你可能在某次轮询时看到这个状态。看到SOCK_SYNRECV本身不用慌,它说明握手进行到一半,对端的ACK还没到。问题在于,如果你在SOCK_SYNRECV状态下做了“非预期”的操作,比如直接发CLOSE命令,就可能打断本来正常的握手流程。正确做法是:不干预,继续等待状态跳到SOCK_ESTABLISHED,或者等待超时标志。

3.3 收到ACK:SOCK_ESTABLISHED与CON中断的到来

客户端ACK到达后,硬件状态机把Sn_SR更新为SOCK_ESTABLISHED,同时置位Sn_IR中的CON标志(bit4)。CON是“Connection Established”的缩写,是这个场景下最重要的中断标志。

这里要强调一个新手容易犯的错:Sn_IR是写1清除的寄存器。也就是说,你处理完事件后,要向对应位写1,而不是写0,才能把标志清零。很多从其他芯片转过来的人习惯写0清除,结果标志清不掉,导致每次都重复处理同一个事件。

// 清除CON标志的正确写法 w5500_write_reg(sn, Sn_IR, Sn_IR_CON);

CON标志置位,意味着连接正式可用,此时你才能进行数据收发。TCP服务器场景中,这也是开启业务逻辑的信号灯。

3.4 握手异常与SYN-ACK重传

如果客户端收到SYN-ACK后一直不回复ACK,W5500会重发SYN-ACK。重传机制遵循TCP协议默认行为:首次约200ms后重试,之后指数退避,共重试7次,总超时约1.4秒。超时后,Socket状态从SOCK_SYNRECV直接回到SOCK_CLOSED,同时置位Sn_IR的TIMEOUT标志。

这个超时行为在调试网络时很有用。如果你看到Socket从SOCK_LISTEN跳到了SOCK_SYNRECV,然后又回到SOCK_CLOSED,基本可以断定:客户端能发出SYN,但SYN-ACK发不回去,或者ACK回不来。接下来去查路由器端口映射、防火墙规则、对端TCP栈配置,比瞎猜效率高得多。

另外注意一点:SOCK_LISTEN状态本身不会因为长时间没人连接而超时。监听是无限期的,只有握手过程或数据收发过程的ACK等待才是有限的。所以服务器初始化一次,放在那里几个月不连接也没问题。

4. SOCK_ESTABLISHED之后的事件驱动与数据收发

4.1 选择轮询还是中断:两种事件模型的实际取舍

连接建立后,剩下的事情就是数据收发。W5500有两个事件模型可选:轮询和中断。

轮询模型最简单:主循环周期读Sn_IR和Sn_SR,有事件就处理,没事件就继续循环。优点是代码直观、不会漏事件(只要循环够快);缺点是MCU要不停跑SPI读取,占用一定CPU时间。大多数物联网设备的主循环本来就要查传感器、跑协议,轮询开销可以接受。

中断模型把W5500的INT引脚接到MCU外部中断引脚。任何一个Socket发生事件(数据到达、连接建立、断开、超时、发送完成),INT引脚都会拉低。MCU在中断服务函数里读通用IR寄存器、Sn_IR寄存器,确定是哪个Socket发生了什么事件。

我的建议是:中断里只置标志位,主循环里做具体处理。SPI访问本身有时序要求,如果在中断函数里做完整的数据读取,很容易打断其他任务,而且嵌套中断风险很高。置一个“该Socket有待处理事件”的标志,回到主循环再处理,系统会稳定得多。

4.2 接收数据的标准流程与RECV命令的意义

W5500接收数据有一个“标准流程”,少一步都会出问题:

  1. 轮询或中断发现Sn_IR的RECV标志(bit2)置位。
  2. 读取Sn_RX_RSR,得到接收缓冲区中可读的字节数。
  3. 如果可读字节数大于0,从Sn_RX_FIFOR读取对应长度的数据。
  4. 调用RECV命令,告诉硬件“这些数据处理完了,缓冲区可以释放”。
  5. 清除RECV标志。

其中最关键的是第4步。RECV命令的本质是更新硬件内部的读指针,把已经读走的缓冲空间标记为可覆盖。如果只读数据不发RECV,硬件会以为缓冲区还是满的,新到的数据要么无处存放,要么覆盖未读区域,最终导致数据错乱。

void w5500_tcp_process_recv(uint8_t sn, uint8_t *buf, uint16_t buf_size) { uint16_t len = 0; len = w5500_read_sn_rx_rsr(sn); if (len == 0) { // 标志置位但无数据,属于异常情况,直接清标志返回 w5500_write_reg(sn, Sn_IR, Sn_IR_RECV); return; } if (len > buf_size) { len = buf_size; // 防止溢出 } w5500_read_sn_rx_fifo(sn, buf, len); // 从RX FIFO读数据 w5500_write_reg(sn, Sn_CR, Sn_CR_RECV); // 释放接收缓冲区 w5500_write_reg(sn, Sn_IR, Sn_IR_RECV); // 清除RECV标志 }

注意一个细节:Sn_RX_RSR是16位寄存器,返回的是当前可读的总字节数。如果应用层一包数据最大1KB,而缓冲区里累积了3KB,单纯读一次可能只拿回1KB。循环处理时,每次读完发RECV后重新读RSR,直到RSR为0,这样才不会漏数据。

4.3 发送数据的标准流程与SEND命令的分包陷阱

发送方向看似简单,却藏着一个分包陷阱。标准流程是:

  1. 读取Sn_TX_FSR,确认发送缓冲区空闲空间足够。
  2. 把待发送数据写入Sn_TX_FIFOR。
  3. 下发SEND命令。
  4. 等待SEND_OK标志(bit0)置位。

分包陷阱在于:SEND命令发送的是TX缓冲区中“从当前写指针到当前读指针”之间的全部数据。如果程序连续两次调用发送函数,每次都写入了部分数据,但只在最后发了一次SEND命令,那么两次写入的数据会被合并成一个TCP包发出去。

这本身不一定是坏事,TCP本来就是一个字节流协议,合并发送甚至能提高网络利用率。但如果你在应用层设计的是“请求-响应”式的消息协议,每个写操作对应一个语义完整的消息,那么不恰当的合并会导致对端无法按消息边界解析数据。

解决方案很简单:每写完一组完整业务数据,立即执行SEND命令并等待SEND_OK,再写下一组数据。这样每个业务消息单独打包发送,对端解析时严格按消息边界处理,就不会出现粘包问题。

5. 断开路径上的状态陷阱

5.1 对端先FIN:SOCK_CLOSE_WAIT和DISCON中断的处理

W5500做服务器时,最常见的连接泄漏场景是对端主动断开连接,程序没有正确处理。

对端调用close()发送FIN后,W5500硬件会自动回ACK,Socket状态从SOCK_ESTABLISHED进入SOCK_CLOSE_WAIT(0x1C),同时置位Sn_IR中的DISCON标志(bit3)。这个状态下,连接已经处于半关闭状态:对端不再发数据,但本地还可以尝试发送(虽然通常没有意义)。

程序需要做的,是立即响应DISCON事件,调用DISCON命令,让芯片发送FIN并完成剩余关闭流程,将状态推进回SOCK_CLOSED。

问题出在一部分代码只在SOCK_ESTABLISHED状态下检查数据事件,忽略了DISCON标志。于是Socket一直停留在SOCK_CLOSE_WAIT,既不能收发数据,也不再响应同一个端口上的新连接。用一段时间后,8个Socket全被耗尽,设备只能重启。

// 在事件检测中,必须覆盖断开路径 if (sn_ir & Sn_IR_DISCON) { w5500_write_reg(sn, Sn_IR, Sn_IR_DISCON); w5500_write_reg(sn, Sn_CR, Sn_CR_DISCON); // 主动发送FIN // 等待状态回到SOCK_CLOSED,然后重新初始化监听 w5500_wait_closed(sn); w5500_tcp_server_init(sn, port); }

5.2 TIMEOUT超时:硬件状态机也会翻车

W5500的TIMEOUT标志(bit1)不只在握手阶段出现,数据收发阶段也可能触发。具体来说,任何依赖ACK确认的操作——包括发送数据的ACK等待——如果在1.4秒左右没有得到确认,都会触发TIMEOUT,Socket被硬件强制复位到SOCK_CLOSED。

触发超时的常见场景有:对端突然断电、网线断开、中间网络设备丢弃了数据包。程序检测到TIMEOUT后,不要尝试继续在旧连接上收发数据,应该立即执行CLOSE命令,然后重新走“模式+端口+OPEN+LISTEN”流程。

另外,W5500本身没有TCP Keep-Alive机制。TCP连接建立后,如果双方都长时间不发数据,中间路由器可能会回收连接映射,对端也可能早已崩溃,但W5500这边的状态机仍然停在SOCK_ESTABLISHED。这时候你的程序发数据,可能先收到ICMP端口不可达,也可能直接超时。如果业务需要保持长连接,建议应用层定时使用SEND_KEEP命令(0x22)发送空TCP包,或者周期性地发送应用层心跳数据。

5.3 主动断开与复用Socket的正确姿势

服务器主动断开连接,一般是业务上的“会话结束”。直接写CLOSE命令虽然能立刻释放Socket,但不会发送FIN,对端会以为自己还连着,容易产生半开连接。正确姿势是:

  1. 确保TX缓冲区中的待发数据已经发送完成(等待SEND_OK)。
  2. 下发DISCON命令,走优雅断开流程。
  3. 等待状态回到SOCK_CLOSED。
  4. 重新初始化Socket,再次进入SOCK_LISTEN。

DISCON之后,W5500会走FIN_WAIT、TIME_WAIT等内部状态,最终回到CLOSED。这个过程中Sn_SR可能依次出现SOCK_FIN_WAIT(0x18)、SOCK_TIME_WAIT(0x1B)等值,程序只需等待最终变成SOCK_CLOSED即可,不需要干预中间过程。

一个实用技巧:每次连接关闭后,不一定要立刻重新初始化,可以根据业务需要延迟几秒再重新监听,避免对端处于TIME_WAIT时重复连接导致端口冲突。

6. 直接可用的TCP服务器状态机骨架

6.1 初始化函数与主循环的代码骨架

综合上面的分析,我给出一个可以直接参考的TCP服务器状态机骨架。它包含初始化、事件处理和异常恢复三部分,适合大多数嵌入式平台移植。

void w5500_tcp_server_task(uint8_t sn, uint16_t port, uint8_t *rx_buf, uint16_t rx_buf_size) { static enum { TCP_SERVER_IDLE, TCP_SERVER_LISTEN } server_state = TCP_SERVER_IDLE; uint8_t sn_ir = 0; uint8_t sn_sr = 0; // 如果服务器还没启动,先初始化 if (server_state == TCP_SERVER_IDLE) { if (w5500_tcp_server_init(sn, port) == W5500_OK) { server_state = TCP_SERVER_LISTEN; } return; } sn_ir = w5500_read_reg(sn, Sn_IR); sn_sr = w5500_read_reg(sn, Sn_SR); switch (sn_sr) { case SOCK_LISTEN: if (sn_ir & Sn_IR_CON) { w5500_write_reg(sn, Sn_IR, Sn_IR_CON); // 连接建立,业务开始 } break; case SOCK_ESTABLISHED: if (sn_ir & Sn_IR_RECV) { w5500_tcp_process_recv(sn, rx_buf, rx_buf_size); } if (sn_ir & Sn_IR_DISCON) { w5500_write_reg(sn, Sn_IR, Sn_IR_DISCON); w5500_write_reg(sn, Sn_CR, Sn_CR_DISCON); server_state = TCP_SERVER_IDLE; // 让任务下次重新监听 } if (sn_ir & Sn_IR_TIMEOUT) { w5500_write_reg(sn, Sn_IR, Sn_IR_TIMEOUT); w5500_write_reg(sn, Sn_CR, Sn_CR_CLOSE); server_state = TCP_SERVER_IDLE; } break; case SOCK_CLOSE_WAIT: // 对端FIN已经到达,主动DISCON完成关闭 w5500_write_reg(sn, Sn_CR, Sn_CR_DISCON); server_state = TCP_SERVER_IDLE; break; case SOCK_CLOSED: // 连接已经关闭,重新初始化监听 server_state = TCP_SERVER_IDLE; break; default: break; } }

这个骨架最核心的设计是“server_state”下驱动重新初始化,而不是在事件分支里直接调用初始化函数。因为初始化函数本身需要等待状态迁移,如果在事件处理中调用,遇到异常可能阻塞主循环。把重连交给任务调度,系统更健壮。

6.2 状态机代码的几个隐蔽坑

第一,等待状态迁移的循环必须带超时。我见过不止一个项目,因为SPI线接触不良导致状态寄存器读不到预期值,程序死等,整机卡死。加一个超时计数器,超时后报错并复位W5500,系统才能自恢复。

第二,中断标志的清除时机要放在事件处理后,而不是事件检测前。如果先清标志再处理数据,处理期间到达的新事件会把标志重新置位,这次不会丢;但如果先清标志、处理时间过长,新事件仍然会置位标志,继续处理没问题。反之,如果后清标志,正好在处理期间又来了同一个事件,清除时会把两个事件一起清掉,等于漏了一次事件。稳妥做法是:读取标志、处理事件、最后清除标志。

第三,Sn_RX_RSR读到的字节数可能比实际一次性读走的要多。一定要循环读,直到RSR归零,并配合RECV命令释放空间。很多“收不全数据”的问题都出在这个循环上。

第四,SEND_OK标志的等待。如果业务上连续发送大量数据,前一次发送还没完成就执行下一次SEND,会覆盖TX缓冲区,造成数据帧损坏。每次写入数据后,应该确认SEND_OK置位后再进行下一轮写入。

6.3 调试状态机时的实用技巧

调试W5500状态机,我自己的习惯是:把Sn_SR的数值映射成字符串,通过串口打印出来。这样状态变化一目了然,比对着十六进制数猜状态高效得多。

const char *w5500_tcp_state_str(uint8_t state) { switch (state) { case SOCK_CLOSED: return "CLOSED"; case SOCK_INIT: return "INIT"; case SOCK_LISTEN: return "LISTEN"; case SOCK_SYNRECV: return "SYNRECV"; case SOCK_ESTABLISHED: return "ESTABLISHED"; case SOCK_CLOSE_WAIT: return "CLOSE_WAIT"; case SOCK_FIN_WAIT: return "FIN_WAIT"; case SOCK_TIME_WAIT: return "TIME_WAIT"; default: return "UNKNOWN"; } }

调试时,通过串口周期性打印当前状态和最近的中断标志。当客户端连接失败时,观察打印内容,基本能定位问题方向:

  • 一直停在LISTEN,没有跳到SYNRECV:SYN包可能没到芯片,查网线、交换机、防火墙。
  • 跳到SYNRECV又回到CLOSED:SYN-ACK发出去了但对端ACK没回来,查路由器或对端TCP栈。
  • 停在CLOSE_WAIT:程序没处理DISCON事件,赶紧检查事件分支。

最后配合Wireshark抓包,看TCP握手的三个报文是否依次出现,能更快锁定问题在网络还是代码侧。

关于缓冲区分配,还有一个经验:W5500的TX/RX缓冲总量各16KB,默认每个Socket分配2KB发送、2KB接收。如果单次交互数据量较大,通过Sn_TXBUF_SIZE和Sn_RXBUF_SIZE可以按0.5KB为单位调大某个Socket的缓冲。高吞吐场景下,发送缓冲区过大会增加丢包重传的代价,接收缓冲区过小则会因为来不及RECV而丢数据。具体分配没有标准答案,根据业务最大报文长度和收发频率综合调整,调试时打印Sn_TX_FSR和Sn_RX_RSR的峰值,逐渐把缓冲调到够用又有余量。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询