简介:STC单片机+W5500模块服务端模式例程,面向嵌入式开发者,展示如何在STC平台上通过W5500以太网控制器实现TCP/UDP服务端程序,并采用模拟SPI方式解决硬件SPI资源不足的问题。例程包内含12个文件,主要包括C源码、头文件、Keil工程配置文件及备份文件,压缩包大小仅44KB,结构紧凑便于移植学习,已有630人学习/下载。包内提供了完整的服务端初始化流程,包括IP配置、端口监听、中断处理和数据收发函数;同时,模拟SPI实现可帮助开发者理解在缺少SPI外设时如何通过GPIO模拟时序完成与W5500的可靠通信。对于希望将STC单片机接入以太网、实现HTTP或FTP服务器的项目,这份例程提供了可参考的工程框架与排错思路,适合具备一定单片机基础的开发者借鉴。
1. 把 STC 的普通 IO 拉成 W5500 要的 SPI:什么时候必须上模拟 SPI
一块 W5500 模块,一片 STC 单片机,两者之间不接硬件 SPI 引脚,而是用四根普通 GPIO 一位一位把时钟和数据“捅”出来——这不是偷懒,是很多 STC 项目里唯一现实的接法。STC 这一系里带硬件 SPI 的型号不算少,但引脚往往和下载口、外部中断、串口复用,真正留给以太网模块的常常只剩几个 IO。W5500 的 SPI 从机接口偏偏对时序非常宽容,SCK 不连续、频率低都能正常工作,于是软件模拟 SPI 反而成为稳定且通用的方案。这篇文章把 STC 单片机 + W5500 模块的服务端模式例程拆开讲:模拟 SPI 的帧结构、Socket 状态机、最小代码骨架、调试路径,以及从例程搬到真实工程时要处理的细节。目标是让一块 STC89C52 或者 STC15 系列单片机,接上裸 W5500 模块,就能被局域网内的 PC 直接 TCP 连接上来回收发数据。
2. W5500 服务端模式的底层准备:帧结构、寄存器与 Socket 状态机
2.1 W5500 的 SPI 帧:2 字节地址、1 字节控制,然后是数据
W5500 的 SPI 访问方式和 W25Qxx 这类 SPI Flash 不一样。它每一帧都由固定的三段组成:16 位地址、8 位控制字节、N 字节数据。地址不是某个寄存器内部的位偏移,而是整个芯片统一编址的空间,靠控制字节里的“块选择”字段决定这次访问落在通用寄存器、某个 Socket 的寄存器、还是 TX/RX 缓冲区。
控制字节是理解 W5500 的关键。bit7~bit5 选块,bit4 决定读写方向,bit3~bit2 是数据长度模式。工程里最常用的是可变数据长度模式,也就是 CS 拉低多久,数据段就传多久,收完最后一字节再把 CS 拉高,一帧结束。
| bit7~bit5:块选择 | bit4:读写 | bit3~bit2:长度模式 | bit1~bit0 |
|---|---|---|---|
| 000=通用寄存器,001=Socket0 寄存器,010=Socket1 寄存器,011=Socket2,100=Socket3,101=TX 缓冲,110=RX 缓冲 | 1=读,0=写 | 00=可变数据长度(CS 控制帧长),01=固定数据长度(数据前带 2 字节长度) | 保留,写 0 |
组装帧头的代码在模拟 SPI 里就三行:
void w5500_frame_head(unsigned char block, unsigned int addr, unsigned char rw) { spi_write_byte((unsigned char)(addr >> 8)); // 地址高 8 位先发 spi_write_byte((unsigned char)(addr & 0xFF)); // 再发低 8 位 spi_write_byte((block << 5) | (rw << 4)); // 控制字节:块 + 读写方向 }这里rw传 1 表示读,传 0 表示写。W5500 是 MSB first,地址高位在前,控制字节里的块选择也要左移到位。固定数据长度模式在控制字节后还要再跟 2 字节长度,例程用不上,不需要管。
2.2 服务端模式的 Socket 状态机:OPEN、LISTEN、ESTABLISHED
服务端模式的本质,是把 W5500 的某个 Socket 配成 TCP,然后执行 OPEN 打开端口,LISTEN 进入监听,等待客户端的三次握手完成,状态跳到 ESTABLISHED 之后才能收发数据。这些动作不是调库函数,而是往 Sn_CR 写命令码,再读 Sn_SR 确认状态。
Sn_CR 的每个命令执行完后会被硬件自动清零,所以标准写法是写完命令后轮询它回到 0,再继续下一步。Sn_SR 是只读状态,不会自动跳变,要用程序轮询。
| Sn_CR 命令码 | 命令 | Sn_SR 状态值 | 状态含义 |
|---|---|---|---|
| 0x01 | OPEN | 0x00 | SOCK_CLOSED |
| 0x02 | LISTEN | 0x13 | SOCK_INIT |
| 0x08 | DISCON | 0x14 | SOCK_LISTEN |
| 0x10 | CLOSE | 0x17 | SOCK_ESTABLISHED |
| 0x20 | SEND | 0x1C | SOCK_CLOSE_WAIT |
| 0x40 | RECV | — | — |
很多第一次写服务端的人,会在客户端断开后卡住,就是因为只处理了 ESTABLISHED 一个状态。客户端正常断开时 Socket 会先进 CLOSE_WAIT,必须对 W5500 执行 DISCON 回到 INIT,再 CLOSE 回到 CLOSED,最后重新 OPEN + LISTEN,否则第二次连接永远建立不起来。
2.3 模拟 SPI 的时序取舍与 STC 引脚处理
模拟 SPI 在代码层面只有四件事:拉高 SCK、拉低 SCK、置 MOSI、读 MISO。W5500 支持 SPI 模式 0 和模式 3,模拟 SPI 固定用模式 0:SCK 空闲为低,上升沿采样数据。模式 3 是空闲为高,如果之前写过别的器件代码,最容易出错的就是这一条。
STC 的 IO 模式要单独处理。STC89C52 这类经典 51 只有准双向口,MISO 读之前必须先写 1,否则读回来全是 0。STC15、STC8 系列可以通过 PnM0/PnM1 寄存器把 SCK、MOSI、CS 配成推挽输出,MISO 保持准双向输入:
// STC15/STC8 系列:P1.0(SCK)、P1.1(MOSI)、P1.3(CS) 配推挽, // P1.2(MISO) 保持准双向输入,读之前写 1 P1M0 = 0x0B; // 对应 bit0、bit1、bit3 推挽 P1M1 = 0x00;模拟 SPI 的速率用 12MHz 的 STC89C52 算一下:一条_nop_()约 1us,一个 bit 至少 4~6us,一个字节约 40~50us,读一次 Sn_SR 的完整帧是 3 字节头加 1 字节数据,约 200us。这意味着每秒可以轮询几千次 Socket 状态,对 TCP 服务端来说完全够用。真正要留意的是别在模拟 SPI 函数里穿插其他耗时操作,否则一帧被拆成两半,W5500 会把后面的数据当成新帧解析。
3. 用模拟 SPI 把 W5500 跑成 TCP 服务端:最小例程的完整骨架
3.1 模拟 SPI 底层读写函数:先保证 VERSIONR 读回 0x04
代码按 Keil C51 的写法给出,重点是寄存器流程。底层就两个函数:写一个字节、读一个字节。写字节时 SCK 拉低后先放 MOSI,再拉高让 W5500 在上升沿采样;读字节时相反,先拉高 SCK 等 MISO 稳定,再采样。
#include <reg52.h> #include <intrins.h> sbit W5500_RST = P1^4; sbit W5500_SCK = P1^0; sbit W5500_MOSI = P1^1; sbit W5500_MISO = P1^2; sbit W5500_CS = P1^3; #define BLOCK_COMMON 0 // 通用寄存器块 #define BLOCK_SOCK0 1 // Socket0 寄存器块 #define BLOCK_TXBUF 5 // TX 缓冲区 #define BLOCK_RXBUF 6 // RX 缓冲区 static void spi_delay(void) { _nop_(); _nop_(); // 12MHz 下约 2us,主频更高时适当加长 } void spi_write_byte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { W5500_SCK = 0; W5500_MOSI = (dat & 0x80) ? 1 : 0; dat <<= 1; spi_delay(); W5500_SCK = 1; // 上升沿,W5500 采样 MOSI spi_delay(); } W5500_SCK = 0; } unsigned char spi_read_byte(void) { unsigned char i, dat = 0; for (i = 0; i < 8; i++) { W5500_SCK = 1; spi_delay(); // 等 MISO 稳定 dat <<= 1; if (W5500_MISO) dat |= 0x01; W5500_SCK = 0; // 下降沿,W5500 切换下一位 spi_delay(); } return dat; }读函数里每次先拉高 SCK 再读,是因为 W5500 在下降沿之后更新 MISO,等到 SCK 回到高电平并延时后,数据线已经稳定。如果读出来全是 0xFF 或者固定 0x00,先检查 MISO 是不是接对,再检查读之前有没有把 MISO 引脚写 1。
基于这两个函数,封装寄存器读写。每次访问都是 CS 拉低、发 3 字节头、传数据、CS 拉高:
void w5500_write_reg(unsigned char block, unsigned int addr, unsigned char dat) { W5500_CS = 0; spi_write_byte((unsigned char)(addr >> 8)); spi_write_byte((unsigned char)(addr & 0xFF)); spi_write_byte(block << 5); // 写命令,可变数据长度 spi_write_byte(dat); W5500_CS = 1; } unsigned char w5500_read_reg(unsigned char block, unsigned int addr) { unsigned char val; W5500_CS = 0; spi_write_byte((unsigned char)(addr >> 8)); spi_write_byte((unsigned char)(addr & 0xFF)); spi_write_byte((block << 5) | 0x10); // 读命令 val = spi_read_byte(); W5500_CS = 1; return val; }一个关键点是 CS 必须在整个帧期间保持低电平,不能在地址、控制字节、数据之间拉高。模拟 SPI 时如果误把每个字节都包一次 CS 拉高拉低,W5500 会把每个字节当成新帧的地址,寄存器读写全部错乱。
调通底层的验证方法很简单,读版本寄存器 VERSIONR,地址是通用寄存器块的 0x0039,W5500 常见返回值是 0x04:
unsigned char ver = w5500_read_reg(BLOCK_COMMON, 0x0039); // ver 应该等于 0x04,否则说明 SPI 时序、接线或复位有问题3.2 网络参数与 Socket 初始化:MAC、IP、端口一次配好
W5500 上电后要先把网关、子网掩码、MAC、本机 IP 写进通用寄存器。这些寄存器都是多字节,全部用可变数据长度模式一帧写完。注意 W5500 是大端字节序,IP 地址的 192 要放在高字节。
void w5500_net_init(void) { unsigned char mac[6] = {0x00, 0x08, 0xDC, 0x01, 0x02, 0x03}; unsigned char ip[4] = {192, 168, 1, 200}; unsigned char gw[4] = {192, 168, 1, 1}; unsigned char mask[4] = {255, 255, 255, 0}; w5500_write_burst(BLOCK_COMMON, 0x0001, gw, 4); // GAR w5500_write_burst(BLOCK_COMMON, 0x0005, mask, 4); // SUBR w5500_write_burst(BLOCK_COMMON, 0x0009, mac, 6); // SHAR w5500_write_burst(BLOCK_COMMON, 0x000F, ip, 4); // SIPR }多字节 Burst 写函数就是把单字节写循环套进同一个 CS 帧里,地址只发一次:
void w5500_write_burst(unsigned char block, unsigned int addr, unsigned char *buf, unsigned int len) { unsigned int i; W5500_CS = 0; spi_write_byte((unsigned char)(addr >> 8)); spi_write_byte((unsigned char)(addr & 0xFF)); spi_write_byte(block << 5); for (i = 0; i < len; i++) { spi_write_byte(buf[i]); } W5500_CS = 1; }Socket0 的 TCP 服务端初始化顺序是固定的:先写 Sn_MR 为 TCP 模式,再写本地端口 Sn_PORT,然后写 Sn_CR 执行 OPEN,等 Sn_CR 清 0,再等 Sn_SR 变成 SOCK_INIT,最后写 LISTEN 命令,等状态变成 SOCK_LISTEN。端口号的字节序同样是大端,5000 要拆成高字节 0x13、低字节 0x88。
void tcp_server_start(unsigned char sock, unsigned int port) { w5500_write_reg(BLOCK_SOCK0 + sock, 0x0000, 0x01); // Sn_MR = TCP w5500_write_reg(BLOCK_SOCK0 + sock, 0x0004, (unsigned char)(port >> 8)); w5500_write_reg(BLOCK_SOCK0 + sock, 0x0005, (unsigned char)(port & 0xFF)); w5500_write_reg(BLOCK_SOCK0 + sock, 0x0001, 0x01); // Sn_CR = OPEN while (w5500_read_reg(BLOCK_SOCK0 + sock, 0x0001) != 0); while (w5500_read_reg(BLOCK_SOCK0 + sock, 0x0003) != 0x13); // SOCK_INIT w5500_write_reg(BLOCK_SOCK0 + sock, 0x0001, 0x02); // Sn_CR = LISTEN while (w5500_read_reg(BLOCK_SOCK0 + sock, 0x0001) != 0); while (w5500_read_reg(BLOCK_SOCK0 + sock, 0x0003) != 0x14); // SOCK_LISTEN }每个命令后等 Sn_CR 清 0 这一步不能省,尤其是 OPEN 后马上写 LISTEN,如果 Sn_CR 还在执行中,LISTEN 命令会被吞掉,Socket 就停在 SOCK_INIT 永远不进监听。
3.3 服务端主循环:Listen、收发与断线重接
进入监听后主循环只需要做三件事:查 Sn_SR 看当前状态,ESTABLISHED 就收发数据,CLOSE_WAIT 就做断开清理,CLOSED 就重新初始化。收发数据的核心是四个 16 位寄存器:发送前查 Sn_TX_FSR 剩余空间,写完数据后更新 Sn_TX_WR;接收前查 Sn_RX_RSR 可读字节数,读完更新 Sn_RX_RD 并执行 RECV 命令释放缓冲。
unsigned int g_tx_wr = 0; unsigned int g_rx_rd = 0; unsigned int tcp_send(unsigned char sock, unsigned char *buf, unsigned int len) { unsigned int free_size; free_size = w5500_read16(BLOCK_SOCK0 + sock, 0x0020); // Sn_TX_FSR if (free_size < len) return 0; w5500_write_burst(BLOCK_TXBUF, g_tx_wr, buf, len); g_tx_wr += len; w5500_write16(BLOCK_SOCK0 + sock, 0x0024, g_tx_wr); // Sn_TX_WR w5500_write_reg(BLOCK_SOCK0 + sock, 0x0001, 0x20); // Sn_CR = SEND while (w5500_read_reg(BLOCK_SOCK0 + sock, 0x0001) != 0); return len; } unsigned int tcp_recv(unsigned char sock, unsigned char *buf, unsigned int len) { unsigned int rx_size; rx_size = w5500_read16(BLOCK_SOCK0 + sock, 0x0026); // Sn_RX_RSR if (rx_size == 0) return 0; if (rx_size > len) rx_size = len; w5500_read_burst(BLOCK_RXBUF, g_rx_rd, buf, rx_size); g_rx_rd += rx_size; w5500_write16(BLOCK_SOCK0 + sock, 0x0028, g_rx_rd); // Sn_RX_RD w5500_write_reg(BLOCK_SOCK0 + sock, 0x0001, 0x40); // Sn_CR = RECV while (w5500_read_reg(BLOCK_SOCK0 + sock, 0x0001) != 0); return rx_size; }w5500_read16和w5500_write16是对 16 位寄存器的封装,先读高字节再读低字节,和 W5500 的大端序保持一致:
unsigned int w5500_read16(unsigned char block, unsigned int addr) { unsigned int val; val = (unsigned int)w5500_read_reg(block, addr) << 8; val |= w5500_read_reg(block, addr + 1); return val; }主循环里把状态分支写清楚。回显是最容易验证的服务端行为:收到什么就原样发回去。
void main(void) { unsigned char status; unsigned char rx_buf[64]; unsigned int n; w5500_reset(); // RST 拉低 50ms,释放后延时 200ms w5500_net_init(); tcp_server_start(0, 5000); while (1) { status = w5500_read_reg(BLOCK_SOCK0, 0x0003); // Sn_SR if (status == 0x17) { // SOCK_ESTABLISHED n = tcp_recv(0, rx_buf, sizeof(rx_buf)); if (n > 0) { tcp_send(0, rx_buf, n); // 回显 } } else if (status == 0x1C) { // SOCK_CLOSE_WAIT w5500_write_reg(BLOCK_SOCK0, 0x0001, 0x08); // DISCON while (w5500_read_reg(BLOCK_SOCK0, 0x0001) != 0); w5500_write_reg(BLOCK_SOCK0, 0x0001, 0x10); // CLOSE while (w5500_read_reg(BLOCK_SOCK0, 0x0001) != 0); while (w5500_read_reg(BLOCK_SOCK0, 0x0003) != 0x00); tcp_server_start(0, 5000); } else if (status == 0x00) { // SOCK_CLOSED tcp_server_start(0, 5000); } } }CLOSE_WAIT 分支里,先 DISCON 再 CLOSE 是标准做法。DISCON 让 Socket 从 CLOSE_WAIT 回到 INIT,CLOSE 再把它彻底关成 CLOSED,之后重新走 OPEN + LISTEN。如果不做 DISCON 直接 CLOSE,部分固件版本会卡在中间态。
3.4 关键参数和常见误用,逐条说清
先看tcp_send。发送前必须查 Sn_TX_FSR,这个寄存器表示当前 Socket 的发送缓冲剩余字节数。W5500 每个 Socket 默认有 2KB TX 缓冲,但数据进来不会自动排队,写入时超出剩余空间就会覆盖未发送的数据,表现出来是粘包、乱码、甚至丢失。空间不足时不能阻塞等待,返回值 0 交给上层稍后重试。
tcp_recv里最容易漏的是 RECV 命令。Sn_RX_RSR 表示接收缓冲里有多少字节可读,读走数据后 W5500 不会自动释放这块缓冲,必须把 Sn_RX_RD 更新到读结束位置,再执行 RECV 命令,缓冲才会回收。漏掉这一步,Sn_RX_RSR 会一直保持旧值或越读越乱。
缓冲区指针是 16 位的,环形边界需要自己处理。g_tx_wr和g_rx_rd增加到接近 0xFFFF 时,要考虑分段读写,不然数据跨越缓冲区末尾时会写到错误位置。工程里常见的做法是先算剩余到缓冲区末尾的长度,不够就拆两次传。
字节序问题集中在端口号、IP 地址和 16 位寄存器上。Sn_PORT 高字节在前,Sn_TX_WR 高字节在前,IP 地址第一字节是高字节。写错过一次端口,表现是服务端监听在错误端口,PC 端连不上但 Socket 状态又确实在 LISTEN。
4. 服务端连不上、收不到、发不出:按链路逐段定位 W5500 的坑
4.1 第一步永远是 VERSIONR:SPI 链路通不通
拿到模块先别配 IP,先把模拟 SPI 跑通。在主函数最开始读一次版本寄存器:
unsigned char ver = w5500_read_reg(BLOCK_COMMON, 0x0039); // 返回 0x04 说明 SPI 通路正常;读回 0xFF 或 0x00 则先查硬件读不到 0x04 的常见原因有三类。第一类是接线,MOSI 和 MISO 接反,或者模块上的电平转换方向不对。第二类是 SPI 模式极性错了,SCK 空闲在高电平,上升沿采样改成下降沿,W5500 把数据采在错误的边沿上。第三类是 CS 时序,帧内字节之间 CS 被拉高过,或者 CS 悬空没有默认上拉。用示波器或者逻辑分析仪看 SCK 和 MOSI 是最直接的排查方式,没有仪器就降低spi_delay的延时,很多高速下不稳定的问题降速后立刻暴露。
4.2 链路层通了但连接不上:查状态机与网络参数
VERSIONR 读回 0x04 之后,下一步是 ping。PC 上和 W5500 配同一个网段,比如模块是 192.168.1.200,PC 就配 192.168.1.10,然后:
ping 192.168.1.200ping 不通,基本是网络参数问题。网关、子网掩码、IP 三个寄存器只要有一个写错,链路层就不通。ping 通了但 TCP 连不上,把焦点转到 Socket 状态上。在 main 循环里把 Sn_SR 读出来看,如果停在 0x13 而不是 0x14,说明 LISTEN 命令没有真正执行成功,最常见是 OPEN 后没等 Sn_CR 清 0 就写了 LISTEN。
还有一种隐蔽情况:Socket 已经进入 ESTABLISHED,但客户端程序没有正常关闭连接就退出了,W5500 在 CLOSE_WAIT 状态一直不处理。新连接进来时它会尝试接入已占用的端口,表现是偶尔能连上、偶尔连不上,或者连接成功后立刻断开。主循环里加 CLOSE_WAIT 分支后这个问题就消失。
现象对照表可以直接拿来当排查清单:
| 现象 | 最可能原因 | 验证手段 |
|---|---|---|
| VERSIONR 读不到 0x04 | 接线错误、SPI 极性不对、CS 被拉高过 | 检查 MOSI/MISO 是否接反,降速重试 |
| 能读寄存器但 ping 不通 | IP/掩码/网关配置错误 | PC 与模块同一网段,核对四个通用寄存器 |
| 能 ping 通但连不上 | Socket 不在 LISTEN 状态 | 读 Sn_SR,停在 0x13 就查 Sn_CR 时序 |
| 连上但收不到数据 | 没执行 RECV 命令释放缓冲 | 看 Sn_RX_RSR 是否一直增长不清零 |
| 收发乱码 | 字节序错误、缓冲区指针越界 | 检查 16 位寄存器高低字节顺序 |
4.3 能连但收发异常:缓冲指针、字节序和中断引脚的误导
连上之后收发不对,优先怀疑三个地方。第一个是缓冲指针。发送流程里必须先写 TX 缓冲,再把新的写指针回写到 Sn_TX_WR,最后发 SEND 命令。有人把 Sn_TX_WR 固定写成 0,每次从缓冲头开始发,数据全乱。
第二个是字节序。调试时在 PC 端发 0x01 0x02 0x03 0x04,单片机收回来如果变成 0x04 0x03 0x02 0x01,那不是 SPI 位序错,而是有人在应用层手动翻了字节序。W5500 的收发缓冲不修改数据内容,TCP 是字节流,收到什么就是什么。
第三个坑在中断引脚。W5500 的 INT 引脚在 Socket 有事件时拉低,很多例程用外部中断触发接收。问题是 TCP 数据可以多段到达,INT 只在第一次到达时拉低一次,后面如果单片机没及时处理、缓冲又没满,INT 不会再次触发,数据就压在缓冲里没人读。服务端模式里最稳的还是主循环轮询 Sn_RX_RSR,INT 只用来做快速唤醒,不要当成唯一的数据到达信号。
5. 把服务端例程接到工程里:分时调度、掉线重连与吞吐验证
5.1 把 Socket 状态机放进主循环 tick,避免阻塞业务
例程的 while(1) 是纯轮询,真实工程里不可能让单片机一直耗在 W5500 上。常见做法是把状态机拆成一个 tick 函数,定时器产生 5ms 时基,主循环在每轮任务间隙调用一次。注意不要在定时器中断里直接跑 W5500 的 SPI 读写,模拟 SPI 一帧要几百微秒,关中断太久会破坏其他实时任务,应该只置一个标志位,主循环里处理。
每秒轮询 200 次对 TCP 服务端足够。数据到达后 Sn_RX_RSR 会保持可读字节数,晚几个毫秒读不会丢,只要别让缓冲溢出就行。W5500 的接收缓冲默认 2KB,轮询间隔大时要估算最坏情况下的数据积压量,必要时调大 Sn_RXBUF_SIZE。缓冲配置寄存器影响的是 W5500 片内 SRAM 划分,和单片机 RAM 大小无关,STC 这类小内存单片机反而适合把缓冲尽量留给 W5500。
5.2 心跳、CLOSE_WAIT 与重连策略
W5500 硬件不实现 TCP keepalive,连接是否还活着靠应用层自己判断。服务端侧的心跳一般做两件事:周期性向已连接的客户端发送自定义心跳包,同时记录最后一次收到客户端数据的时间,超过阈值就主动断开重连。心跳包不要影响业务数据,建议单独用一个端口号或者复用现有连接但靠包头标志区分。
重连策略要带退避。客户端拉网线、路由器断电这些情况,W5500 侧不会立刻感知,Sn_SR 可能长时间停留在 ESTABLISHED。连续心跳超时后,执行 DISCON + CLOSE 回到 CLOSED,再重新 OPEN + LISTEN。如果状态机里不加延迟,异常情况下会陷入“重连失败立刻再重连”的死循环,把模拟 SPI 的带宽全部吃掉。
5.3 用 PC 端 Python 脚本验证收发与吞吐
验证服务端例程最简单的方式是 PC 上跑一段 Python 脚本。先测回显,再测重连:
import socket import time s = socket.create_connection(("192.168.1.200", 5000), timeout=3) print("connected") total = 0 t0 = time.time() for i in range(100): s.sendall(b"ping-%03d" % i) data = s.recv(64) total += len(data) dt = time.time() - t0 print("round-trip avg: %.2f ms" % (dt * 1000 / 100)) s.close() time.sleep(2) s2 = socket.create_connection(("192.168.1.200", 5000), timeout=3) print("reconnect ok") s2.close()往返延迟能稳定在几十毫秒内,说明主循环及时。重连检测要重点看 CLOSE_WAIT 分支是否生效,脚本里 close 之后两秒再连,如果能成功,说明 Socket 状态机正确处理了断开。吞吐方面,模拟 SPI 在 12MHz STC89C52 上能跑到十几 KB/s 量级,大包测试时发送缓冲一旦不够,tcp_send返回 0 的次数会明显增加,这是正常的背压表现,不要靠延长延时硬扛,应该让业务层按返回值做分段发送。
本文还有配套的精品资源,点击获取