socket.h
SF_* socket 标志位(映射到 Sn_MR 高位)2.4 非 TCP 包分片标志 PACK_*2.5 IO 模式宏2.6 枚举 sockint_kind(socket 中断类型位掩码)2.7 枚举 ctlsock_type(ctlsocket() 操作码)2.8 枚举 sockopt_type(setsockopt() / getsockopt() 选项码)2.9 文件内的大段契约性注释3. 函数清单4. 核心逻辑解析4.1 返回值三级语义(> 0 / = 0 / < 0)4.2 void* arg 的类型不安全契约4.3 非 TCP 包分片状态机(PACK_* 三态)4.4 sockint_kind 与寄存器 Sn_IR / Sn_IMR 的位对应4.5 recvfrom() 的 addr/port 有效性契约4.6 WIZnet 与 Berkeley API 的差异(socket.h:45-80)4.7 时序与全局变量读写5. 对外接口5.1 本文件导出5.2 被谁包含(8 处,已 Grep 确认)5.3 各函数的实际调用者汇总5.4 调用方向6. 依赖关系7. 备注与风险点源文件路径:
midware/ethernt/lan/socket.h所属模块:midware/ethernt/lan(W5500 以太网协议栈 — Socket API 契约层) 代码行数:470
1. 文件职责
本文件是 WIZnet 官方 ioLibrary 的 socket.h(文件头标注 V1.0.2 / 2013-10-21 / 作者 MidnightCow,见 socket.h:6、socket.h:9、socket.h:13),把 W5500 的 8 个硬件 socket 封装成一套类 Berkeley 接口。它是纯契约头:470 行中约 300 行是 Doxygen 注释,全部内容只有宏定义、三个枚举类型和 13 个函数原型,没有任何函数体、没有结构体定义、没有任何 extern 全局变量。
在本工程中它是应用层与 W5500 之间的唯一接口层(socket.c 是它唯一的实现者)。数据流方向为:业务层(云端 TCP 客户端、Modbus TCP 从站、DHCP/DNS)→ 本头文件声明的 API → socket.c → w5500.h 的寄存器读写宏 → SPI → W5500 芯片。
1.1 重要澄清:寄存器地址、状态枚举、命令码、结构体都不在本文件内
任务描述中提到的"哈希表寄存器地址、SOCK_* 状态枚举、命令码、ioLibrary 结构体"均不在 socket.h 中定义,它们分布在同目录的另外两个文件里。本文件只是通过 #include "wizchip_conf.h"(socket.h:84)间接获得这些符号。经 Grep 逐个确认,其真实定义位置如下(供交叉阅读):
| 内容类别 | 真实定义位置 | 关键符号(行号) |
|---|---|---|
| W5500 寄存器哈希表地址(含 Sn 块偏移) | midware/ethernt/lan/w5500.h | Sn_CR(N)(441)、Sn_SR(N)(482)、Sn_DIPR(N)(508)、Sn_DPORT(N)(518)、Sn_MSSR(525)、Sn_TOS(535)、Sn_TTL(542)、Sn_TX_FSR(583)、Sn_RX_RSR(618)、Sn_IMR(N)(650);块偏移由 WIZCHIP_SREG_BLOCK(N) << 3 与 _W5500_IO_BASE_ 组合而成 |
socket 状态枚举 SOCK_* | w5500.h | SOCK_CLOSED(1037)、SOCK_INIT(1045)、SOCK_LISTEN(1053)、SOCK_SYNSENT(1062)、SOCK_SYNRECV(1070)、SOCK_ESTABLISHED(1079)、SOCK_FIN_WAIT(1087)、SOCK_CLOSING(1095)、SOCK_TIME_WAIT(1103)、SOCK_CLOSE_WAIT(1111)、SOCK_LAST_ACK(1118)、SOCK_UDP(1126)、SOCK_MACRAW(1136) |
socket 命令码 Sn_CR_* | w5500.h | Sn_CR_OPEN(922)、Sn_CR_LISTEN(933)、Sn_CR_CONNECT(945)、Sn_CR_DISCON(958)、Sn_CR_CLOSE(964)、Sn_CR_SEND(972)、Sn_CR_SEND_MAC(982)、Sn_CR_SEND_KEEP(990)、Sn_CR_RECV(998) |
socket 中断位 Sn_IR_* | w5500.h | Sn_IR_SENDOK(1005)、Sn_IR_TIMEOUT(1011)、Sn_IR_RECV(1017)、Sn_IR_DISCON(1023)、Sn_IR_CON(1029) |
协议模式位 Sn_MR_* | w5500.h | Sn_MR_MULTI(802)、Sn_MR_BCASTB(811)、Sn_MR_ND(821)、Sn_MR_UCASTB(829)、Sn_MR_MACRAW(836)、Sn_MR_UDP(844)、Sn_MR_TCP(850)、Sn_MR_CLOSE(856);别名 Sn_MR_MFEN(869)、Sn_MR_MMB(878)、Sn_MR_MIP6B(886)、Sn_MR_MC(895) |
| ioLibrary 结构体 | wizchip_conf.h | __WIZCHIP(151-195)、wiz_PhyConf_t(299)、wiz_NetInfo_t(324, 类型名 wiz_NetInfo)、wiz_NetTimeout_t(353-357);枚举 ctlwizchip_type(224)、ctlnetwork_type(238)、intr_kind(276)、netmode_type(347) |
| 芯片选型与 socket 数量 | wizchip_conf.h | _WIZCHIP_ 5500(59)、_WIZCHIP_SOCK_NUM_ 8(138,条件 _WIZCHIP_ > 5100) |
结论:阅读本文件时,凡注释里出现的 @ref Sn_MR_*、@ref SOCK_ESTABLISHED、@ref setSn_IR() 等,都是 Doxygen 交叉引用标签,指向 w5500.h/wizchip_conf.h,本文件并不定义它们。这也解释了为什么本文件只有 27 KB 却号称"寄存器"头文件——真正 80 KB 的寄存器定义在 w5500.h。
2. 关键数据结构与宏
2.1 类型与结果码
| 名称 | 类型 | 值 | 说明(行号) |
|---|---|---|---|
SOCKET | 宏(充当类型别名) | uint8_t | 无 typedef,直接 #define SOCKET uint8_t,注释为"legacy driver"兼容(socket.h:86)。名字与 Winsock 的 SOCKET 类型冲突,属官方遗留风险 |
SOCK_OK | 宏 | 1 | 操作成功(socket.h:88) |
SOCK_BUSY | 宏 | 0 | 非阻塞模式下"操作尚未完成"(socket.h:89)。与 SOCK_ERROR 同值 |
SOCK_FATAL | 宏 | -1000 | 致命错误基准(socket.h:90) |
SOCK_ERROR | 宏 | 0 | 错误基准值,后续 SOCKERR_* 全部是它减 N(socket.h:92) |
SOCKFATAL_PACKLEN | 宏 | SOCK_FATAL - 1 = -1001 | 包长非法(socket.h:107) |
2.2 错误码全集
以下 13 个错误码全部以 SOCK_ERROR(0) 为基准递减,落在 -1 ~ -15 区间(socket.h:93-105):
| 宏 | 值 | 含义 |
|---|---|---|
SOCKERR_SOCKNUM | -1 | 非法 socket 号 |
SOCKERR_SOCKOPT | -2 | 非法 socket 选项 |
SOCKERR_SOCKINIT | -3 | socket 未初始化(Sn_SR == SOCK_CLOSED) |
SOCKERR_SOCKCLOSED | -4 | socket 被意外关闭 |
SOCKERR_SOCKMODE | -5 | 该操作不支持当前 socket 模式 |
SOCKERR_SOCKFLAG | -6 | 非法 socket 标志 |
SOCKERR_SOCKSTATUS | -7 | 当前 Sn_SR 不允许该操作 |
SOCKERR_ARG | -10 | 参数非法(注意跳过了 -8、-9,官方留空) |
SOCKERR_PORTZERO | -11 | 端口号为 0 |
SOCKERR_IPINVALID | -12 | IP 地址非法 |
SOCKERR_TIMEOUT | -13 | 超时 |
SOCKERR_DATALEN | -14 | 数据长度为 0 或超过缓冲上限 |
SOCKERR_BUFFER | -15 | socket 缓冲区不足 |
注意 SOCKERR_ARG 直接跳到 -10(socket.h:100),因此不能用"错误码 = -8/-9"或"范围连续性"做判断;正确做法是与符号比较(如 dhcp.c:916 的 if(ret == SOCKERR_TIMEOUT))。
2.3 SF_* socket 标志位(映射到 Sn_MR 高位)
| 名称 | 定义(行号) | 展开后的实际值(本工程 _WIZCHIP_ == 5500) | 说明 |
|---|---|---|---|
SF_ETHER_OWN | (Sn_MR_MFEN)(socket.h:112) | 0x80 | MACRAW 下只收广播/组播/本机包 |
SF_IGMP_VER2 | (Sn_MR_MC)(socket.h:113) | 0x20 | UDP+多播时选 IGMP v2 |
SF_TCP_NODELAY | (Sn_MR_ND)(socket.h:114) | 0x20 | TCP 关闭延迟 ACK |
SF_MULTI_ENABLE | (Sn_MR_MULTI)(socket.h:115) | 0x80 | UDP 多播使能 |
SF_BROAD_BLOCK | (Sn_MR_BCASTB)(socket.h:118) | 0x40 | W5500 专有:屏蔽广播包(受 #if _WIZCHIP_ == 5500 门控,socket.h:117) |
SF_MULTI_BLOCK | (Sn_MR_MMB)(socket.h:119) | 0x20 | W5500 专有:屏蔽组播包 |
SF_IPv6_BLOCK | (Sn_MR_MIP6B)(socket.h:120) | 0x10 | W5500 专有:屏蔽 IPv6 包 |
SF_UNI_BLOCK | (Sn_MR_UCASTB)(socket.h:121) | 0x10 | W5500 专有:UDP+多播下屏蔽单播包 |
SF_IO_NONBLOCK | 0x01(socket.h:124) | 0x01 | 不是 Sn_MR 位,是 socket() 的 flag 参数里的独立控制位,代表非阻塞 IO |
由于 w5500.h 把 Sn_MR_MFEN、Sn_MR_MMB、Sn_MR_MIP6B、Sn_MR_MC 定义为其它位的别名(w5500.h:869/878/886/895),本工程中出现了三组 SF_* 常量值完全重合的情况(见 7 节风险点)。
2.4 非 TCP 包分片标志 PACK_*
| 名称 | 值 | 说明(行号) |
|---|---|---|
PACK_FIRST | 0x80 | 非 TCP 包(UDP/MACRAW):本包刚开始被读取(socket.h:129) |
PACK_REMAINED | 0x01 | 该包还有剩余数据未取完(socket.h:130) |
PACK_COMPLETED | 0x00 | 一整包已读完(socket.h:131) |
三者被拼装进 socket.c 的静态数组 sock_pack_info[_WIZCHIP_SOCK_NUM_](socket.c:60),通过 getsockopt(sn, SO_PACKINFO, ...) 读出。注意头文件 V1.0.2 的修订记录(socket.h:10)就是把这组宏的历史名 SO_REMAINED 改名为 PACK_REMAINED。
2.5 IO 模式宏
| 名称 | 值 | 说明(行号) |
|---|---|---|
SOCK_IO_BLOCK | 0 | 阻塞模式,ctlsocket() 的 CS_SET_IOMODE 参数(socket.h:321) |
SOCK_IO_NONBLOCK | 1 | 非阻塞模式(socket.h:322) |
2.6 枚举 sockint_kind(socket 中断类型位掩码)
定义于 socket.h:333-341,注释明确标注它对应寄存器 Sn_IR / Sn_IMR。与 w5500.h 的 Sn_IR_* 宏逐位一一对应:
| 枚举量 | 值 | 对应 w5500.h 宏(行号) | 含义 |
|---|---|---|---|
SIK_CONNECTED | 1 << 0 = 0x01 | Sn_IR_CON(1029) | 连接建立 |
SIK_DISCONNECTED | 1 << 1 = 0x02 | Sn_IR_DISCON(1023) | 连接断开 |
SIK_RECEIVED | 1 << 2 = 0x04 | Sn_IR_RECV(1017) | 收到数据 |
SIK_TIMEOUT | 1 << 3 = 0x08 | Sn_IR_TIMEOUT(1011) | 超时 |
SIK_SENT | 1 << 4 = 0x10 | Sn_IR_SENDOK(1005) | 发送完成 |
SIK_ALL | 0x1F | 上述五者按位或 | 全部中断(低 5 位) |
2.7 枚举 ctlsock_type(ctlsocket() 操作码)
定义于 socket.h:347-357,无显式赋值,实际序数为 0~7:
| 枚举量 | 序数 | 参数类型(按 socket.h:393-398 的表格) | 含义 |
|---|---|---|---|
CS_SET_IOMODE | 0 | uint8_t* = SOCK_IO_BLOCK/SOCK_IO_NONBLOCK | 设置 IO 模式 |
CS_GET_IOMODE | 1 | uint8_t* | 读 IO 模式 |
CS_GET_MAXTXBUF | 2 | uint16_t*(0~16K) | 读该 socket TX 内存分配上限 |
CS_GET_MAXRXBUF | 3 | uint16_t*(0~16K) | 读该 socket RX 内存分配上限 |
CS_CLR_INTERRUPT | 4 | uint8_t*(sockint_kind) | 清中断(写 Sn_IR) |
CS_GET_INTERRUPT | 5 | uint8_t*(sockint_kind) | 读中断(读 Sn_IR) |
CS_SET_INTMASK | 6 | uint8_t*(sockint_kind) | 设中断屏蔽(写 Sn_IMR) |
CS_GET_INTMASK | 7 | uint8_t*(sockint_kind) | 读中断屏蔽(读 Sn_IMR) |
2.8 枚举 sockopt_type(setsockopt() / getsockopt() 选项码)
定义于 socket.h:364-383,受两层条件编译裁剪。本工程 _WIZCHIP_ == 5500(wizchip_conf.h:59),因此 _WIZCHIP_ != 5100 与 _WIZCHIP_ > 5200 两个条件都成立,SO_KEEPALIVESEND 与 SO_KEEPALIVEAUTO 都被编译进来:
| 枚举量 | 本工程序数 | 条件门控 | 方向 | 参数类型 | 对应寄存器(w5500.h) |
|---|---|---|---|---|---|
SO_FLAG | 0 | 无 | 仅 get | uint8_t* | Sn_MR(get 时返回 & 0xF0 高四位,socket.c:676) |
SO_TTL | 1 | 无 | set/get | uint8_t | Sn_TTL(w5500.h:542) |
SO_TOS | 2 | 无 | set/get | uint8_t | Sn_TOS(w5500.h:535) |
SO_MSS | 3 | 无 | set/get | uint16_t | Sn_MSSR(w5500.h:525) |
SO_DESTIP | 4 | 无 | set/get | uint8_t[4] | Sn_DIPR(w5500.h:508) |
SO_DESTPORT | 5 | 无 | set/get | uint16_t | Sn_DPORT(w5500.h:518) |
SO_KEEPALIVESEND | 6 | #if _WIZCHIP_ != 5100(socket.h:372) | 仅 set | NULL | Sn_CR_SEND_KEEP + Sn_IR_TIMEOUT |
SO_KEEPALIVEAUTO | 7 | #if _WIZCHIP_ > 5200(socket.h:374) | set/get | uint8_t | Sn_KPALVTR(w5500.h:670) |
SO_SENDBUF | 8 | 无 | 仅 get | uint16_t* | Sn_TX_FSR(w5500.h:583) |
SO_RECVBUF | 9 | 无 | 仅 get | uint16_t* | Sn_RX_RSR(w5500.h:618) |
SO_STATUS | 10 | 无 | 仅 get | uint8_t* | Sn_SR(w5500.h:482),返回 SOCK_* 状态 |
SO_REMAINSIZE | 11 | 无 | 仅 get | uint16_t* | TCP 取 Sn_RX_RSR;非 TCP 取 sock_remained_size[sn] |
SO_PACKINFO | 12 | 无 | 仅 get | uint8_t* | 软件状态 sock_pack_info[sn],非寄存器 |
关键约束:这个枚举的序数是 _WIZCHIP_ 的函数。若 socket.h 与 socket.c 编译时看到的 _WIZCHIP_ 不一致(例如某处重复定义了 _WIZCHIP_),SO_SENDBUF 之后的选项会整体错位,静默读写错误的寄存器。本工程只有一处 #define _WIZCHIP_(wizchip_conf.h:59),风险受控。
2.9 文件内的大段契约性注释
socket.h:45-80 是一段 @defgroup WIZnet_socket_APIs 说明,含一张 WIZnet 与 Berkeley API 的对照表。这是理解本工程 socket 使用方式的前提,内容见 4.6 节。
3. 函数清单
本文件只有函数声明,没有函数定义;13 个原型全部是外部链接(无 static),全部由 socket.c 实现(send_u32() 除外,见 7 节)。
| 函数名(原型) | 作用域 | 声明行 | 适用模式 | 说明 |
|---|---|---|---|---|
int8_t socket(uint8_t sn, uint8_t protocol, uint16_t port, uint8_t flag) | 全局(声明) | socket.h:150 | 全部 | 打开并初始化 socket;与 Berkeley 不同,同时完成绑定本地端口。成功返回该 socket 号。失败:SOCKERR_SOCKNUM/SOCKERR_SOCKMODE/SOCKERR_SOCKFLAG |
int8_t close(uint8_t sn) | 全局(声明) | socket.h:162 | 全部 | 关闭 socket,清中断并等待回到 SOCK_CLOSED。失败:SOCKERR_SOCKNUM |
int8_t listen(uint8_t sn) | 全局(声明) | socket.h:175 | 仅 TCP | TCP 被动模式监听并同时接受连接(无独立 accept());成功后进入 SOCK_LISTEN。失败:SOCKERR_SOCKINIT/SOCKERR_SOCKCLOSED |
int8_t connect(uint8_t sn, uint8_t * addr, uint16_t port) | 全局(声明) | socket.h:198 | 仅 TCP 客户端 | 向 addr:port 发起连接;阻塞模式等连接完成,非阻塞模式立即返回 SOCK_BUSY。失败还有 SOCKERR_IPINVALID/SOCKERR_PORTZERO/SOCKERR_TIMEOUT |
int8_t disconnect(uint8_t sn) | 全局(声明) | socket.h:215 | 仅 TCP | 主动向对端发送断开请求。非阻塞模式立即返回 SOCK_BUSY |
int32_t send(uint8_t sn, uint8_t * buf, uint32_t len) | 全局(声明) | socket.h:235 | 仅 TCP | TCP 发送,返回实际写入字节数;不能超过 socket TX 缓冲。失败含 SOCKERR_SOCKSTATUS/SOCKERR_DATALEN/SOCKERR_TIMEOUT/SOCK_BUSY |
int32_t recv(uint8_t sn, uint8_t * buf, uint16_t len) | 全局(声明) | socket.h:257 | 仅 TCP | TCP 接收,返回值是 uint32_t 而 len 是 uint16_t(宽度不对称);阻塞模式会一直等到收满 len。失败含 SOCKERR_SOCKSTATUS/SOCKERR_SOCKMODE/SOCKERR_DATALEN/SOCK_BUSY |
int32_t sendto(uint8_t sn, uint8_t * buf, uint16_t len, uint8_t * addr, uint16_t port) | 全局(声明) | socket.h:285 | UDP / MACRAW | 发送数据报,目的地址与端口由参数给出,即使 socket 已 connect 过也会被本次覆盖。失败含 SOCKERR_IPINVALID/SOCKERR_PORTZERO/SOCKERR_SOCKCLOSED |
int32_t recvfrom(uint8_t sn, uint8_t * buf, uint16_t len, uint8_t * addr, uint16_t *port) | 全局(声明) | socket.h:315 | UDP / MACRAW | 接收数据报,自带包头处理;支持分多次取出一整包,addr/port 仅首包有效,续包用 SO_PACKINFO/SO_REMAINSIZE 判断。MACRAW 下 addr/port 被忽略。失败含 SOCKERR_DATALEN/SOCKERR_SOCKMODE/SOCK_BUSY |
int8_t ctlsocket(uint8_t sn, ctlsock_type cstype, void* arg) | 全局(声明) | socket.h:402 | 全部 | 控制 IO 模式、清/读中断、设/读中断屏蔽,查询收发缓冲上限。第三参类型随 cstype 变化(见 2.7);失败仅 SOCKERR_ARG |
int8_t setsockopt(uint8_t sn, sockopt_type sotype, void* arg) | 全局(声明) | socket.h:430 | 依选项 | 写 TTL/TOS/MSS/目的 IP/目的端口/手工保活/保活自动周期。参数类型随 sotype 变化;失败含 SOCKERR_SOCKNUM/SOCKERR_SOCKMODE/SOCKERR_SOCKOPT/SOCKERR_TIMEOUT |
int8_t getsockopt(uint8_t sn, sockopt_type sotype, void* arg) | 全局(声明) | socket.h:464 | 依选项 | 读 FLAG/TOS/MSS/目的 IP/目的端口/保活周期/TX 空闲/已收字节/状态/剩余包长/包分片信息。失败含 SOCKERR_SOCKNUM/SOCKERR_SOCKOPT/SOCKERR_SOCKMODE |
int32_t send_u32(uint8_t sn, uint8_t * buf, uint32_t len) | 全局(声明) | socket.h:467 | — | 仅声明,全工程无实现也无调用(Grep send_u32 全仓仅命中 socket.h:467 一处),属遗留悬空接口;且与 send() 签名完全相同,属重复声明 |
4. 核心逻辑解析
本文件无代码逻辑,以下解析的是它所约定的契约,以及这些契约在实现层 socket.c 中的实际落点。
4.1 返回值三级语义(> 0 / = 0 / < 0)
- 成功:
socket()返回 socket 号(1 字节正数);send/recv/sendto/recvfrom返回实际字节数;其余返回SOCK_OK(1)。 - 未完成:
SOCK_BUSY(0),仅非阻塞模式出现。 - 失败:负值,区间
SOCKERR_SOCKNUM(-1) ~ SOCKERR_BUFFER(-15),致命时为SOCKFATAL_PACKLEN(-1001)。
结构性问题:SOCK_BUSY == SOCK_ERROR == 0。调用方若只做 ret > 0 / ret < 0 判断,会把"忙"与"错误基准值"混同。本工程正踩在此处——g_stNetSysInfo.s8SocketOk 用 SOCK_OK / SOCK_ERROR 双态表示连接状态,判定点见 midware/ethernt/ethernet.c:698、ethernet.c:715、ethernet.c:1007、ethernet.c:1054、ethernet.c:1301、ethernet.c:1310、midware/ethernt/wireless/wireless_driver.c:150、net/ems.c:787、net/ems.c:1886、net/ems.c:1890。
4.2 void* arg 的类型不安全契约
ctlsocket() / setsockopt() / getsockopt() 的第三参是 void*,实际写入宽度完全由 cstype/sotype 决定:SO_TTL 用 uint8_t*、SO_MSS 用 uint16_t*、SO_DESTIP 用 uint8_t[4]、SO_KEEPALIVESEND 忽略该参数。头文件用三张表格把映射写明(socket.h:393-398、socket.h:412-421、socket.h:440-452),编译器无法校验,类型错误不会报错只会静默写越界或截断。
4.3 非 TCP 包分片状态机(PACK_* 三态)
UDP / MACRAW 是数据报语义,但 W5500 的 RX 缓冲可能装不下一整包,故 recvfrom() 允许"分次取出"。状态转移依据是 socket.c 的静态数组 sock_remained_size[](socket.c:59)与 sock_pack_info[](socket.c:60):
- 首包:
sock_remained_size[sn] == 0时,recvfrom()从 W5500 读包头,设置sock_pack_info[sn] = PACK_FIRST,并回填*addr/*port。 - 取走
pack_len后执行sock_remained_size[sn] -= pack_len(socket.c:566);若仍非 0,则sock_pack_info[sn] |= PACK_REMAINED(socket.c:567-568)。 - 取完时
sock_pack_info[sn]复位为PACK_COMPLETED(0),下一次recvfrom()重新按首包处理。
对应的读接口是 getsockopt(sn, SO_PACKINFO, &v)(返回三态标志)与 getsockopt(sn, SO_REMAINSIZE, &v)(返回 sock_remained_size[sn],socket.c:705-710)。头文件 socket.h:459-463 的 @note 特别给出一个边界条件:当 SO_PACKINFO == PACK_FIRST 而 recvfrom() 返回 0 时,表示收到的是零长度 UDP 数据(只有 UDP 头)——这是 V1.0.2 版新增的说明(socket.h:11)。本工程的 dhcp.c/dns.c 均一次性收包,未使用续收路径。
4.4 sockint_kind 与寄存器 Sn_IR / Sn_IMR 的位对应
SIK_* 的 5 个位(0x01~0x10)与 w5500.h 的 Sn_IR_CON~Sn_IR_SENDOK 完全同值(见 2.6 表),所以 ctlsocket() 可以直接把 arg 透传给 setSn_IR()/setSn_IMR():
CS_CLR_INTERRUPT:先校验*(uint8_t*)arg > SIK_ALL则返回SOCKERR_ARG,否则setSn_IR(sn, *(uint8_t*)arg)(socket.c:598-601)。CS_SET_INTMASK:同样校验后setSn_IMR(sn, *(uint8_t*)arg)(socket.c:605-608)。
由于 SIK_ALL = 0x1F 只覆盖低 5 位,而 w5500.h 还定义了 IM_IR4~IM_IR7(0x10/0x20/0x40/0x80,w5500.h:770-791),Sn_IR/Sn_IMR 的第 5~7 位(Magic Packet、PPPoE Close、Destination unreachable 等)无法通过本 API 设置,会被 > SIK_ALL 检查直接拒绝。
4.5 recvfrom() 的 addr/port 有效性契约
头文件 socket.h:302-307 声明 addr 与 port "仅在本包第一次 recvfrom() 调用时有效",判断方式是在调用后用 getsockopt(sn, SO_PACKINFO, &packinfo) 检查 packinfo == PACK_FIRST。这条契约对应实现里的 if(sock_remained_size[sn] == 0) 分支。头文件的 @param port 段落文字被误并入 addr 段(socket.h:305-307),描述不完整,见 7 节。
4.6 WIZnet 与 Berkeley API 的差异(socket.h:45-80)
这是本文件最有信息量的注释段,直接决定了本工程的 socket 编号设计:
| API | WIZnet | Berkeley | 说明 |
|---|---|---|---|
socket() | 有 | 有 | WIZnet 版同时完成绑定本地端口 |
bind() | 无 | 有 | 被 socket() 吸收 |
listen() | 有 | 有 | WIZnet 版同时完成接受连接 |
accept() | 无 | 有 | 被 listen() 吸收 |
connect()/recv()/send()/recvfrom()/sendto() | 有 | 有 | 语义基本一致 |
closesocket() | 有 | 有 | WIZnet 拆成 close() 与 disconnect() |
后果(socket.h:72-79):WIZnet 允许同一个端口号上同时存在最多 8 个监听 socket;客户端连接到来时,编号最小的 socket 接受该连接,并从监听态转为通信态,此后不再监听。因此在 WIZnet 模型里"一个 socket 号 = 一个固定角色",而不是 Berkeley 那种"监听 socket 反复派生新连接 socket"。本工程据此把角色写死为:SOCK_TCP_CLIENT = 2(midware/ethernt/ethernet.h:97,云端 TCP 客户端)、SOCK_TCP_SERVER = 3(midware/ethernt/ethernet.h:98,Modbus TCP 从站);DHCP、DNS 另有自己的 socket 号(DHCP_SOCKET、DNS_SOCKET)。
该注释引用的两张图片 Berkeley_SOCKET.jpg / WIZnet_SOCKET.jpg(socket.h:71、socket.h:79)在仓库中不存在。
4.7 时序与全局变量读写
本文件不声明任何全局变量,也无线程/时序逻辑;但通过 #include "wizchip_conf.h"(socket.h:84)→ w5500.h → 工程公共头,间接引入 uCOS-II 环境,故 socket.c 能在内部使用 OSTimeGet() 做超时判定(socket.c:324、socket.c:354,2000 tick 门限)和 OSTimeDlyHMSM(0,0,0,1) 让出 CPU(socket.c:431)。任何包含本头文件的文件都隐式依赖 uCOS-II 头文件链。
实现层实际维护的全局状态(均未在本头文件暴露):
sock_any_port(socket.c:56,静态,本地端口自增游标)sock_io_mode(socket.c:57,静态,IO 模式位图,可按位寻址多个 socket)sock_is_sending(socket.c:58,非 static,send()重入互斥标志,是唯一非 static 的实现层全局量)sock_remained_size[8](socket.c:59,静态)sock_pack_info[8](socket.c:60,静态)- 宏
SOCK_ANY_PORT_NUM(socket.c:54,值为0xC000;—— 带尾分号,见 7 节)
数组维度来自 _WIZCHIP_SOCK_NUM_,本工程为 8(wizchip_conf.h:138)。
5. 对外接口
5.1 本文件导出
- 函数原型 13 个:
socket()(150)、close()(162)、listen()(175)、connect()(198)、disconnect()(215)、send()(235)、recv()(257)、sendto()(285)、recvfrom()(315)、ctlsocket()(402)、setsockopt()(430)、getsockopt()(464)、send_u32()(467,无实现)。 - 宏:
SOCKET、SOCK_OK/SOCK_BUSY/SOCK_FATAL、SOCK_ERROR、13 个SOCKERR_*、SOCKFATAL_PACKLEN、9 个SF_*、PACK_FIRST/PACK_REMAINED/PACK_COMPLETED、SOCK_IO_BLOCK/SOCK_IO_NONBLOCK。 - 枚举类型 3 个:
sockint_kind、ctlsock_type、sockopt_type。 - 全局变量:无(无
extern声明)。 - 包含保护:
_SOCKET_H_(socket.h:81-82、socket.h:470)。
5.2 被谁包含(8 处,已 Grep 确认)
| 包含者 | 行号 | 使用方式 |
|---|---|---|
midware/ethernt/lan/socket.c | socket.c:52 | 唯一实现者 |
midware/ethernt/lan/dhcp.c | dhcp.c:52 | UDP:socket(700)、sendto(409/505/565/911)、recvfrom(581)、close(828) |
midware/ethernt/lan/dns.c | dns.c:55 | UDP:socket(520)、sendto(527)、recvfrom(539)、close(544/558) |
midware/ethernt/ethernet.c | ethernet.c:15 | 云端 TCP 客户端:connect(688)、setsockopt(699)、disconnect(708/970)、socket(723)、close(732)、send(593) |
midware/ethernt/wireless/wireless_driver.c | wireless_driver.c:24 | 仅用 SOCK_OK 判连接态(150) |
midware/interface/modbus/modbus.c | modbus.c:16 | Modbus TCP 从站:listen(9647)、setsockopt(9657)、recv(9515/9536)、send(9090 等 12 处)、close(9737)、socket(9744) |
net/ems.c | ems.c:13 | 用 SOCK_OK 判状态(787/1886/1890)、send(6241) |
net/server.c | server.c:15 | 用 recv(6298/6326) |
5.3 各函数的实际调用者汇总
| 函数 | 外部调用点 |
|---|---|
socket() | ethernet.c:723、dhcp.c:700、dns.c:520、modbus.c:9744 |
close() | ethernet.c:732、dhcp.c:828、dns.c:544、dns.c:558、modbus.c:9737 |
listen() | modbus.c:9647(唯一) |
connect() | ethernet.c:688(唯一) |
disconnect() | ethernet.c:708、ethernet.c:970 |
send() | ethernet.c:593、modbus.c 12 处、net/ems.c:6241 |
recv() | modbus.c:9515、modbus.c:9536、net/server.c:6298、net/server.c:6326 |
sendto() | dhcp.c:409/505/565/911、dns.c:527 |
recvfrom() | dhcp.c:581、dns.c:539 |
setsockopt() | ethernet.c:699、modbus.c:9657,均只用 SO_KEEPALIVEAUTO |
getsockopt() | 无外部调用者(仅 socket.c:670 定义) |
ctlsocket() | 无任何调用者(仅 socket.c:574 定义);枚举量 SIK_ALL 仅在 socket.c:599/606 的参数校验中被引用 |
send_u32() | 无实现、无调用,悬空 |
5.4 调用方向
socket.h 本身不调用任何函数。被它声明的 API 向下依赖:socket.c → w5500.h 的 getSn_*/setSn_* 宏 → wizchip_conf.h 的 _WIZCHIP_IO_MODE_ 总线/SPI 回调 → 硬件层。
6. 依赖关系
- 包含:
wizchip_conf.h(socket.h:84,唯一 include),进而w5500.h→ 工程公共头 → uCOS-II。 - 由该 include 间接获得的、被本文件直接使用的符号:
Sn_MR_MFEN/Sn_MR_MC/Sn_MR_ND/Sn_MR_MULTI/Sn_MR_BCASTB/Sn_MR_MMB/Sn_MR_MIP6B/Sn_MR_UCASTB(用于定义 9 个SF_*)、_WIZCHIP_(用于#if _WIZCHIP_ == 5500与#if _WIZCHIP_ != 5100/> 5200三处条件编译)。 - 被依赖:5.2 表中 8 个 .c 文件;
socket.h的 API 是整个工程网络收发的唯一入口。 - 不依赖:无位操作、无字符串、无标准库调用;头文件不含
extern,故不产生跨编译单元的符号依赖。 - 条件编译依赖(重点):本文件的行为随
_WIZCHIP_变化(socket.h:117、socket.h:372、socket.h:374)。本工程固定为5500,三个条件的结果分别为"生效/生效/生效"。
7. 备注与风险点
send_u32()悬空声明(socket.h:467):与send()签名完全重复,全工程无定义无调用(Grep 全仓仅一处命中)。若被调用会链接失败。建议删除。SOCK_BUSY == SOCK_ERROR == 0:无法区分"非阻塞未完成"与"错误基准值",是本套 API 的结构性缺陷。工程内g_stNetSysInfo.s8SocketOk正是贴在这个歧义上做双态连接标志(ethernet.c:698/715等)。SF_*常量在 W5500 下大面积别名重合(已用w5500.h:869/878/886/895逐条核对):SF_ETHER_OWN == SF_MULTI_ENABLE == 0x80SF_IGMP_VER2 == SF_TCP_NODELAY == SF_MULTI_BLOCK == 0x20SF_IPv6_BLOCK == SF_UNI_BLOCK == 0x10
即传入 flag 时无法只表达其中一个语义;把 SF_TCP_NODELAY 用在 MACRAW socket 上,芯片会把该位解释为 MMB(屏蔽组播),反之亦然。头文件的条件编译(socket.h:117-122)在 W5500 上同时提供了两组名字,更容易让调用者误以为它们是互相独立的位。
SF_IO_NONBLOCK(0x01) 不属于Sn_MR位空间:它与其余SF_*混在同一个flag参数里(socket()的flag)。0x01 落在Sn_MR的协议选择域(Sn_MR_TCP = 0x01)内,若实现层没有先剥掉这一位就直接写Sn_MR,会把 socket 意外配成 TCP。使用时应只按头文件socket.h:124的约定传给socket()。sockopt_type序数随_WIZCHIP_漂移(socket.h:372-376):SO_SENDBUF及之后的选项序数在 5100/5200/5500 下不同。本工程只在一处定义_WIZCHIP_(wizchip_conf.h:59),头/实现一致,暂无实害;但任何新增的"局部重定义_WIZCHIP_"都会造成选项静默错位。ctlsocket()的CS_GET_INTMASK在实现层缺少break:socket.c:609-612中case CS_GET_INTMASK执行完赋值后直接落到default: return SOCKERR_ARG;,即已经读到了Sn_IMR却仍返回失败。头文件socket.h:399-400承诺"成功返回SOCK_OK",与该实现不符。目前无调用者,未被触发。getsockopt()存在两处 fall-through(socket.c:684-704):SO_MSS→SO_DESTIP、SO_SENDBUF→SO_RECVBUF→SO_STATUS。结果:查SO_MSS会把 4 字节 IP 写进uint16_t*(写越界),查SO_SENDBUF实际得到的是Sn_RX_RSR。头文件socket.h:440-452的表格逐项列出了这些选项,但没有提示实现缺陷,按表格使用会拿到错误结果。目前这两个选项无调用者。recvfrom()的port参数文档缺失:socket.h:302-307把port的"仅首包有效"说明误写进了addr段,@param port段落不存在。调用方(dhcp.c:581、dns.c:539)依赖*port回填服务端端口,误在续包时读取会拿到旧值。另注recv()的返回值是int32_t而长度参数是uint16_t,宽度不对称。SIK_ALL = 0x1F覆盖不全:Sn_IR/Sn_IMR的第 5~7 位有对应宏(w5500.h:770-791的IM_IR5~IM_IR7),但ctlsocket()在socket.c:599/606用> SIK_ALL拦截,这些中断无法通过本 API 管理。#define SOCKET uint8_t(socket.h:86):宏名SOCKET与 Windows/Winsock 的类型名冲突;在宿主机工具链上编译任何包含链含有 Winsock 的代码时会直接编译错误。嵌入式目标上无实害,但不利于代码移植或 PC 侧单元测试。- 包含保护名
_SOCKET_H_(socket.h:81):过于通用,易与第三方库冲突,属官方遗留。 SOCK_ANY_PORT_NUM带尾分号的宏定义(socket.c:54,#define SOCK_ANY_PORT_NUM 0xC000;)不在本头文件内,但影响本头文件 API 的行为:socket()传port = SOCK_ANY_PORT_NUM时由实现层替换为动态端口。该写法本身是风格缺陷(若出现在头文件中会污染所有包含者)。- Doxygen 图片缺失:
socket.h:71、socket.h:79引用Berkeley_SOCKET.jpg/WIZnet_SOCKET.jpg,仓库中不存在,只在生成文档时报错,属官方遗留。 - 枚举类型本身未被用作类型:
sockint_kind与ctlsock_type在本工程中没有任何变量声明使用(ctlsocket()无调用者);仅sockint_kind的枚举量SIK_ALL在socket.c:599/606被引用做参数上界校验。若做代码裁剪,删除这两个枚举需同步修改socket.c。