返回首页

iOS 上使用 POSIX sockets 的 TCP 服务器

本文详细解析了使用 POSIX API 在 iOS 应用中实现 TCP 服务器的过程。介绍了 Apple 的网络框架、socket 创建、HTTP 解析和文件服务。提供完整的带错误处理的代码。

iOS 内部的 TCP 服务器:sockets 完整指南
Advertisement 728x90

# 在 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 类封装了套接字、端口和生命周期:

Google AdInline article slot
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) 填充:

Google AdInline article slot
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。解析器从原始请求中提取方法、路径和头部。

Google AdInline article slot
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

Advertisement 728x90

继续阅读