# 在 iOS 应用中使用 POSIX 套接字实现 TCP 服务器:从理论到实践
在 iOS 开发中,网络栈涵盖了从高层 HTTP 请求到低层 BSD 套接字的一切。URLSession 非常适合标准 HTTP 和 WebSocket 操作,Network 框架处理带 TLS 的 TCP/UDP,而 CFNetwork 支持如 FTP 和 Bonjour 等旧协议。POSIX API 提供对传输层的直接访问,而 SwiftNIO 则为 Swift 添加了服务器端功能。
所有这些工具都依赖于套接字。对于自定义协议(如 SMTP 或 IMAP)或服务器逻辑,你需要传输层访问。本文将带你一步步使用 POSIX API 实现 TCP 服务器——最小依赖,完全控制。
POSIX 套接字:创建与设置
TCPServer 类封装了套接字、端口和生命周期:
final class TCPServer {
let port: UInt16
init(port: UInt16 = 8081) {
self.port = port
}
deinit {
stop()
}
func start() throws { }
func stop() { }
}
错误采用强类型:
enum SocketError: LocalizedError {
case socketCreationFailed(errno: Int32)
case bindFailed(errno: Int32)
case listenFailed(errno: Int32)
case writeFailed(errno: Int32)
case writeTimeout
var errorDescription: String? { /* ... */ }
}
在 start() 中:
socket(AF_INET, SOCK_STREAM, 0)创建一个 IPv4 TCP 套接字。它返回描述符或出错时 -1(检查 errno)。
setsockopt设置SO_REUSEADDR=1,允许在无 TIME_WAIT 延迟的情况下重新绑定地址。
var reuseAddr: Int32 = 1
setsockopt(currentSocket, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, socklen_t(MemoryLayout<Int32>.size))
绑定、监听与处理连接
sockaddr_in 地址结构为 INADDR_ANY (0.0.0.0) 填充:
var addr = sockaddr_in()
addr.sin_family = sa_family_t(AF_INET)
addr.sin_port = in_port_t(port.bigEndian)
addr.sin_addr.s_addr = in_addr_t(0)
bind(currentSocket, sockaddr_cast(&addr), socklen_t(MemoryLayout<sockaddr_in>.size))
listen(currentSocket, 10) 将最多 10 个待连接排队。
处理循环在后台线程运行:
DispatchQueue.global().async { [weak self] in
while let server = self, server.isRunning {
var clientAddr = sockaddr_in()
var len = socklen_t(MemoryLayout<sockaddr_in>.size)
let clientSocket = accept(server.currentSocket, sockaddr_cast(&clientAddr), &len)
// Handle clientSocket
}
}
HTTP 解析与文件服务
服务器实现了基本的 HTTP/1.1。解析器从原始请求中提取方法、路径和头部。
struct HTTPRequest {
let method: String
let path: String
let headers: [String: String]
let body: Data?
}
对于 GET /files/,它返回 Documents 目录中的文件列表:
<html><body><h1>App Files</h1><ul>
<li><a href="/file/filename">filename</a></li>
</ul></body></html>
通过 /file/ 下载:使用 sendfile 以提高效率。
响应手动构建:
"HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/html\r\n"
+ "Content-Length: \(data.count)\r\n"
+ "\r\n" + String(data: data, encoding: .utf8)!
安全与限制
- 本地网络:服务器绑定到 0.0.0.0:8081,可通过设备 IP 访问。
- 沙箱:访问限于 Documents。无系统文件。
- 超时:每个请求 30 秒;出错时关闭。
- iOS 限制:后台网络需要
background-fetch或 VoIP 权限。
关键要点:
- POSIX API 是通用低层接口,与 macOS/Linux 相同。
- SO_REUSEADDR 对于快速开发重启至关重要。
- 在异步循环中运行
accept(),不阻塞主线程。 - HTTP 解析极简:方法 + 路径 + Content-Length。
sendfile()在大文件上优于write()。
测试与调试
- 启动:
try server.start() - 本地访问:
http://localhost:8081/files - 网络访问:
http://192.168.1.100:8081/files - 日志:
print(String(cString: strerror(errno))) - Instruments:使用 Network Link Conditioner 进行模拟。
该项目展示了完整周期:从套接字到 Web 界面。代码可扩展用于 WebSocket 或自定义协议。
— Editorial Team
暂无评论。