# iOS 앱에서 POSIX 소켓을 사용한 TCP 서버: 이론에서 실전까지
iOS 개발에서 네트워킹 스택은 고수준 HTTP 요청부터 저수준 BSD 소켓까지 모든 것을 다룹니다. URLSession은 표준 HTTP 및 WebSocket 작업에 이상적이며, Network 프레임워크는 TLS가 적용된 TCP/UDP를 처리하고, CFNetwork은 FTP나 Bonjour 같은 레거시 프로토콜을 지원합니다. POSIX API는 전송 계층에 직접 접근할 수 있게 해주며, SwiftNIO는 Swift에서 서버 측 기능을 추가합니다.
이 모든 도구들은 소켓에 의존합니다. 사용자 정의 프로토콜(SMTP나 IMAP처럼)이나 서버 로직의 경우 전송 계층 접근이 필요합니다. 이 글에서는 POSIX API를 사용한 TCP 서버 구현 과정을 단계별로 안내합니다—최소한의 의존성과 완전한 제어.
POSIX 소켓: 생성과 설정
TCPServer 클래스는 소켓, 포트, 생명 주기를 캡슐화합니다:
final class TCPServer {
let port: UInt16
init(port: UInt16 = 8081) {
self.port = port
}
deinit {
stop()
}
func start() throws { }
func stop() { }
}
오류는 강타입으로 정의됩니다:
enum SocketError: LocalizedError {
case socketCreationFailed(errno: Int32)
case bindFailed(errno: Int32)
case listenFailed(errno: Int32)
case writeFailed(errno: Int32)
case writeTimeout
var errorDescription: String? { /* ... */ }
}
start()에서:
socket(AF_INET, SOCK_STREAM, 0)은 IPv4 TCP 소켓을 생성합니다. 오류 시 -1을 반환하며 (errno 확인).
setsockopt으로SO_REUSEADDR=1을 설정하면 TIME_WAIT 지연 없이 주소를 재바인딩할 수 있습니다.
var reuseAddr: Int32 = 1
setsockopt(currentSocket, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, socklen_t(MemoryLayout<Int32>.size))
바인딩, 리스닝, 연결 처리
sockaddr_in 주소 구조체를 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)은 최대 10개의 대기 연결을 큐잉합니다.
처리 루프는 백그라운드 스레드에서 실행됩니다:
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 파싱과 파일 서빙
서버는 기본 HTTP/1.1을 구현합니다. 파서는 원시 요청에서 메서드, 경로, 헤더를 추출합니다.
struct HTTPRequest {
let method: String
let path: String
let headers: [String: String]
let body: Data?
}
GET /files/의 경우 Documents 디렉터리의 파일 목록을 반환합니다:
<html><body><h1>App Files</h1><ul>
<li><a href="/file/filename">filename</a></li>
</ul></body></html>
/file/를 통한 다운로드는 효율성을 위해 sendfile을 사용합니다.
응답은 수동으로 구성됩니다:
"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)!
보안과 제한 사항
- 로컬 네트워크: 서버는 0.0.0.0:8081에 바인딩되며, 기기 IP를 통해 접근 가능합니다.
- 샌드박스: Documents에만 접근 가능. 시스템 파일은 불가.
- 타임아웃: 요청당 30초; 오류 시 연결 종료.
- iOS 제한: 백그라운드 네트워킹은
background-fetch또는 VoIP 권한이 필요합니다.
핵심 요약:
- POSIX API는 macOS/Linux와 동일한 범용 저수준 인터페이스입니다.
- SO_REUSEADDR은 빠른 개발 재시작에 필수입니다.
accept()을 메인 스레드를 블로킹하지 않고 비동기 루프에서 실행하세요.- HTTP 파싱은 최소화: 메서드 + 경로 + Content-Length.
sendfile()은 큰 파일에서write()보다 우수합니다.
테스트와 디버깅
- 실행:
try server.start() - 로컬 접근:
http://localhost:8081/files - 네트워크 접근:
http://192.168.1.100:8081/files - 로그:
print(String(cString: strerror(errno))) - Instruments: 시뮬레이션을 위해 Network Link Conditioner 사용.
이 프로젝트는 소켓부터 웹 인터페이스까지 전체 사이클을 보여줍니다. 코드는 WebSocket이나 사용자 정의 프로토콜로 확장 가능합니다.
— Editorial Team
아직 댓글이 없습니다.