# 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:
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).
setsockoptzSO_REUSEADDR=1pozwala 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):
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.
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-fetchlub 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
Brak komentarzy.