먼저 Socket.IO를 사용해서 웹소켓 연결을 설정하고 통신을 처리하는 코드를 살펴보자!
import SocketIO
// SocketManager: 소켓IO에서 소켓 연결을 관리하는 주요 객체
// 로컬서버에서 8080포트를 통해 웹소켓 서버에 연결
// config: [.log(true): 로깅활성화 / 소켓 연결, 메시지 전송, 수신 등과 관련된 다양한 활동을 콘솔에 출력하는 것을 의미
// .compress: 데이터압축 / 소켓을 통해 전송되는 데이터를 압축하여 전송하는 것을 의미
let manager = SocketManager(socketURL: URL(string: "http://localhost:8080")!, config: [.log(true), .compress])
// 기본 소켓을 가져옴
let socket = manager.defaultSocket
// socket.on로 소켓 연결, .connect 이벤트가 발생 시 "socket connected" print함
socket.on(clientEvent: .connect) {data, ack in
print("socket connected")
}
// "currentAmount" 이벤트가 수신되었을 때
socket.on("currentAmount") {data, ack in
// data 배열에서 첫 번째 요소를 Double로 변환하여 cur 변수에 저장
guard let cur = data[0] as? Double else { return }
// emitWithAck 메소드: 클라이언트에서 이벤트를 서버로 전송하고, 서버로부터 확인 응답을 기다리는 기능 제공
// 클라이언트에서 서버로 "canUpdate" 이벤트와 cur 변수 전달
// 타임아웃 없음
socket.emitWithAck("canUpdate", cur).timingOut(after: 0) {data in
// 서버에서 받은 데이터의 첫번째 요소 확인, 데이터가 없거나 SocketAckValue가 noAck이면 타임아웃 처리
if data.first as? String ?? "passed" == SocketAckValue.noAck {
// Handle ack timeout
}
// 데이터가 있으면 서버로 "update" 이벤트와 cur 변수에 2.50를 증가시켜서 새로운 데이터 전달
socket.emit("update", ["amount": cur + 2.50])
}
// ack: 클라이언트가 서버로부터 이벤트를 수신했을 때 함께 제공되는 확인 응답, 이 객체를 이용해서 서버에 응답을 보냄
// 클라이언트가 서버로부터 "currentAmount" 이벤트를 수신했음을 확인 응답으로 보냄
ack.with("Got your currentAmount", "dude")
}
// 소켓 연결 시작
socket.connect()
SocketIO 버전 변화를 살펴보자!
클라이언트 생성: 소켓 클라이언트를 직접 생성하고 관리했다.
네임스페이스: 각 클라이언트는 고유의 엔진을 가졌고, 네임스페이스를 사용하기 위해 여러 개의 클라이언트를 생성해야 했다.
let defaultSocket = SocketIOClient(socketURL: URL(string: "http://localhost:8080")!)
let namespaceSocket = SocketIOClient(socketURL: URL(string: "http://localhost:8080")!, config: [.nsp("/swift")])
💡 Namespace와 Room이란?
Namespace란 namespace안에 있는 소켓들을 room으로 쪼개어 나눈 것이다.
서버에서 제공하는 서비스나 기능들을 구분해서 사용하거나, 클라이언트가 특정 메시지를 받을 필요가 없는 경우를 구분해서 관리할 수 있다.
Room이란 Namespace의 하위 개념으로 예를 들어, /chat 네임스페이스 안에는 general, private, group1 등 여러 개의 룸이 있을 수 있다. 특정 룸에 신호를 보내면 룸 안의 소켓들은 신호를 받지만, 다른 룸에 소속된 소켓들은 신호를 받지 못한다.
SocketManager 도입: 소켓 연결을 관리하는 SocketManager 객체가 도입되었다.
클라이언트 생성: 직접 소켓 클라이언트를 생성하는 대신 SocketManager를 통해 소켓을 생성하고 관리한다.
네임스페이스: 네임스페이스가 필요하지 않으면 defaultSocket 속성을 사용하고, 네임스페이스가 필요한 경우 socket(forNamespace:) 메서드를 호출한다.
let manager = SocketManager(socketURL: URL(string: "http://localhost:8080")!)
let defaultSocket = manager.defaultSocket
let namespaceSocket = manager.socket(forNamespace: "/swift")
socket(forNamespace:)
메소드를 여러번 호출해도 처음 호출시 반환된 동일한 소켓 인스턴스를 반환하기 때문에 같은 네임스페이스 내에서 여러 개의 소켓이 필요할 때는 여러개의 매니저를 사용해야 한다.
class Manager {
let socketManager = SocketManager(socketURL: someURL)
func addHandlers() {
let socket = socketManager.socket(forNamespace: "/swift")
// 핸들러 추가
}
}
let manager = SocketManager(socketURL: myURL)
manager.connect()
let manager = SocketManager(socketURL: myURL)
let namespaceSocket = manager.socket(forNamespace: "/swift")
namespaceSocket.connect()
Objective-C가 지원되지않고 Swift를 사용해야 한다.
클라이언트는 여러 버전의 Socket.IO를 지원하고 기본적으로 Socket.IO 3 서버를 지원한다. 서버가 Socket.IO 2인 경우에는 매니저에 .version(.two) 옵션을 전달해야 한다.
let manager = SocketManager(socketURL: URL(string: "http://localhost:8087/")!, config: [.version(.two)])