웹 소켓의 핵심 기능은 클라이언트와 서버 간의 실시간 메시지 교환입니다. 이 섹션에서는 텍스트 메시지와 바이너리 데이터를 전송하는 방법, 메시지를 수신하고 처리하는 방법, 그리고 효율적인 메시지 형식 및 직렬화 기법에 대해 알아봅니다. 이를 통해 웹 소켓을 활용한 양방향 통신 애플리케이션을 효과적으로 구현할 수 있습니다.
웹 소켓은 텍스트 기반 메시지를 쉽게 전송할 수 있는 방법을 제공합니다. 텍스트 메시지는 일반적으로 문자열 형태로 전송되며, JSON과 같은 형식을 사용하여 구조화된 데이터를 교환하는 데 적합합니다.
클라이언트에서 서버로 텍스트 메시지를 전송하는 가장 기본적인 방법은 WebSocket 객체의 send() 메서드를 사용하는 것입니다:
const socket: WebSocket = new WebSocket('wss://example.com/chat');
// 연결이 열린 후 메시지 전송
socket.addEventListener('open', () => {
// 단순 문자열 전송
socket.send('안녕하세요!');
// JSON 형식의 구조화된 데이터 전송
const message = {
type: 'chat',
content: '안녕하세요!',
timestamp: new Date().toISOString()
};
socket.send(JSON.stringify(message));
});메시지를 전송하기 전에 연결 상태를 확인하는 것이 좋습니다:
function sendMessage(socket: WebSocket, message: string | object): boolean {
if (socket.readyState !== WebSocket.OPEN) {
console.error('웹 소켓이 열려있지 않습니다.');
return false;
}
try {
// 객체인 경우 JSON 문자열로 변환
const data = typeof message === 'object' ? JSON.stringify(message) : message;
socket.send(data);
return true;
} catch (error) {
console.error('메시지 전송 중 오류 발생:', error);
return false;
}
}Spring WebSocket을 사용한 예시:
public class ChatWebSocketHandler extends TextWebSocketHandler {
private final ConcurrentHashMap<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws IOException {
String sessionId = session.getId();
sessions.put(sessionId, session);
// 단일 클라이언트에 메시지 전송
session.sendMessage(new TextMessage("서버에 연결되었습니다!"));
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
// 모든 클라이언트에 메시지 브로드캐스트
sessions.values().stream()
.filter(WebSocketSession::isOpen)
.forEach(client -> {
try {
client.sendMessage(message);
} catch (IOException e) {
// 로그 기록 및 클라이언트 연결 상태 관리
logger.error("메시지 전송 실패: " + e.getMessage(), e);
// 연결 문제가 있는 클라이언트 세션 관리
handleFailedMessageDelivery(client, e);
}
});
}
}sequenceDiagram
participant Client as 클라이언트
participant Server as 서버
Client->>Server: 연결 요청
Server->>Client: 연결 수락
Client->>Server: 텍스트 메시지 전송
Note over Server: 메시지 처리
Server->>Client: 응답 메시지 전송
Client->>Server: JSON 형식 메시지 전송
Note over Server: JSON 파싱 및 처리
Server->>Client: JSON 응답 전송
Note over Client,Server: 양방향 통신 계속...
텍스트 메시지 외에도 웹 소켓은 바이너리 데이터 전송을 지원합니다. 이는 이미지, 오디오, 비디오 또는 기타 바이너리 형식의 데이터를 효율적으로 전송할 때 유용합니다.
클라이언트에서 바이너리 데이터를 전송하려면 ArrayBuffer, Blob 또는 TypedArray 객체를 send() 메서드에 전달합니다:
const socket: WebSocket = new WebSocket('wss://example.com/binary');
socket.addEventListener('open', () => {
// ArrayBuffer 전송
const buffer = new ArrayBuffer(4);
const view = new Uint32Array(buffer);
view[0] = 42;
socket.send(buffer);
// Blob 전송 (예: 파일 업로드)
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
if (fileInput.files && fileInput.files.length > 0) {
const file = fileInput.files[0];
socket.send(file); // Blob 객체 직접 전송
}
// TypedArray 전송
const uint8Array = new Uint8Array([1, 2, 3, 4, 5]);
socket.send(uint8Array);
});Spring WebSocket을 사용한 바이너리 메시지 처리:
public class BinaryWebSocketHandler extends BinaryWebSocketHandler {
@Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws IOException {
ByteBuffer buffer = message.getPayload();
System.out.println("바이너리 메시지 수신: " + buffer.capacity() + " 바이트");
// 바이너리 데이터 처리
// ...
// 바이너리 응답 전송
ByteBuffer response = ByteBuffer.wrap(new byte[]{1, 2, 3, 4});
session.sendMessage(new BinaryMessage(response));
}
}웹 소켓을 통한 파일 전송 구현 예시:
// 클라이언트 측 코드
const socket: WebSocket = new WebSocket('wss://example.com/file-transfer');
const fileInput = document.getElementById('fileInput') as HTMLInputElement;
const sendButton = document.getElementById('sendButton') as HTMLButtonElement;
sendButton.addEventListener('click', () => {
if (fileInput.files && fileInput.files.length > 0) {
const file = fileInput.files[0];
// 파일 정보 먼저 전송
const fileInfo = {
type: 'file-info',
name: file.name,
size: file.size,
mimeType: file.type
};
socket.send(JSON.stringify(fileInfo));
// 파일 데이터 전송
const reader = new FileReader();
reader.onload = (e) => {
if (e.target && e.target.result) {
// 파일 내용 전송
socket.send(e.target.result);
console.log('파일 전송 완료');
}
};
reader.readAsArrayBuffer(file);
}
});// 서버 측 코드 (Spring WebSocket)
public class FileTransferHandler extends BinaryWebSocketHandler {
private Map<String, FileTransferSession> sessions = new ConcurrentHashMap<>();
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// JSON 메시지 파싱
ObjectMapper mapper = new ObjectMapper();
JsonNode json = mapper.readTree(message.getPayload());
if (!"file-info".equals(json.path("type").asText())) {
return;
}
// 파일 정보 저장
String sessionId = session.getId();
String fileName = json.path("name").asText();
long fileSize = json.path("size").asLong();
String mimeType = json.path("mimeType").asText();
// 파일 전송 세션 생성
FileTransferSession transferSession = new FileTransferSession(fileName, fileSize, mimeType);
sessions.put(sessionId, transferSession);
System.out.println("파일 정보 수신: " + fileName + " (" + fileSize + " 바이트)");
}
@Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
String sessionId = session.getId();
FileTransferSession transferSession = sessions.get(sessionId);
if (transferSession == null) {
return;
}
// 바이너리 데이터를 파일 스트림에 추가
ByteBuffer buffer = message.getPayload();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
transferSession.appendData(bytes);
// 파일 수신 완료 확인
if (!transferSession.isComplete()) {
return;
}
System.out.println("파일 수신 완료: " + transferSession.getFileName());
// 파일 저장 또는 처리
saveFile(transferSession.getFileName(), transferSession.getData());
// 응답 전송
session.sendMessage(new TextMessage("{\"status\":\"success\",\"message\":\"파일 수신 완료\"}"));
// 리소스 정리
sessions.remove(sessionId);
}
private void saveFile(String fileName, byte[] data) throws IOException {
Path path = Paths.get("uploads", fileName);
Files.createDirectories(path.getParent());
Files.write(path, data);
}
// 파일 전송 세션 클래스
private static class FileTransferSession {
private String fileName;
private long fileSize;
private String mimeType;
private ByteArrayOutputStream fileData;
public FileTransferSession(String fileName, long fileSize, String mimeType) {
this.fileName = fileName;
this.fileSize = fileSize;
this.mimeType = mimeType;
this.fileData = new ByteArrayOutputStream();
}
public void appendData(byte[] data) throws IOException {
fileData.write(data);
}
public boolean isComplete() {
return fileData.size() >= fileSize;
}
public String getFileName() {
return fileName;
}
public byte[] getData() {
return fileData.toByteArray();
}
}
}웹 소켓 메시지를 수신하고 처리하는 방법은 클라이언트와 서버 측에서 약간 다릅니다.
클라이언트에서는 message 이벤트를 통해 메시지를 수신합니다:
const socket: WebSocket = new WebSocket('wss://example.com/chat');
socket.addEventListener('message', (event: MessageEvent) => {
// 텍스트 메시지 처리
if (typeof event.data === 'string') {
console.log('텍스트 메시지 수신:', event.data);
try {
// JSON 메시지 파싱 시도
const jsonData = JSON.parse(event.data);
handleJsonMessage(jsonData);
} catch (error) {
// 일반 텍스트 메시지 처리
handleTextMessage(event.data);
}
}
// 바이너리 메시지 처리
else if (event.data instanceof ArrayBuffer) {
console.log('ArrayBuffer 수신:', event.data);
handleArrayBuffer(event.data);
}
else if (event.data instanceof Blob) {
console.log('Blob 수신:', event.data);
handleBlob(event.data);
}
});
function handleJsonMessage(data: any): void {
// 메시지 유형에 따른 처리
switch (data.type) {
case 'chat':
displayChatMessage(data.sender, data.content);
break;
case 'notification':
showNotification(data.content);
break;
case 'status':
updateStatus(data.status);
break;
default:
console.warn('알 수 없는 메시지 유형:', data.type);
}
}
function handleTextMessage(text: string): void {
// 일반 텍스트 메시지 처리
displayChatMessage('시스템', text);
}
function handleArrayBuffer(buffer: ArrayBuffer): void {
// ArrayBuffer 처리 예시
const view = new Uint8Array(buffer);
console.log('첫 번째 바이트:', view[0]);
}
function handleBlob(blob: Blob): void {
// Blob 처리 예시 (예: 이미지 표시)
if (blob.type.startsWith('image/')) {
const url = URL.createObjectURL(blob);
displayImage(url);
}
}
function displayChatMessage(sender: string, content: string): void {
// DOM에 채팅 메시지 추가
const chatBox = document.getElementById('chatBox');
if (chatBox) {
const messageElement = document.createElement('div');
messageElement.innerHTML = `<strong>${sender}:</strong> ${content}`;
chatBox.appendChild(messageElement);
chatBox.scrollTop = chatBox.scrollHeight;
}
}
function showNotification(content: string): void {
// 알림 표시
alert(content);
}
function updateStatus(status: string): void {
// 상태 업데이트
const statusElement = document.getElementById('status');
if (statusElement) {
statusElement.textContent = status;
}
}
function displayImage(url: string): void {
// 이미지 표시
const imageContainer = document.getElementById('imageContainer');
if (imageContainer) {
const img = document.createElement('img');
img.src = url;
img.style.maxWidth = '100%';
imageContainer.appendChild(img);
}
}서버 측에서는 메서드 오버라이딩을 통해 메시지 수신을 처리합니다:
Spring WebSocket 사용:
public class ChatWebSocketHandler extends TextWebSocketHandler {
private final ConcurrentHashMap<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String payload = message.getPayload();
System.out.println("텍스트 메시지 수신: " + payload);
try {
// JSON 메시지 파싱 시도
JsonNode jsonNode = objectMapper.readTree(payload);
handleJsonMessage(jsonNode, session);
} catch (Exception e) {
// 일반 텍스트 메시지 처리
handleTextMessage(payload, session);
}
}
private void handleJsonMessage(JsonNode json, WebSocketSession session) throws IOException {
String type = json.path("type").asText("unknown");
switch (type) {
case "chat":
// 모든 클라이언트에 메시지 브로드캐스트
TextMessage chatMessage = new TextMessage(objectMapper.writeValueAsString(json));
sessions.values().stream()
.filter(WebSocketSession::isOpen)
.forEach(client -> {
try {
client.sendMessage(chatMessage);
} catch (IOException e) {
// 로그 기록 및 클라이언트 연결 상태 관리
logger.error("채팅 메시지 전송 실패: " + e.getMessage(), e);
// 연결 문제가 있는 클라이언트 세션 관리
handleFailedMessageDelivery(client, e);
}
});
break;
case "private":
String recipient = json.path("recipient").asText("");
TextMessage privateMessage = new TextMessage(objectMapper.writeValueAsString(json));
// 특정 사용자에게만 메시지 전송
sessions.values().stream()
.filter(WebSocketSession::isOpen)
.filter(client -> getUserId(client).equals(recipient))
.findFirst()
.ifPresent(client -> {
try {
client.sendMessage(privateMessage);
} catch (IOException e) {
// 로그 기록 및 클라이언트 연결 상태 관리
logger.error("개인 메시지 전송 실패: " + e.getMessage(), e);
// 발신자에게 전송 실패 알림
notifySenderOfFailedDelivery(session, recipient, e);
}
});
break;
default:
System.out.println("알 수 없는 메시지 유형: " + type);
}
}
private void handleTextMessage(String text, WebSocketSession session) throws IOException {
// 일반 텍스트 메시지 처리
TextMessage message = new TextMessage(text);
sessions.values().stream()
.filter(WebSocketSession::isOpen)
.forEach(client -> {
try {
client.sendMessage(message);
} catch (IOException e) {
// 로그 기록 및 클라이언트 연결 상태 관리
logger.error("일반 텍스트 메시지 전송 실패: " + e.getMessage(), e);
// 연결 문제가 있는 클라이언트 세션 관리
handleFailedMessageDelivery(client, e);
}
});
}
private String getUserId(WebSocketSession session) {
// 세션에서 사용자 ID 추출 (구현에 따라 다름)
return session.getAttributes().get("userId").toString();
}
}graph TD
A[메시지 수신] --> B{메시지 유형?}
B -->|텍스트| C[텍스트 메시지 처리]
B -->|바이너리| D[바이너리 메시지 처리]
C --> E{JSON 형식?}
E -->|예| F[JSON 파싱]
E -->|아니오| G[일반 텍스트 처리]
F --> H{메시지 타입?}
H -->|채팅| I[채팅 메시지 처리]
H -->|상태| J[상태 업데이트 처리]
H -->|알림| K[알림 처리]
H -->|기타| L[기타 메시지 처리]
D --> M{데이터 유형?}
M -->|ArrayBuffer| N[ArrayBuffer 처리]
M -->|Blob| O[Blob 처리]
style A fill:#3cb371,stroke:#333,stroke-width:2px
style B fill:#daa520,stroke:#333,stroke-width:2px
style E fill:#daa520,stroke:#333,stroke-width:2px
style H fill:#daa520,stroke:#333,stroke-width:2px
style M fill:#daa520,stroke:#333,stroke-width:2px
웹 소켓 통신에서 채널은 단일 웹 소켓 연결 내에서 여러 논리적 통신 경로를 제공하는 개념입니다. 이를 통해 하나의 WebSocket 인스턴스로 여러 유형의 메시지를 구조적으로 관리할 수 있습니다.
웹 소켓 채널은 단일 연결을 통해 여러 종류의 데이터를 구분하여 전송할 수 있게 해줍니다:
- 메시지 분류: 다양한 유형의 메시지를 논리적으로 분리
- 코드 구조화: 각 채널별로 독립적인 처리 로직 구현 가능
- 확장성: 새로운 기능 추가 시 기존 채널에 영향 없이 새 채널 추가 가능
- 유지보수성: 관심사 분리를 통한 코드 유지보수 용이성 증가
graph TD
A[단일 WebSocket 연결] --> B[채팅 채널]
A --> C[알림 채널]
A --> D[상태 업데이트 채널]
A --> E[파일 전송 채널]
style A fill:#c35b5b,stroke:#333,stroke-width:2px
style B fill:#3cb371,stroke:#333,stroke-width:2px
style C fill:#3cb371,stroke:#333,stroke-width:2px
style D fill:#3cb371,stroke:#333,stroke-width:2px
style E fill:#3cb371,stroke:#333,stroke-width:2px
웹 소켓 프로토콜 자체는 채널 개념을 직접 지원하지 않지만, 메시지 형식을 통해 논리적 채널을 구현할 수 있습니다:
가장 간단한 방법은 각 메시지에 채널 식별자나 타입을 포함시키는 것입니다:
// 채널 기반 메시지 구조
interface ChannelMessage {
channel: string; // 채널 식별자
data: any; // 채널별 데이터
}
// 채널별 메시지 전송 함수
function sendToChannel(socket: WebSocket, channel: string, data: any): void {
const message: ChannelMessage = {
channel,
data
};
socket.send(JSON.stringify(message));
}
// 사용 예시
sendToChannel(socket, 'chat', { sender: 'user1', content: '안녕하세요!' });
sendToChannel(socket, 'notification', { type: 'info', message: '새 메시지가 도착했습니다.' });
sendToChannel(socket, 'status', { user: 'user2', status: 'online' });더 구조화된 접근 방식으로, 채널 관리자 클래스를 구현할 수 있습니다:
class WebSocketChannelManager {
private socket: WebSocket;
private channels: Map<string, (data: any) => void> = new Map();
constructor(url: string) {
this.socket = new WebSocket(url);
this.socket.addEventListener('message', this.handleMessage.bind(this));
}
// 채널 구독
subscribe(channel: string, handler: (data: any) => void): void {
this.channels.set(channel, handler);
}
// 채널 구독 해제
unsubscribe(channel: string): void {
this.channels.delete(channel);
}
// 채널로 메시지 전송
send(channel: string, data: any): void {
if (this.socket.readyState !== WebSocket.OPEN) {
return;
}
const message = {
channel,
data
};
this.socket.send(JSON.stringify(message));
}
// 수신된 메시지 처리
private handleMessage(event: MessageEvent): void {
try {
const message = JSON.parse(event.data);
const { channel, data } = message;
// 해당 채널의 핸들러 호출
const handler = this.channels.get(channel);
if (!handler) {
return;
}
handler(data);
} catch (error) {
console.error('메시지 처리 오류:', error);
}
}
// 연결 종료
close(): void {
this.socket.close();
}
}
// 사용 예시
const channelManager = new WebSocketChannelManager('wss://example.com/socket');
// 채팅 채널 구독
channelManager.subscribe('chat', (data) => {
console.log(`${data.sender}: ${data.content}`);
});
// 알림 채널 구독
channelManager.subscribe('notification', (data) => {
showNotification(data.message);
});
// 채널로 메시지 전송
channelManager.send('chat', { sender: 'user1', content: '안녕하세요!' });서버 측에서도 채널 개념을 구현하여 메시지를 처리할 수 있습니다:
public class ChannelWebSocketHandler extends TextWebSocketHandler {
private final ConcurrentHashMap<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String payload = message.getPayload();
// 채널 메시지 파싱
JsonNode jsonNode = objectMapper.readTree(payload);
String channel = jsonNode.path("channel").asText();
JsonNode data = jsonNode.path("data");
// 채널별 처리
switch (channel) {
case "chat":
handleChatChannel(session, data);
break;
case "notification":
handleNotificationChannel(session, data);
break;
case "status":
handleStatusChannel(session, data);
break;
default:
System.out.println("알 수 없는 채널: " + channel);
}
}
private void handleChatChannel(WebSocketSession session, JsonNode data) throws IOException {
// 채팅 메시지 처리 로직
String sender = data.path("sender").asText();
String content = data.path("content").asText();
// 모든 클라이언트에 브로드캐스트
broadcastToChannel("chat", data);
}
private void handleNotificationChannel(WebSocketSession session, JsonNode data) throws IOException {
// 알림 메시지 처리 로직
broadcastToChannel("notification", data);
}
private void handleStatusChannel(WebSocketSession session, JsonNode data) throws IOException {
// 상태 업데이트 처리 로직
broadcastToChannel("status", data);
}
private void broadcastToChannel(String channel, JsonNode data) throws IOException {
Map<String, Object> messageMap = new HashMap<>();
messageMap.put("channel", channel);
messageMap.put("data", objectMapper.convertValue(data, Map.class));
TextMessage message = new TextMessage(objectMapper.writeValueAsString(messageMap));
sessions.values().stream()
.filter(WebSocketSession::isOpen)
.forEach(client -> {
try {
client.sendMessage(message);
} catch (IOException e) {
// 로그 기록 및 클라이언트 연결 상태 관리
logger.error("채널 메시지 전송 실패: " + e.getMessage(), e);
// 연결 문제가 있는 클라이언트 세션 관리
handleFailedMessageDelivery(client, e);
}
});
}
}웹 소켓 채널과 멀티플렉싱 확장은 유사하지만 다른 개념입니다:
- 채널: 애플리케이션 레벨에서 구현되는 논리적 분리로, 메시지 형식을 통해 구현됩니다.
- 멀티플렉싱 확장: 프로토콜 레벨에서 구현되는 물리적 분리로, 웹 소켓 프레임 헤더에 채널 ID를 추가합니다.
채널은 별도의 확장 없이 표준 웹 소켓 프로토콜만으로 구현 가능하며, 대부분의 웹 소켓 애플리케이션에서 사용되는 패턴입니다.
효율적인 웹 소켓 통신을 위해서는 적절한 메시지 형식과 직렬화 방법을 선택하는 것이 중요합니다.
JSON은 웹 소켓 메시지에 가장 널리 사용되는 형식입니다. 이는 가독성이 좋고 다양한 언어에서 쉽게 처리할 수 있기 때문입니다.
// 클라이언트 측 JSON 메시지 예시
const chatMessage = {
type: 'chat',
sender: 'user123',
content: '안녕하세요!',
timestamp: new Date().toISOString()
};
socket.send(JSON.stringify(chatMessage));// 서버 측 JSON 처리 예시 (Jackson 사용)
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> jsonMessage = new HashMap<>();
jsonMessage.put("type", "chat");
jsonMessage.put("sender", "server");
jsonMessage.put("content", "환영합니다!");
jsonMessage.put("timestamp", Instant.now().toString());
session.sendMessage(new TextMessage(mapper.writeValueAsString(jsonMessage)));효율적인 통신을 위해 일관된 메시지 스키마를 설계하는 것이 좋습니다:
// 기본 메시지 인터페이스
interface BaseMessage {
type: string;
timestamp: string;
}
// 채팅 메시지
interface ChatMessage extends BaseMessage {
type: 'chat';
sender: string;
content: string;
room?: string;
}
// 상태 메시지
interface StatusMessage extends BaseMessage {
type: 'status';
userId: string;
status: 'online' | 'offline' | 'away';
}
// 오류 메시지
interface ErrorMessage extends BaseMessage {
type: 'error';
code: number;
message: string;
}
// 메시지 타입 유니온
type WebSocketMessage = ChatMessage | StatusMessage | ErrorMessage;
// 메시지 생성 함수
function createChatMessage(sender: string, content: string, room?: string): ChatMessage {
return {
type: 'chat',
timestamp: new Date().toISOString(),
sender,
content,
room
};
}텍스트 기반 JSON 외에도 바이너리 직렬화 형식을 사용하여 메시지 크기를 줄이고 성능을 향상시킬 수 있습니다:
- Protocol Buffers (protobuf): Google에서 개발한 효율적인 바이너리 직렬화 형식
- MessagePack: JSON과 유사하지만 더 작고 빠른 바이너리 형식
- CBOR (Concise Binary Object Representation): JSON 데이터 모델을 기반으로 한 바이너리 형식
MessagePack 예시:
// 클라이언트 측 (MessagePack 사용)
import * as msgpack from 'msgpack-lite';
const socket: WebSocket = new WebSocket('wss://example.com/msgpack');
socket.addEventListener('open', () => {
const message = {
type: 'chat',
sender: 'user123',
content: '안녕하세요!',
timestamp: new Date().toISOString()
};
// MessagePack으로 직렬화
const encoded = msgpack.encode(message);
socket.send(encoded);
});
socket.addEventListener('message', (event: MessageEvent) => {
if (event.data instanceof ArrayBuffer) {
// MessagePack 디코딩
const decoded = msgpack.decode(new Uint8Array(event.data));
console.log('수신된 메시지:', decoded);
}
});// 서버 측 (MessagePack 사용)
public class MsgPackHandler extends BinaryWebSocketHandler {
private MessagePack msgpack = new MessagePack();
@Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws IOException {
// MessagePack 디코딩
ByteBuffer buffer = message.getPayload();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
Map<String, Object> decodedMessage = msgpack.read(bytes, new ValueTypeDecoderBuilder().build());
System.out.println("수신된 메시지: " + decodedMessage);
// 응답 생성
Map<String, Object> response = new HashMap<>();
response.put("type", "response");
response.put("status", "success");
response.put("timestamp", Instant.now().toString());
// MessagePack으로 직렬화
byte[] encoded = msgpack.write(response);
session.sendMessage(new BinaryMessage(ByteBuffer.wrap(encoded)));
}
}대용량 메시지의 경우 압축을 사용하여 전송 크기를 줄일 수 있습니다:
// 클라이언트 측 (pako 라이브러리 사용)
import * as pako from 'pako';
function sendCompressedMessage(socket: WebSocket, message: object): void {
const jsonString = JSON.stringify(message);
// 메시지 압축
const compressed = pako.deflate(jsonString);
socket.send(compressed.buffer);
}
socket.addEventListener('message', (event: MessageEvent) => {
if (event.data instanceof ArrayBuffer) {
try {
// 압축 해제
const decompressed = pako.inflate(new Uint8Array(event.data), { to: 'string' });
const message = JSON.parse(decompressed);
console.log('압축 해제된 메시지:', message);
} catch (error) {
console.error('압축 해제 오류:', error);
}
}
});- 메시지 전송 방식: 웹 소켓은
send()메서드를 통해 텍스트와 바이너리 데이터를 모두 전송할 수 있으며, 연결 상태를 확인한 후 전송하는 것이 중요합니다. - 데이터 형식: JSON은 가장 널리 사용되는 메시지 형식이지만, 성능이 중요한 경우 MessagePack이나 Protocol Buffers와 같은 바이너리 직렬화 형식을 고려할 수 있습니다.
- 메시지 처리: 클라이언트는
message이벤트 리스너를, 서버는 핸들러 메서드를 통해 수신된 메시지를 처리하며, 메시지 유형에 따라 적절한 로직을 실행합니다. - 웹 소켓 채널: 단일 웹 소켓 연결 내에서 여러 논리적 통신 경로를 구현하여 메시지를 분류하고 코드를 구조화할 수 있으며, 이는 애플리케이션 레벨에서 메시지 형식을 통해 구현됩니다.
- 파일 전송: 웹 소켓을 통해 파일을 전송할 때는 메타데이터(파일 이름, 크기 등)를 먼저 전송한 후 바이너리 데이터를 전송하는 패턴을 사용하는 것이 효과적입니다.
-
웹 소켓에서 텍스트 메시지를 전송하는 올바른 방법은?
- socket.transmit("Hello");
- socket.send("Hello");
- socket.postMessage("Hello");
- socket.emit("message", "Hello");
-
웹 소켓을 통해 바이너리 데이터를 전송할 때 사용할 수 있는 형식이 아닌 것은?
- ArrayBuffer
- Blob
- TypedArray
- JSON String
-
웹 소켓 채널에 대한 설명으로 올바른 것은?
- 웹 소켓 프로토콜에 기본적으로 내장된 기능이다
- 채널은 애플리케이션 레벨에서 메시지 형식을 통해 구현된다
- 채널을 사용하려면 반드시 웹 소켓 확장이 필요하다
- 하나의 웹 소켓 연결은 최대 4개의 채널만 지원한다
-
웹 소켓 채널 구현 방법으로 올바른 것을 모두 고르세요. (복수 응답)
- 메시지에 채널 식별자를 포함시켜 구현한다
- 각 채널마다 별도의 웹 소켓 연결을 생성해야 한다
- 채널 관리자 클래스를 통해 구조화된 방식으로 구현할 수 있다
- 서버 측에서 채널별로 다른 처리 로직을 구현할 수 있다
- 웹 소켓 프레임 헤더를 직접 수정해야 한다
-
웹 소켓 채널과 멀티플렉싱 확장의 차이점으로 올바른 것은?
- 채널은 프로토콜 레벨에서, 멀티플렉싱은 애플리케이션 레벨에서 구현된다
- 채널은 애플리케이션 레벨에서, 멀티플렉싱은 프로토콜 레벨에서 구현된다
- 채널은 텍스트 메시지만, 멀티플렉싱은 바이너리 메시지만 지원한다
- 채널과 멀티플렉싱은 동일한 개념의 다른 이름일 뿐이다
-
웹 소켓 메시지 수신에 관한 설명으로 올바른 것을 모두 고르세요.
- 클라이언트에서는 'message' 이벤트를 통해 메시지를 수신한다
- 서버에서는 handleTextMessage와 handleBinaryMessage 메서드를 오버라이드하여 메시지 핸들러를 정의할 수 있다
- 수신된 메시지는 항상 문자열 형태이다
- MessageEvent.data 속성은 항상 JSON 객체이다
- 바이너리 메시지와 텍스트 메시지는 동일한 이벤트 핸들러로 처리된다
-
웹 소켓 메시지 형식 및 직렬화에 관한 설명으로 올바른 것은?
- JSON은 바이너리 형식이므로 항상 MessagePack보다 효율적이다
- Protocol Buffers는 웹 소켓에서 사용할 수 없다
- 대용량 메시지 전송 시 압축을 사용하면 전송 효율성을 높일 수 있다
- 웹 소켓은 텍스트 메시지만 지원하므로 바이너리 직렬화는 불필요하다