Servidor TCP en una app de iOS usando sockets POSIX: De la teoría a la práctica
En el desarrollo para iOS, la pila de red abarca desde solicitudes HTTP de alto nivel hasta sockets BSD de bajo nivel. URLSession es ideal para operaciones estándar de HTTP y WebSocket, el framework Network maneja TCP/UDP con TLS, y CFNetwork soporta protocolos heredados como FTP y Bonjour. La API POSIX proporciona acceso directo a la capa de transporte, mientras que SwiftNIO añade capacidades del lado del servidor en Swift.
Todas estas herramientas dependen de sockets. Para protocolos personalizados (como SMTP o IMAP) o lógica de servidor, necesitas acceso a la capa de transporte. Este artículo te guía paso a paso en la implementación de un servidor TCP usando la API POSIX: dependencias mínimas y control total.
Sockets POSIX: Creación y configuración
La clase TCPServer encapsula el socket, el puerto y el ciclo de vida:
final class TCPServer {
let port: UInt16
init(port: UInt16 = 8081) {
self.port = port
}
deinit {
stop()
}
func start() throws { }
func stop() { }
}
Los errores están fuertemente tipados:
enum SocketError: LocalizedError {
case socketCreationFailed(errno: Int32)
case bindFailed(errno: Int32)
case listenFailed(errno: Int32)
case writeFailed(errno: Int32)
case writeTimeout
var errorDescription: String? { /* ... */ }
}
En start():
socket(AF_INET, SOCK_STREAM, 0)crea un socket TCP IPv4. Devuelve un descriptor o -1 en caso de error (verifica errno).
setsockoptconSO_REUSEADDR=1permite volver a enlazar la dirección sin demoras de TIME_WAIT.
var reuseAddr: Int32 = 1
setsockopt(currentSocket, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, socklen_t(MemoryLayout<Int32>.size))
Enlazado, escucha y manejo de conexiones
La estructura de dirección sockaddr_in se llena para 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) pone en cola hasta 10 conexiones pendientes.
El bucle de manejo se ejecuta en un hilo en segundo plano:
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
}
}
Análisis de HTTP y servicio de archivos
El servidor implementa HTTP/1.1 básico. El analizador extrae el método, la ruta y los encabezados de la solicitud cruda.
struct HTTPRequest {
let method: String
let path: String
let headers: [String: String]
let body: Data?
}
Para GET /files/, devuelve una lista de archivos del directorio Documents:
<html><body><h1>App Files</h1><ul>
<li><a href="/file/filename">filename</a></li>
</ul></body></html>
Descargas vía /file/: usa sendfile por eficiencia.
La respuesta se construye manualmente:
"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)!
Seguridad y limitaciones
- Red local: El servidor se enlaza a 0.0.0.0:8081, accesible vía la IP del dispositivo.
- Sandbox: Acceso limitado a Documents. Sin archivos del sistema.
- Tiempo de espera: 30 s por solicitud; cierra en errores.
- Limitaciones de iOS: Redes en segundo plano requieren entitlements de
background-fetcho VoIP.
Lecciones clave:
- La API POSIX es una interfaz de bajo nivel universal, idéntica a macOS/Linux.
- SO_REUSEADDR es esencial para reinicios rápidos en desarrollo.
- Ejecuta
accept()en un bucle asíncrono sin bloquear el hilo principal. - Análisis HTTP minimalista: método + ruta + Content-Length.
sendfile()supera awrite()para archivos grandes.
Pruebas y depuración
- Lanzamiento:
try server.start() - Acceso local:
http://localhost:8081/files - Acceso en red:
http://192.168.1.100:8081/files - Registros:
print(String(cString: strerror(errno))) - Instruments: Usa Network Link Conditioner para simulaciones.
El proyecto demuestra el ciclo completo: desde el socket hasta la interfaz web. El código es extensible para WebSocket o protocolos personalizados.
— Editorial Team
Aún no hay comentarios.