Volver al inicio

Servidor TCP en iOS con sockets POSIX

El artículo desglosa la implementación de un servidor TCP en una app de iOS usando API POSIX. Los frameworks de red de Apple, creación de sockets, análisis HTTP y servicio de archivos se describen. Se proporciona código completo funcional con manejo de errores.

Servidor TCP dentro de iOS: guía completa de sockets
Advertisement 728x90

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:

Google AdInline article slot
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).
  • setsockopt con SO_REUSEADDR=1 permite 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):

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) 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.

Google AdInline article slot
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-fetch o 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 a write() 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

Advertisement 728x90

Leer después