Powrót do strony głównej

Serwer TCP w iOS na gniazdach POSIX

Artykuł omawia implementację serwera TCP w aplikacji iOS z użyciem POSIX API. Opisane są sieciowe frameworki Apple, tworzenie gniazd, parsowanie HTTP i serwowanie plików. Podany jest pełny działający kod z obsługą błędów.

Serwer TCP wewnątrz iOS: pełny przewodnik po gniazdach
Advertisement 728x90

# Serwer TCP w aplikacji iOS na gniazdach POSIX: od teorii do praktyki

W rozwoju iOS stos sieciowy obejmuje poziomy od wysokopoziomowych zapytań HTTP po niskopoziomowe gniazda BSD. URLSession nadaje się do standardowych operacji HTTP/WebSocket, framework Network — do TCP/UDP z TLS, CFNetwork implementuje starsze protokoły takie jak FTP i Bonjour. API POSIX zapewnia bezpośredni dostęp do poziomu transportowego, SwiftNIO dodaje możliwości serwerowe w Swift.

Wszystkie narzędzia opierają się na gniazdach. Dla niestandardowych protokołów (SMTP, IMAP) lub logiki serwerowej wymagany jest poziom transportowy. W artykule zaimplementujemy serwer TCP za pomocą API POSIX — minimalne zależności, pełna kontrola.

Gniazda POSIX: tworzenie i konfiguracja

Klasa TCPServer enkapsuluje gniazdo, port i cykl życia:

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

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

    deinit {
        stop()
    }

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

Błędy są typizowane:

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

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

W start():

  • socket(AF_INET, SOCK_STREAM, 0) tworzy gniazdo TCP IPv4. Zwraca deskryptor lub -1 przy błędzie (errno).
  • setsockopt z SO_REUSEADDR=1 pozwala na ponowne wiązanie adresu bez opóźnień TIME_WAIT.
var reuseAddr: Int32 = 1
setsockopt(currentSocket, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, socklen_t(MemoryLayout<Int32>.size))

Wiązanie, nasłuchiwanie i obsługa połączeń

Struktura adresu sockaddr_in jest wypełniana dla 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) oczekuje do 10 połączeń w kolejce.

Pętla obsługi w oddzielnym wątku:

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)
        // Obsługa clientSocket
    }
}

Parsowanie HTTP i serwowanie plików

Serwer implementuje podstawowy HTTP/1.1. Parser wyciąga metodę, ścieżkę, nagłówki z surowego żądania.

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

Dla GET /files/ zwraca listę plików z katalogu Documents:

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

Pobieranie przez /file/: sendfile dla efektywności.

Odpowiedź jest formowana ręcznie:

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

Bezpieczeństwo i ograniczenia

  • Sieć lokalna: serwer wiąże się na 0.0.0.0:8081, dostępny po IP urządzenia.
  • Sandbox: dostęp tylko do Documents. Brak plików systemowych.
  • Timeout: 30 s na żądanie, zamknij przy błędach.
  • Ograniczenia iOS: sieciowanie w tle wymaga background-fetch lub uprawnień VoIP.

Co ważne:

  • API POSIX — uniwersalny niski poziom, identyczny z macOS/Linux.
  • SO_REUSEADDR niezbędny do restartów dev.
  • accept() w asynchronicznej pętli, bez blokowania głównego wątku.
  • Parsowanie HTTP minimalistyczne: metoda+ścieżka+Content-Length.
  • sendfile() > write() pod względem wydajności na dużych plikach.

Testowanie i debugowanie

  • Uruchomienie: try server.start()
  • Dostęp lokalny: http://localhost:8081/files
  • Sieć: http://192.168.1.100:8081/files
  • Logi: print(String(cString: strerror(errno)))
  • Instruments: Network Link Conditioner do symulacji.

Projekt demonstruje pełny cykl: od gniazda do interfejsu webowego. Kod można rozszerzyć o WebSocket lub niestandardowe protokoły.

— Editorial Team

Advertisement 728x90

Czytaj dalej