소켓 통신은 네트워크를 통해 서로 다른 시스템 간에 데이터를 교환하기 위한 기본적인 통신 방법입니다
소켓 통신이 사용되는 상황
소켓 통신에 대해 알아보기 전에 어떤 상황에서 사용되는지 알아봅시다.
- 채팅 앱: 사용자가 메시지를 빠르게 실시간으로 주고받을 수 있습니다.
- 온라인 게임: 게임 상태, 플레이어 동작 등을 즉각적으로 전송해 실시간 상호 작용을 가능하게 합니다.
- 라이브 스트리밍: 비디오나 오디오 스트림을 실시간으로 전송할 수 있습니다.
- IoT 및 임베디드 시스템: 분산된 센서 장치들이 중앙 서버로 데이터를 실시간으로 송수신할 수 있습니다.
- 멀티미디어 통신: 인터넷을 통한 음성 통화 VoIP(Void over IP), 화상 회의를 위한 실시간 오디오 및 비디오 데이터 전송(줌) 가능
- 알림 및 푸시 서비스: 카카오톡처럼 서버에서 클라이언트로 즉각적인 알림을 전송할 수 있게 만들 수 있습니다.
- 보안 및 인증 시스템: SSH, TLS/SSL 등 암호화된 인증 시스템을 구현하는데 사용됩니다.
클라이언트-서버
소켓 통신은 여러개의 클라이언트와 하나의 서버를 통해 통신합니다.
소켓이란?
소켓은 프로세스가 네트워크를 통해 데이터를 송수신할 수 있도록 하는 창구이며, 떨어져 있는 두 호스트를 연결해주는 인터페이스의 역할을 합니다. 역할에 따라 서버 소켓, 클라이언트 소켓으로 구분됩니다.
장점
- 실시간성
- 양방향 통신
- 유연성
- 낮은 오버헤드
소켓 통신의 종류 2가지
소켓 통신에는 TCP 소켓과 UDP 소켓이 존재합니다.
TCP 소켓
tcp 소켓은 연결 지향적이며 데이터의 신뢰성과 순서를 보장합니다.
신뢰성과 순서 보장
데이터를 보낼 때 데이터가 손실되지 않고, 전송된 순서대로 도착하도록 보장합니다. 이를 위해 흐름 제어, 오류 검출, 재전송 등의 메커니즘을 사용합니다. (패리티 비트 등이 이에 해당)
데이터 전송 특성
데이터를 보낼 때, 송신자는 수신자로부터 확인 응답(ACK)을 받을 때까지 해당 데이터 전송 상태를 관리합니다.
만약 일정 시간 내에 응답을 받지 못하면, 해당 데이터를 재전송합니다.
UDP 소켓
UDP 소켓은 비연결 지향적이며 빠르지만 데이터의 신뢰성이 보장되지 않습니다.
데이터를 보낼 때 상대방지 받았던 말던 신경쓰지 않고 데이터를 한번 보내고 이후 검증을 하지 않는 특징이 있습니다.
신뢰성 미보장
UDP는 데이터의 도착 여부나 순서를 보장하지 않습니다. 따라서 데이터가 손실되거나 중복되거나 순서가 바뀔 수 있습니다.
데이터 전송 특성
송신자는 데이터를 한번 전송하고 따로 받았는지 확인하지 않습니다. 재전송이나 오류 검출 등의 기능은 제공하지 않으며, 애플리케이션 수준에서 구현이 필요합니다.
TCP와 UDP 사용되는 상황
TCP와 UDP가 어떤 특징을 갖고 있는지 알아봤으며 이제 어떤 상황에서 사용되는지 알아보겠습니다.
TCP
TCP는 연결 지향적이고 신뢰성을 보장하는 프로토콜이기에 다음과 같은 상황에서 사용될 수 있겠습니다.
- 웹 브라우징: HTTP/HTTPS 프로토콜이 TCP 기반으로 하여, 웹 페이지가 누락 없이 로드될 수 있도록 보장합니다.
- 파일 전송: FTP, SFTP와 같은 파일 전송 프로토콜은 데이터가 손상되거나 누락되지 않도록 TCP를 사용합니다.
- 이메일: SMTP, IMAP, POP3 등의 프로토콜은 안정적인 이메일 전송을 위해 TCP를 사용합니다.
- 데이터베이스 통신: 데이터베이스 서버와 통신에서는 안정적인 데이터 전송이 필요하므로 TCP를 사용합니다.
UDP
UDP는 비연결 지향적이고, 신뢰성을 보장하지 않는 프로토콜로, 데이터가 순서에 상관없이 빠르게 전송되는 것을 중요시합니다.
- 실시간 스트리밍: 비디오, 오디오 스트리밍에서는 약간의 패킷 손실이 발생해도 전체적인 흐름에는 큰 영향을 주지 않으므로 UDP를 사용합니다.
- 온라인 게임: 빠른 응답 속도가 중요한 온라인 게임에서는 UDP를 통해 데이터를 빠르게 전송합니다.
- VoIP: 인터넷 통화에서도 실시간 전송이 필요하기에 약간의 지연이나 손실을 감수하고 빠른 전송을 보장하는 UDP를 사용합니다.
- DNS: 도메인 이름을 IP 주소로 변환하는 DNS는 짧은 메시지를 빠르게 전달하기 위해 UDP를 사용합니다.
Swift 에서 제공하는 통신 방법들
Swift 에서는 URLSessionWebSocketTask 와 NWConnection 두가지 방법으로 소켓 통신을 제공하고 있습니다.
URLSessionWebSocketTask
- URLSessionWebSocketTask 는 TCP 만 지원합니다.
- HTTP 기반의 WebSocket 프로토콜만 지원합니다.
- 주로 실시간 채팅, 알림, 실시간 피드 등 빠르고 신뢰성 있는 양방향 데이터 스트림에 필요한 앱에 사용됩니다.
NWConnection
- TCP와 UDP 모두를 지원하며, 원하는 프로토콜을 지정할 수 있습니다.
- 고성능 네트워크 애플리케이션 (실시간 게임, 미디어 스트리밍 등) 및 TCP, UDP 소켓 통신에 필요한 환경에 적합합니다.
- URLSessionWebSocketTask 보다 구현이 약간 더 어려운 대신 다양한 저수준 네트워크 기능을 제공하며, 보다 세밀한 네트워크 제어와 고성능 애플리케이션에 최적화된 기능을 가지고 있습니다.
앞으로 2가지 모두 알아보겠습니다.
URLSessionWebSocketTask 파악하기
WebSockets 프로토콜 표준을 통해 통신하는 URL session task 입니다.
URLSessionWebSocketTask는 TCP 및 TLS 기반 메시지 지향전송 프로토콜을 제공하는 URLSessionTask의 하위 클래스입니다.
이 클래스는 RFC 6455 WebSocket 프로토콜을 따르며 간단하게 웹 소켓 통신을 가능하게 합니다.
URLSessionWebSocketTask 를 생성할 때는 ws: 또는 wss: URL을 통해 WebSocket 연결을 설정하며, 보안이 필요한 경우 TLS를 사용하는 wss: URL을 사용합니다.
연결을 설정 시 웹소켓 핸드셰이크 과정을 거칩니다. 핸드셰이크가 완료되면 데이터 송수신이 가능해집니다.
핸드셰이크란?
악수한다는 의미로 상대방과의 연결이 확인되었을 때를 의미합니다.
비동기 데이터 전송
바이너리 프레임과 UTF-8로 인코딩된 텍스트 프레임을 포함하는 메시지를 보내고 받을 수 있습니다.
send(_:completionHandler:): 비동기적으로 메시지를 송신합니다.
receive(completionHandler:): 비동기적으로 메시지를 수신합니다.
URLSessionTask 와 마찬가지로 리다이렉션 기능을 지원하며, 필요 시 리다이렉션을 수행할 수 있습니다.
URLSessionTaskDelegate 의 메서드를 사용하여 이를 처리합니다.
사용 예시
웹소켓 연결하기
// 메시지 전송 및 수신 시작
func connect() {
let url = URL(string: "ws://127.0.0.1:65432")! // HTTPS 대신 HTTP 웹소켓 사용
let session = URLSession(configuration: .default)
let webSocketTask = session.webSocketTask(with: url)
webSocketTask.resume()
sendMessage("Hello, WebSocket!")
receiveMessage()
}
- 소켓 통신할 URL 를 지정합니다.
- http는 ws, https는 wss 입니다.
- 통신을 관리할 URLSession 을 만들어줍니다.
- 소켓 통신할 URLSession 의 webSocketTask 인스턴스를 만들어줍니다.
- resume 을 통해 webSocket 을 시작해줍니다.
메시지 전송
func sendMessage(_ message: String) {
let message = URLSessionWebSocketTask.Message.string(message)
webSocketTask.send(message) { error in
if let error = error {
print("Error sending message: \(error)")
} else {
print("Message sent")
}
}
}
- URLSessionWebSocketTask.Message 를 통해 송신할 메시지를 만들어줍니다.
- data(Data) 또는 string(String)을 지원합니다.
- send 메서드를 통해 지정한 메시지를 송신합니다.
메시지 수신
func receiveMessage() {
webSocketTask.receive { result in
switch result {
case .failure(let error):
print("Error receiving message: \(error)")
case .success(let message):
switch message {
case .string(let text):
print("Received text: \(text)")
case .data(let data):
print("Received data: \(data)")
@unknown default:
break
}
}
// 다음 메시지를 수신하기 위해 재귀적으로 호출
receiveMessage()
}
}
- 데이터 또한 받을 수 있으며 receive 메서드를 통해 받습니다.
- 받은 메시지는 클로저를 통해 비동기적으로 수행되는데 string 또는 data 타입의 데이터를 받을 수 있습니다.
- receiveMessage 를 재귀적으로 호출해서 계속해서 데이터를 수신할 수 있도록 합니다.
웹소켓 연결의 유효성 확인하기
func startPing() {
self.timer?.invalidate()
self.timer = Timer.scheduledTimer(
withTimeInterval: 10,
repeats: true,
block: { [weak self] _ in self?.ping() }
)
}
func ping() {
webSocketTask?.sendPing { [weak self](error) in
if let error = error {
print("Ping failed: \(error)")
}
self?.startPing()
}
}
- 이 코드는 웹소켓 연결의 유효성을 주기적으로 확인하는 데 사용됩니다. 일정 시간마다 핑을 보내어 연결 상태가 유지되고 있는지 확인하며, 핑 실패 시 네트워크 문제나 서버 상태에 따른 조치를 취할 수 있도록 합니다.
파이썬 소켓 통신 구현
파이썬으로 ios 와 통신할 수 있는 TCP를 구축해보겠습니다.
소켓(객체) 생성 방법
import socket
s = socket.socket(주소유형, 소켓타입)
주소 유형
socket.AF_INET IPv4
socket.AF_INET6 IPv6
소켓 타입
socket.SOCK_STREAM TCP(Connection based stream)
socket.SOCK_DGRAM UDP(Datagram)
예시
from socket import *
s1 = socket(AF_INET, SOCK_STREAM)// IPv4, TCP 사용
s2 = socket(AF_INET, SOCK_DGRAM) // IPv4, UDP 사용
다시 한번 TCP 소켓 프로그래밍 순서를 알아보겠습니다.
TCP 소켓 프로그래밍 순서
- 소켓을 생성한다.
- 소켓과 종단점을 결합한다. (IP 주소 + 포트 번호 설정)
- 클라이언트 연결을 대기한다.
- 클라이언트 연결을 수용한다.
- 데이터를 송수신한다.
파이썬을 이용한 웹소켓 통신
파이썬은 websockets 이라는 라이브러리를 제공하여 웹소켓 통신을 가능하게 합니다.
- WebSocket 프로토콜을 위한 고수준 라이브러리로, 실시간 양방향 통신을 지원하며 주로 웹 애플리케이션에서 사용됩니다.
- HTTP 핸드셰이크를 통해 연결을 설정하고 이후 WebSocket을 통한 지속적인 양방향 데이터 전송을 지원합니다.
- 브라우저와 서버 간에 실시간으로 메시지를 주고받을 때 유용하며, 비동기 통신이 기본입니다.
- • WebSocket 프로토콜은 연결이 수립된 이후에는 TCP를 기반으로 하여 효율적인 실시간 통신을 제공합니다.
서버 시작하기
async def start_server():
server = await websockets.serve(handle_client, "127.0.0.1", 9000)
print("[*] 웹소켓 서버 시작: ws://127.0.0.1:9000")
await server.wait_closed()
if __name__ == "__main__":
asyncio.run(start_server())
- websockets.serve 는 websockets 라이브러리에서 WebSocket 서버를 시작하는 함수입니다.
- 이 함수는 서버를 설정하고 클라이언트의 연결 요청을 수락하기 위해 지정된 IP 주소와 포트에서 대기합니다.
- await 을 통해서 서버가 종료될 때까지 대기합니다.
클라이언트 처리하기
async def handle_client(websocket):
clients.add(websocket)
client_address = websocket.remote_address
print(f"[+] 새로운 연결: {client_address}")
try:
async for message in websocket:
print(f"[{client_address}] 메시지: {message}")
# 클라이언트에서 받은 메시지를 JSON으로 파싱
parsed_message = json.loads(message)
message_with_sender = json.dumps(parsed_message)
# 모든 클라이언트에게 메시지 전송
await broadcast(message_with_sender, websocket)
except websockets.ConnectionClosed:
print(f"[-] 연결 종료: {client_address}")
finally:
clients.remove(websocket)
- 클라이언트가 새로 들어오면 handle_client 가 수행되며 클라이언트로부터 데이터를 받을 때마다 실행됩니다.
- 클라이언트를 추가해주고 받은 메시지를 json으로 파싱 후 json 데이터를 다른 클라이언트들에게 전송합니다.
전파하기
async def broadcast(message, sender_websocket):
for client in clients:
if client != sender_websocket: # 보낸 클라이언트에게는 전송하지 않음
await client.send(message)
- 클라이언트로부터 받은 메시지를 다른 클라이언트들에게 전송합니다.
연결 유지하기
private func startPing() {
self.timer?.invalidate()
self.timer = Timer.scheduledTimer(
withTimeInterval: 10,
repeats: true,
block: { [weak self] _ in self?.ping() }
)
}
private func ping() {
webSocket?.sendPing { [weak self](error) in
if let error = error {
print("Ping failed: \(error)")
}
self?.startPing()
}
}
- 이부분 또한 중요한 부분입니다. 연결을 유지하기 위해서는 클라이언트가 서버에 지속적으로 핑을 보내서 연결할 의향이 있다는 사실을 알려야합니다. 그렇지 않으면 연결을 끊어버립니다.
- 10초 간격으로 핑을 전송하도록 만듭니다.
'SwiftUI' 카테고리의 다른 글
SwiftUI - 접근제어자 (0) | 2024.11.17 |
---|---|
SwiftUI - 소켓 통신으로 영상 전송받기 (0) | 2024.11.13 |
SwiftUI - 커스텀 카메라 (0) | 2024.11.11 |
WidgetKit - UserDefault 로 위젯과 앱 데이터 공유하기 (0) | 2024.10.23 |
WidgetKit 에서 SwiftData 로 저장된 데이터 사용하기 (0) | 2024.10.22 |