SDL_net 核心 API 集合(CategorySDLNet)
SDL_net 是 SDL 官方的网络编程扩展库,它对 BSD Sockets、WinSock 等系统级网络 API 进行了轻量级封装,核心设计目标是简化网络编程复杂度、处理各类边界情况,让开发者无需关注底层协议细节即可实现网络通信。其核心设计理念包括:全异步非阻塞接口(可显式等待操作完成)、抽象化地址管理(屏蔽不同网络协议差异)、简洁优先且不失功能完整性。
核心概念与使用流程
- 初始化:调用
NET_Init()初始化库,程序退出前调用NET_Quit()清理; - 地址处理:通过
NET_ResolveHostname()解析域名到NET_Address对象,或直接构造地址; - 通信模式选择:
- TCP(流套接字):客户端用
NET_CreateClient()连接服务器,服务器用NET_CreateServer()监听并通过NET_AcceptClient()接受连接,通过NET_StreamSocket收发数据; - UDP(数据报套接字):用
NET_CreateDatagramSocket()创建套接字,通过NET_SendDatagram()/NET_ReceiveDatagram()收发数据包;
- TCP(流套接字):客户端用
- 异步操作:所有网络操作非阻塞,可通过
NET_WaitUntil*()系列函数显式等待操作完成; - 测试辅助:通过模拟网络异常函数(如丢包)测试恶劣网络环境下的程序表现。
核心 API 列表(按功能分类)
一、基础初始化与版本管理
- NET_Init:初始化 SDL_net 库(必须调用,加载底层网络接口)
- NET_Quit:清理 SDL_net 所有资源(程序退出前必须调用)
- NET_Version:获取当前 SDL_net 版本信息
- SDL_NET_MAJOR_VERSION:SDL_net 主版本号宏(如 3 代表 SDL_net 3.x)
- SDL_NET_MINOR_VERSION:SDL_net 次版本号宏
- SDL_NET_MICRO_VERSION:SDL_net 修订版本号宏
- SDL_NET_VERSION:构造版本结构体的宏
- SDL_NET_VERSION_ATLEAST:编译期检查版本是否满足最低要求
二、网络地址(NET_Address)管理
1. 地址解析与创建
- NET_ResolveHostname:异步解析域名/IP 到 NET_Address 对象(后台线程执行 DNS 查询)
- NET_GetLocalAddresses:获取本地主机的所有网络地址(返回 NET_Address 数组)
- NET_FreeLocalAddresses:释放
NET_GetLocalAddresses返回的地址数组
2. 地址属性与操作
- NET_GetAddressString:将 NET_Address 转换为可读的字符串(如 "192.168.1.1:8080")
- NET_GetAddressStatus:获取地址解析状态(未解析/解析成功/解析失败)
- NET_CompareAddresses:比较两个 NET_Address 是否指向同一网络地址
- NET_RefAddress:增加 NET_Address 对象的引用计数(防止被提前释放)
- NET_UnrefAddress:减少 NET_Address 对象的引用计数,计数为 0 时自动释放
三、TCP 流套接字(StreamSocket)- 客户端
- NET_CreateClient:创建 TCP 客户端套接字,发起对指定 NET_Address 的连接
- NET_GetConnectionStatus:获取客户端连接状态(未连接/连接中/已连接/连接失败)
- NET_WaitUntilConnected:阻塞等待客户端连接完成(支持超时,可非阻塞查询/无限等待)
- NET_GetStreamSocketAddress:获取流套接字对应的远端 NET_Address
- NET_GetStreamSocketPendingWrites:获取流套接字待发送的字节数
- NET_WriteToStreamSocket:向流套接字写入数据(非阻塞,返回实际写入字节数)
- NET_ReadFromStreamSocket:从流套接字读取数据(非阻塞,返回实际读取字节数)
- NET_WaitUntilInputAvailable:阻塞等待流套接字有可读数据(支持超时)
- NET_WaitUntilStreamSocketDrained:阻塞等待套接字待发送数据全部发送完成
- NET_DestroyStreamSocket:销毁流套接字,关闭连接
四、TCP 服务器(NET_Server)
- NET_CreateServer:创建 TCP 服务器,监听指定 NET_Address 的端口
- NET_AcceptClient:接受客户端连接(非阻塞,返回新的 NET_StreamSocket)
- NET_DestroyServer:销毁服务器,停止监听端口
五、UDP 数据报套接字(DatagramSocket)
- NET_CreateDatagramSocket:创建 UDP 数据报套接字(无连接,支持点对点通信)
- NET_SendDatagram:发送 UDP 数据包到指定 NET_Address(非阻塞)
- NET_ReceiveDatagram:接收 UDP 数据包(非阻塞,返回数据包和发送方地址)
- NET_DestroyDatagram:销毁接收到的 UDP 数据包,释放内存
- NET_DestroyDatagramSocket:销毁 UDP 套接字
六、阻塞等待函数(异步操作显式等待)
- NET_WaitUntilResolved:阻塞等待地址解析完成(支持超时)
- NET_WaitUntilConnected:阻塞等待客户端连接完成
- NET_WaitUntilInputAvailable:阻塞等待套接字有可读数据
- NET_WaitUntilStreamSocketDrained:阻塞等待套接字发送缓冲区排空
七、网络异常模拟(测试专用)
- NET_SimulateAddressResolutionLoss:模拟地址解析失败(测试 DNS 异常)
- NET_SimulateStreamPacketLoss:模拟 TCP 流数据包丢失(测试网络不稳定)
- NET_SimulateDatagramPacketLoss:模拟 UDP 数据包丢失(测试丢包场景)
八、状态枚举(NET_Status)
- 核心状态值(隐含在 NET_Status 枚举中):
- NET_STATUS_INVALID:无效状态(未初始化/已销毁)
- NET_STATUS_RESOLVING:地址解析中
- NET_STATUS_RESOLVED:地址解析成功
- NET_STATUS_RESOLVE_FAILED:地址解析失败
- NET_STATUS_CONNECTING:连接建立中
- NET_STATUS_CONNECTED:连接已建立
- NET_STATUS_CONNECT_FAILED:连接建立失败
数据类型(Datatypes)
- NET_Address:网络地址抽象对象(封装 IP 地址、端口、协议等信息)
- NET_DatagramSocket:UDP 数据报套接字对象(无连接,用于收发数据包)
- NET_Server:TCP 服务器对象(监听端口,接受客户端连接)
- NET_StreamSocket:TCP 流套接字对象(面向连接,可靠的字节流传输)
结构体(Structs)
- NET_Datagram:UDP 数据包结构体(包含数据缓冲区、数据长度、发送方地址)
宏定义(Macros)
- SDL_NET_MAJOR_VERSION / SDL_NET_MINOR_VERSION / SDL_NET_MICRO_VERSION:版本号宏
- SDL_NET_VERSION:构造 SDL_version 结构体的宏
- SDL_NET_VERSION_ATLEAST:版本检查宏(如
SDL_NET_VERSION_ATLEAST(3,0,0))
FreeBASIC 示例代码
' 引入 SDL 及 SDL_net 相关声明(需链接 SDL3 和 SDL3_net 库)
#Include "SDL.bi"
#Include "SDL_net.bi"
' 补充关键常量/类型声明(FreeBASIC 绑定可能缺失)
#Define NET_PORT_ANY 0
Type NET_Status
value As Integer
End Type
Enum NET_Status_Values
NET_STATUS_INVALID = 0
NET_STATUS_RESOLVING
NET_STATUS_RESOLVED
NET_STATUS_CONNECT_FAILED
NET_STATUS_CONNECTING
NET_STATUS_CONNECTED
End Enum
' 检查 SDL_net 版本
Sub CheckNetVersion()
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "【检查 SDL_net 版本】")
Dim As SDL_version ver
NET_Version(@ver)
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "SDL_net 版本:%d.%d.%d", ver.major, ver.minor, ver.patch)
' 编译期版本检查
#If SDL_NET_VERSION_ATLEAST(3, 0, 0)
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "✓ 满足 SDL_net 3.0.0 最低版本要求")
#Else
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "✗ 不满足 SDL_net 3.0.0 最低版本要求")
#EndIf
End Sub
' TCP 服务器示例(简单回显服务器)
Sub TCPServerExample(ByVal port As Uint16)
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, vbCrLf & "【TCP 回显服务器示例】")
' 1. 初始化 SDL_net
If (NET_Init() = SDL_FALSE) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_net 初始化失败:%s", SDL_GetError())
Exit Sub
End If
' 2. 构造服务器地址(监听所有网卡的指定端口)
Dim As NET_Address Ptr serverAddr = NET_ResolveHostname(NULL, port)
If (serverAddr = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "解析地址失败:%s", SDL_GetError())
NET_Quit()
Exit Sub
End If
' 等待地址解析完成
If (NET_WaitUntilResolved(serverAddr, -1) = SDL_FALSE) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "地址解析超时")
NET_UnrefAddress(serverAddr)
NET_Quit()
Exit Sub
End If
' 3. 创建 TCP 服务器
Dim As NET_Server Ptr server = NET_CreateServer(serverAddr)
If (server = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "创建服务器失败:%s", SDL_GetError())
NET_UnrefAddress(serverAddr)
NET_Quit()
Exit Sub
End If
NET_UnrefAddress(serverAddr) ' 释放地址引用
Dim As ZString * 256 addrStr
NET_GetAddressString(serverAddr, addrStr, sizeof(addrStr))
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "服务器已启动,监听地址:%s", addrStr)
' 4. 接受客户端连接并处理
Dim As NET_StreamSocket Ptr clientSocket = NULL
Dim As SDL_bool running = SDL_TRUE
Dim As UByte buffer(1023) ' 1KB 接收缓冲区
While (running)
' 非阻塞接受客户端连接
clientSocket = NET_AcceptClient(server)
If (clientSocket <> NULL) Then
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "客户端已连接")
' 循环接收并回显数据
While (running)
' 等待有可读数据(超时 100ms)
If (NET_WaitUntilInputAvailable(clientSocket, 100) = SDL_TRUE) Then
' 读取数据
Dim As Sint64 readBytes = NET_ReadFromStreamSocket(clientSocket, @buffer(0), sizeof(buffer))
If (readBytes > 0) Then
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "收到数据:%.*s", readBytes, @buffer(0))
' 回显数据给客户端
Dim As Sint64 writeBytes = NET_WriteToStreamSocket(clientSocket, @buffer(0), readBytes)
If (writeBytes < 0) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "发送数据失败:%s", SDL_GetError())
Exit While
End If
ElseIf (readBytes = 0) Then
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "客户端断开连接")
Exit While
Else
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "读取数据失败:%s", SDL_GetError())
Exit While
End If
End If
' 处理退出事件(简化示例,实际需处理 SDL 事件)
Dim As SDL_Event evt
If (SDL_PollEvent(@evt) AndAlso evt.type = SDL_QUIT) Then
running = SDL_FALSE
End If
Wend
' 关闭客户端连接
NET_DestroyStreamSocket(clientSocket)
clientSocket = NULL
End If
' 处理退出事件
Dim As SDL_Event evt
If (SDL_PollEvent(@evt) AndAlso evt.type = SDL_QUIT) Then
running = SDL_FALSE
End If
SDL_Delay(10)
Wend
' 5. 释放资源
If (clientSocket <> NULL) Then
NET_DestroyStreamSocket(clientSocket)
End If
NET_DestroyServer(server)
NET_Quit()
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "服务器已停止")
End Sub
' TCP 客户端示例
Sub TCPClientExample(ByVal hostname As ZString Ptr, ByVal port As Uint16)
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, vbCrLf & "【TCP 客户端示例】")
' 1. 初始化 SDL_net
If (NET_Init() = SDL_FALSE) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_net 初始化失败:%s", SDL_GetError())
Exit Sub
End If
' 2. 解析服务器地址
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "解析地址:%s:%d", hostname, port)
Dim As NET_Address Ptr serverAddr = NET_ResolveHostname(hostname, port)
If (serverAddr = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "解析地址失败:%s", SDL_GetError())
NET_Quit()
Exit Sub
End If
' 等待地址解析完成(无限等待)
If (NET_WaitUntilResolved(serverAddr, -1) = SDL_FALSE) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "地址解析失败")
NET_UnrefAddress(serverAddr)
NET_Quit()
Exit Sub
End If
' 打印解析后的地址
Dim As ZString * 256 addrStr
NET_GetAddressString(serverAddr, addrStr, sizeof(addrStr))
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "解析完成:%s", addrStr)
' 3. 创建客户端并连接服务器
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "连接服务器...")
Dim As NET_StreamSocket Ptr clientSocket = NET_CreateClient(serverAddr)
If (clientSocket = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "创建客户端失败:%s", SDL_GetError())
NET_UnrefAddress(serverAddr)
NET_Quit()
Exit Sub
End If
NET_UnrefAddress(serverAddr)
' 等待连接完成(超时 5 秒)
If (NET_WaitUntilConnected(clientSocket, 5000) = SDL_FALSE) Then
Dim As NET_Status status
NET_GetConnectionStatus(clientSocket, @status)
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "连接失败,状态:%d", status.value)
NET_DestroyStreamSocket(clientSocket)
NET_Quit()
Exit Sub
End If
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "连接成功!")
' 4. 发送测试数据
Dim As ZString * 64 sendData = "Hello SDL_net Server!"
Dim As Sint64 writeBytes = NET_WriteToStreamSocket(clientSocket, sendData, Len(sendData))
If (writeBytes < 0) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "发送数据失败:%s", SDL_GetError())
NET_DestroyStreamSocket(clientSocket)
NET_Quit()
Exit Sub
End If
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "发送数据:%s", sendData)
' 5. 接收服务器回显数据
Dim As UByte recvBuffer(1023)
If (NET_WaitUntilInputAvailable(clientSocket, 2000) = SDL_TRUE) Then
Dim As Sint64 readBytes = NET_ReadFromStreamSocket(clientSocket, @recvBuffer(0), sizeof(recvBuffer))
If (readBytes > 0) Then
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "收到回显:%.*s", readBytes, @recvBuffer(0))
End If
Else
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "接收超时")
End If
' 6. 等待数据发送完成并关闭连接
NET_WaitUntilStreamSocketDrained(clientSocket, -1)
NET_DestroyStreamSocket(clientSocket)
NET_Quit()
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "客户端已断开连接")
End Sub
' UDP 通信示例(简单点对点发送)
Sub UDPCommunicationExample(ByVal localPort As Uint16, ByVal targetHost As ZString Ptr, ByVal targetPort As Uint16)
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, vbCrLf & "【UDP 通信示例】")
' 1. 初始化 SDL_net
If (NET_Init() = SDL_FALSE) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_net 初始化失败:%s", SDL_GetError())
Exit Sub
End If
' 2. 创建 UDP 套接字
Dim As NET_Address Ptr localAddr = NET_ResolveHostname(NULL, localPort)
Dim As NET_DatagramSocket Ptr udpSocket = NET_CreateDatagramSocket(localAddr)
NET_UnrefAddress(localAddr)
If (udpSocket = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "创建 UDP 套接字失败:%s", SDL_GetError())
NET_Quit()
Exit Sub
End If
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "UDP 套接字已创建,监听端口:%d", localPort)
' 3. 解析目标地址
Dim As NET_Address Ptr targetAddr = NET_ResolveHostname(targetHost, targetPort)
If (NET_WaitUntilResolved(targetAddr, 3000) = SDL_FALSE) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "解析目标地址失败")
NET_UnrefAddress(targetAddr)
NET_DestroyDatagramSocket(udpSocket)
NET_Quit()
Exit Sub
End If
' 4. 发送 UDP 数据包
Dim As ZString * 64 sendData = "Hello UDP Peer!"
If (NET_SendDatagram(udpSocket, targetAddr, sendData, Len(sendData)) = SDL_FALSE) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "发送 UDP 数据包失败:%s", SDL_GetError())
Else
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "发送 UDP 数据:%s", sendData)
End If
' 5. 尝试接收响应(超时 2 秒)
Dim As NET_Datagram Ptr datagram = NULL
Dim As UByte recvBuffer(1023)
If (NET_ReceiveDatagram(udpSocket, @datagram, 2000) = SDL_TRUE AndAlso datagram <> NULL) Then
' 提取数据包数据
Dim As Const UByte Ptr data = NET_DatagramGetData(datagram)
Dim As Sint64 len = NET_DatagramGetLength(datagram)
Dim As NET_Address Ptr senderAddr = NET_DatagramGetAddress(datagram)
Dim As ZString * 256 senderStr
NET_GetAddressString(senderAddr, senderStr, sizeof(senderStr))
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "从 %s 收到 UDP 数据:%.*s", senderStr, len, data)
NET_DestroyDatagram(datagram)
Else
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "未收到 UDP 响应(超时)")
End If
' 6. 释放资源
NET_UnrefAddress(targetAddr)
NET_DestroyDatagramSocket(udpSocket)
NET_Quit()
End Sub
' 主程序
Sub Main()
' 初始化 SDL 核心
If (SDL_Init(0) < 0) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL 初始化失败:%s", SDL_GetError())
Exit Sub
End If
' 运行示例(按需取消注释)
CheckNetVersion()
' TCPServerExample(8080) ' 先启动服务器
' TCPClientExample(StrPtr("127.0.0.1"), 8080) ' 再启动客户端
' UDPCommunicationExample(8081, StrPtr("127.0.0.1"), 8082)
' 清理 SDL
SDL_Quit()
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, vbCrLf & "程序正常退出")
End Sub
' 运行主程序
Main()
核心知识点补充
-
异步非阻塞核心:
- SDL_net 所有操作默认非阻塞,调用后立即返回,需通过状态查询(如
NET_GetConnectionStatus)或NET_WaitUntil*()等待操作完成; - 阻塞函数(
NET_WaitUntil*)建议在后台线程使用,避免阻塞主线程(如游戏渲染线程)。
- SDL_net 所有操作默认非阻塞,调用后立即返回,需通过状态查询(如
-
TCP vs UDP 选择:
- TCP(流套接字):可靠的字节流传输,自动重传丢失数据、保证顺序,适合需要数据完整性的场景(如登录、文件传输);
- UDP(数据报套接字):无连接、不可靠,数据包可能丢失/乱序,但延迟低、开销小,适合实时性要求高的场景(如游戏帧同步、语音)。
-
地址引用计数:
NET_Address采用引用计数管理,NET_RefAddress增加计数,NET_UnrefAddress减少计数;- 确保不再使用的地址调用
NET_UnrefAddress,避免内存泄漏。
-
网络异常模拟:
NET_Simulate*Loss系列函数用于测试恶劣网络环境,仅在开发/测试阶段使用,生产环境需禁用;- 可模拟 DNS 解析失败、TCP/UDP 丢包,验证程序的容错能力。
-
跨平台注意事项:
- Windows 系统需确保防火墙允许程序访问网络,监听端口需配置入站规则;
- Linux/macOS 需注意端口权限(1024 以下端口需 root 权限);
- 移动平台(Android/iOS)需申请网络权限:Android 需
INTERNET权限,iOS 需关闭 ATS(或配置例外)。
总结
-
核心优势:
- 轻量级封装系统套接字 API,屏蔽跨平台差异(Windows/ Linux/macOS/移动端);
- 全异步非阻塞设计,适配游戏/实时应用的主线程模型;
- 抽象化地址管理,无需关注 IPv4/IPv6 底层细节;
- 内置网络异常模拟功能,便于测试程序的鲁棒性;
- 与 SDL 生态无缝集成,可直接结合 SDL 事件循环使用。
-
使用建议:
- 网络操作尽量放在后台线程,避免阻塞主线程;
- TCP 通信需处理连接断开、超时等异常,UDP 需自行实现重传/校验逻辑;
- 频繁使用的
NET_Address需合理管理引用计数,避免内存泄漏; - 服务器监听端口时,优先使用
NET_PORT_ANY自动分配端口,或选择 1024 以上的非特权端口; - 生产环境禁用网络异常模拟函数,避免影响正常通信。
-
关键点回顾:
- SDL_net 核心是抽象化的地址(
NET_Address)、套接字(TCP 流/UDP 数据报)、服务器(NET_Server)三层结构; NET_Init()/NET_Quit()是必选的初始化/清理接口;- TCP 适合可靠传输,UDP 适合实时传输,需根据业务场景选择;
- 所有操作默认非阻塞,可通过
NET_WaitUntil*()显式等待; - 地址对象采用引用计数管理,需正确调用
NET_RefAddress/NET_UnrefAddress。
- SDL_net 核心是抽象化的地址(
评论一下?