Zurück zur Startseite

TCP-Server in iOS auf POSIX-Sockets

Der Artikel zerlegt die Implementierung eines TCP-Servers in einer iOS-App mit POSIX-API. Apples Netzwerk-Frameworks, Socket-Erstellung, HTTP-Parsing und Dateiserver werden beschrieben. Vollständiger funktionierender Code mit Fehlerbehandlung wird bereitgestellt.

TCP-Server in iOS: vollständiger Leitfaden zu Sockets
Advertisement 728x90

# TCP-Server in einer iOS-App mit POSIX-Sockets: Von der Theorie zur Praxis

In der iOS-Entwicklung deckt der Netzwerkstapel alles ab, von hochstufigen HTTP-Anfragen bis zu niedrigstufigen BSD-Sockets. URLSession eignet sich ideal für standardmäßige HTTP- und WebSocket-Operationen, der Network framework behandelt TCP/UDP mit TLS, und CFNetwork unterstützt Legacy-Protokolle wie FTP und Bonjour. Die POSIX API bietet direkten Zugriff auf die Transportschicht, während SwiftNIO serverseitige Funktionen in Swift hinzufügt.

Alle diese Tools basieren auf Sockets. Für benutzerdefinierte Protokolle (wie SMTP oder IMAP) oder Serverlogik benötigen Sie Zugriff auf die Transportschicht. Dieser Artikel führt Sie Schritt für Schritt durch die Implementierung eines TCP-Servers mit der POSIX API – minimale Abhängigkeiten und volle Kontrolle.

POSIX-Sockets: Erstellung und Einrichtung

Die TCPServer-Klasse kapselt den Socket, den Port und den Lebenszyklus:

Google AdInline article slot
final class TCPServer {
    let port: UInt16

    init(port: UInt16 = 8081) {
        self.port = port
    }

    deinit {
        stop()
    }

    func start() throws { }
    func stop() { }
}

Fehler sind stark typisiert:

enum SocketError: LocalizedError {
  case socketCreationFailed(errno: Int32)
  case bindFailed(errno: Int32)
  case listenFailed(errno: Int32)
  case writeFailed(errno: Int32)
  case writeTimeout

  var errorDescription: String? { /* ... */ }
}

In start():

  • socket(AF_INET, SOCK_STREAM, 0) erstellt einen IPv4-TCP-Socket. Es gibt einen Deskriptor zurück oder -1 bei Fehler (errno prüfen).
  • setsockopt mit SO_REUSEADDR=1 erlaubt das Neubinden der Adresse ohne TIME_WAIT-Verzögerungen.
var reuseAddr: Int32 = 1
setsockopt(currentSocket, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, socklen_t(MemoryLayout<Int32>.size))

Binden, Lauschen und Behandeln von Verbindungen

Die sockaddr_in-Adressstruktur wird für INADDR_ANY (0.0.0.0) befüllt:

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) stellt bis zu 10 ausstehende Verbindungen in die Warteschlange.

Die Behandlungsschleife läuft in einem Hintergrund-Thread:

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-Parsing und Dateiservieren

Der Server implementiert grundlegendes HTTP/1.1. Der Parser extrahiert die Methode, den Pfad und die Header aus der rohen Anfrage.

Google AdInline article slot
struct HTTPRequest {
    let method: String
    let path: String
    let headers: [String: String]
    let body: Data?
}

Für GET /files/ gibt es eine Liste von Dateien aus dem Documents-Verzeichnis zurück:

<html><body><h1>App Files</h1><ul>
<li><a href="/file/filename">filename</a></li>
</ul></body></html>

Downloads über /file/: verwendet sendfile für Effizienz.

Die Antwort wird manuell aufgebaut:

"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)!

Sicherheit und Einschränkungen

  • Lokales Netzwerk: Der Server bindet an 0.0.0.0:8081, erreichbar über die IP des Geräts.
  • Sandbox: Zugriff beschränkt auf Documents. Keine Systemdateien.
  • Timeout: 30 Sekunden pro Anfrage; bei Fehlern schließen.
  • iOS-Einschränkungen: Hintergrund-Netzwerkverbindungen erfordern background-fetch oder VoIP-Berechtigungen.

Wichtige Erkenntnisse:

  • POSIX API ist eine universelle Low-Level-Schnittstelle, identisch zu macOS/Linux.
  • SO_REUSEADDR ist essenziell für schnelle Entwicklungs-Neustarts.
  • Führen Sie accept() in einer asynchronen Schleife aus, ohne den Hauptthread zu blockieren.
  • HTTP-Parsing ist minimalistisch: Methode + Pfad + Content-Length.
  • sendfile() übertrifft write() bei großen Dateien.

Testen und Debuggen

  • Starten: try server.start()
  • Lokaler Zugriff: http://localhost:8081/files
  • Netzwerkzugriff: http://192.168.1.100:8081/files
  • Logs: print(String(cString: strerror(errno)))
  • Instruments: Verwenden Sie Network Link Conditioner für Simulationen.

Das Projekt demonstriert den vollen Zyklus: vom Socket bis zur Weboberfläche. Der Code ist erweiterbar für WebSocket oder benutzerdefinierte Protokolle.

— Editorial Team

Advertisement 728x90

Weiterlesen