WebSocket은 실시간 양방향 통신을 제공하는 프로토콜로, 클라이언트와 서버 간의 데이터 전송을 더욱 효율적으로 만들어 줍니다. WebSocket의 작동 원리는 다음과 같습니다:
핸드셰이크(Handshake)
클라이언트와 서버 간의 WebSocket 연결은 HTTP 핸드셰이크로 시작됩니다. 클라이언트는 서버에 HTTP 요청을 보내며, 이 요청에는 업그레이드 헤더가 포함되어 있어 WebSocket 연결로 업그레이드를 요청합니다. 서버는 이 요청을 받아들일 경우 HTTP 101 Switching Protocols 응답을 보내어 WebSocket 연결을 수립합니다.
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
양방향 통신
핸드셰이크가 완료되면, 클라이언트와 서버는 WebSocket을 통해 양방향으로 데이터를 주고받을 수 있습니다. 이 연결은 지속적이며, HTTP의 요청-응답 방식과 달리 데이터를 실시간으로 주고받을 수 있습니다. 클라이언트와 서버는 메시지를 프레임 단위로 전송합니다. 이 프레임은 텍스트 또는 바이너리 데이터일 수 있습니다.
프레임 구조:WebSocket 프레임은 특정 형식을 따릅니다. 첫 번째 바이트는 메타데이터(종료 비트, 예약 비트, opcode 등)를 포함하고, 두 번째 바이트는 페이로드 길이를 나타냅니다. 이후 페이로드 데이터가 뒤따릅니다. 예: 텍스트 메시지를 전송할 때는 첫 번째 바이트의 opcode가 텍스트 프레임(1)으로 설정됩니다.
연결 유지 및 관리:WebSocket 연결은 클라이언트나 서버가 명시적으로 연결을 닫지 않는 한 계속 유지됩니다. 연결을 닫을 때는 클로즈 프레임을 보내어 종료를 알립니다. 클라이언트나 서버는 핑/퐁 프레임을 사용하여 연결이 여전히 유효한지 확인할 수 있습니다. 이는 연결 유지 관리(keep-alive)를 위한 기능입니다.
예시 코드:클라이언트에서 WebSocket 연결을 생성하고 사용하는 간단한 예제는 다음과 같습니다:
// 클라이언트에서 WebSocket 연결 생성
const socket = new WebSocket('ws://example.com/socket');
// 연결이 열렸을 때 실행되는 이벤트
socket.onopen = function(event) {
console.log('WebSocket connection established');
// 서버로 메시지 전송
socket.send('Hello, Server!');
};
// 서버로부터 메시지를 수신할 때 실행되는 이벤트
socket.onmessage = function(event) {
console.log('Message from server:', event.data);
};
// 연결이 닫혔을 때 실행되는 이벤트
socket.onclose = function(event) {
console.log('WebSocket connection closed');
};
// 에러가 발생했을 때 실행되는 이벤트
socket.onerror = function(event) {
console.error('WebSocket error:', event);
};
WebSocket을 사용하면 HTTP의 요청-응답 모델보다 더 적은 오버헤드로 실시간 데이터를 주고받을 수 있어, 실시간 채팅, 실시간 알림, 게임 등 다양한 애플리케이션에서 유용하게 사용됩니다.
PHP 소켓 서버 코드 (server.php)
<?php
// 설정
$host = '127.0.0.1';
$port = 8080;
// 소켓 생성
$serverSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($serverSocket, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($serverSocket, $host, $port);
socket_listen($serverSocket);
$clients = [];
echo "WebSocket server started on ws://$host:$port\n";
while (true) {
$readSockets = array_merge([$serverSocket], $clients);
$writeSockets = null;
$exceptSockets = null;
// 소켓 변경 대기
socket_select($readSockets, $writeSockets, $exceptSockets, null);
if (in_array($serverSocket, $readSockets)) {
$newClient = socket_accept($serverSocket);
$clients[] = $newClient;
$index = array_search($serverSocket, $readSockets);
unset($readSockets[$index]);
}
foreach ($readSockets as $socket) {
$data = @socket_recv($socket, $buffer, 2048, 0);
if ($data === false) {
$index = array_search($socket, $clients);
unset($clients[$index]);
socket_close($socket);
continue;
}
if ($data > 0) {
if (strpos($buffer, 'GET') !== false) {
performHandshake($socket, $buffer);
} else {
$message = decode($buffer);
$response = encode($message);
foreach ($clients as $client) {
@socket_write($client, $response, strlen($response));
}
}
}
}
}
socket_close($serverSocket);
function performHandshake($client, $headers)
{
if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $headers, $matches)) {
$key = $matches[1];
$acceptKey = base64_encode(pack(
'H*',
sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
));
$upgrade = "HTTP/1.1 101 Switching Protocols\r\n" .
"Upgrade: websocket\r\n" .
"Connection: Upgrade\r\n" .
"Sec-WebSocket-Accept: $acceptKey\r\n\r\n";
socket_write($client, $upgrade, strlen($upgrade));
}
}
function encode($message)
{
$frame = [];
$frame[0] = 129;
$len = strlen($message);
if ($len <= 125) {
$frame[1] = $len;
} else {
$frame[1] = 126;
$frame[2] = ($len >> 8) & 255;
$frame[3] = $len & 255;
}
for ($i = 0; $i < $len; $i++) {
$frame[] = ord($message[$i]);
}
$response = '';
foreach ($frame as $b) {
$response .= chr($b);
}
return $response;
}
function decode($buffer)
{
$length = ord($buffer[1]) & 127;
if ($length == 126) {
$masks = substr($buffer, 4, 4);
$data = substr($buffer, 8);
} elseif ($length == 127) {
$masks = substr($buffer, 10, 4);
$data = substr($buffer, 14);
} else {
$masks = substr($buffer, 2, 4);
$data = substr($buffer, 6);
}
$text = '';
for ($i = 0; $i < strlen($data); ++$i) {
$text .= $data[$i] ^ $masks[$i % 4];
}
return $text;
}
지금까지 WebSocket 실시간 양방향 통신 프로토콜 및 구현 (자바스크립트, php)에 대해서 알아보았습니다
많은 도움 되셨으면 합니다.
HTTP 1.0과 HTTP 2.0의 차이점, HTTP 1.0 구현 방법