웹 소켓(WebSocket) 프로그래밍 기초
웹 소켓(WebSocket)은 웹 브라우저와 서버 간의 양방향 통신을 위한 프로토콜입니다. HTTP 프로토콜과 달리 웹 소켓은 클라이언트와 서버 간에 지속적인 연결을 유지하여 실시간 데이터를 주고받을 수 있습니다. 웹 소켓은 HTML5 표준의 일부로, 실시간 애플리케이션(예: 채팅 애플리케이션, 실시간 데이터 스트리밍)에서 많이 사용됩니다.
웹 소켓의 주요 특징
- 양방향 통신: 클라이언트와 서버 간에 양방향 통신이 가능합니다. 서버는 클라이언트에게 데이터를 푸시할 수 있습니다.
- 소켓 연결 유지: 연결이 유지되는 동안 데이터를 주고받을 수 있습니다. HTTP 프로토콜처럼 매 요청마다 연결을 설정하고 해제하는 오버헤드가 없습니다.
- 낮은 레이턴시: 연결 유지로 인한 낮은 레이턴시로 실시간 애플리케이션에 적합합니다.
웹 소켓 프로토콜
웹 소켓 프로토콜은 HTTP 핸드셰이크를 사용하여 연결을 설정합니다. 연결이 설정되면, 데이터 프레임을 통해 메시지를 주고받을 수 있습니다.
웹 소켓 핸드셰이크
- 클라이언트 요청:
- 클라이언트는 서버에 HTTP 요청을 보내어 웹 소켓 연결을 요청합니다.
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=
- 서버는 클라이언트의 요청을 승인하고, 웹 소켓 연결을 설정합니다.
Boost.Beast를 이용한 웹 소켓 프로그래밍
Boost.Beast는 HTTP 및 웹 소켓 프로토콜을 지원하는 라이브러리로, Boost.Asio를 기반으로 합니다. Boost.Beast를 사용하여 웹 소켓 서버와 클라이언트를 쉽게 구현할 수 있습니다.
웹 소켓 서버 구현
웹 소켓 서버 코드 예제
AsyncWebSocketServer.h
#ifndef ASYNCWEBSOCKETSERVER_H
#define ASYNCWEBSOCKETSERVER_H
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp>
#include <boost/config.hpp>
#include <memory>
#include <string>
#include <thread>
#include <vector>
#include <iostream>
namespace beast = boost::beast; // Boost.Beast 네임스페이스
namespace http = beast::http; // HTTP 관련 네임스페이스
namespace websocket = beast::websocket; // 웹 소켓 관련 네임스페이스
namespace net = boost::asio; // Boost.Asio 네임스페이스
using tcp = net::ip::tcp; // TCP 네임스페이스
// 세션 클래스 정의
class session : public std::enable_shared_from_this<session> {
websocket::stream<tcp::socket> ws_; // 웹 소켓 스트림
beast::flat_buffer buffer_; // 데이터 버퍼
public:
// 생성자
explicit session(tcp::socket socket)
: ws_(std::move(socket)) {}
// 세션 실행
void run() {
ws_.async_accept(
[self = shared_from_this()](beast::error_code ec) {
if (!ec) {
self->do_read();
}
});
}
private:
// 데이터 읽기
void do_read() {
ws_.async_read(buffer_,
[self = shared_from_this()](beast::error_code ec, std::size_t bytes_transferred) {
boost::ignore_unused(bytes_transferred);
if (!ec) {
self->do_write();
}
});
}
// 데이터 쓰기
void do_write() {
ws_.text(ws_.got_text());
ws_.async_write(buffer_.data(),
[self = shared_from_this()](beast::error_code ec, std::size_t bytes_transferred) {
boost::ignore_unused(bytes_transferred);
if (!ec) {
self->buffer_.consume(self->buffer_.size());
self->do_read();
}
});
}
};
// 리스너 클래스 정의
class listener : public std::enable_shared_from_this<listener> {
net::io_context& ioc_; // IO 컨텍스트
tcp::acceptor acceptor_; // TCP 수신자
public:
// 생성자
listener(net::io_context& ioc, tcp::endpoint endpoint)
: ioc_(ioc), acceptor_(net::make_strand(ioc)) {
beast::error_code ec;
// 소켓 열기
acceptor_.open(endpoint.protocol(), ec);
if (ec) {
std::cerr << "오류: " << ec.message() << std::endl;
return;
}
// 소켓 옵션 설정
acceptor_.set_option(net::socket_base::reuse_address(true), ec);
if (ec) {
std::cerr << "오류: " << ec.message() << std::endl;
return;
}
// 바인딩
acceptor_.bind(endpoint, ec);
if (ec) {
std::cerr << "오류: " << ec.message() << std::endl;
return;
}
// 수신 대기
acceptor_.listen(net::socket_base::max_listen_connections, ec);
if (ec) {
std::cerr << "오류: " << ec.message() << std::endl;
return;
}
do_accept();
}
private:
// 연결 수락
void do_accept() {
acceptor_.async_accept(
net::make_strand(ioc_),
[self = shared_from_this()](beast::error_code ec, tcp::socket socket) {
if (!ec) {
std::make_shared<session>(std::move(socket))->run();
}
self->do_accept();
});
}
};
#endif // ASYNCWEBSOCKETSERVER_H
main.cpp
#include <iostream>
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp>
#include <boost/config.hpp>
#include <memory>
#include <string>
#include <thread>
#include <vector>
#include "AsyncWebSocketServer.h"
namespace net = boost::asio;
using tcp = net::ip::tcp;
int main() {
try {
// 서버 주소 및 포트 설정
auto const address = net::ip::make_address("0.0.0.0");
auto const port = static_cast<unsigned short>(std::atoi("12345"));
net::io_context ioc{1};
// 리스너 생성 및 실행
std::make_shared<listener>(ioc, tcp::endpoint{address, port})->run();
ioc.run();
} catch (std::exception const& e) {
std::cerr << "예외 발생: " << e.what() << std::endl;
}
return EXIT_SUCCESS;
}
설명
위의 코드는 Boost.Beast를 사용하여 간단한 웹 소켓 서버를 구현한 예제입니다. 이 서버는 클라이언트와의 웹 소켓 연결을 수락하고, 데이터를 주고받는 기능을 제공합니다.
- session 클래스:
session
클래스는 클라이언트와의 세션을 관리합니다. 웹 소켓 스트림을 사용하여 클라이언트와 데이터를 주고받습니다.run()
함수는 웹 소켓 연결을 수락합니다.do_read()
함수는 클라이언트로부터 데이터를 비동기적으로 읽습니다.do_write()
함수는 클라이언트에게 데이터를 비동기적으로 씁니다.
- listener 클래스:
listener
클래스는 클라이언트의 연결을 수락합니다. TCP 수신자를 사용하여 연결을 수락하고, 새로운session
객체를 생성합니다.do_accept()
함수는 비동기적으로 클라이언트의 연결을 수락합니다.
웹 소켓 클라이언트 구현
웹 소켓 클라이언트 코드 예제
AsyncWebSocketClient.h
#ifndef ASYNCWEBSOCKETCLIENT_H
#define ASYNCWEBSOCKETCLIENT_H
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp>
#include <boost/config.hpp>
#include <memory>
#include <string>
#include <iostream>
namespace beast = boost::beast; // Boost.Beast 네임스페이스
namespace websocket = beast::websocket; // 웹 소켓 관련 네임스페이스
namespace net = boost::asio; // Boost.Asio 네임스페이스
using tcp = net::ip::tcp; // TCP 네임스페이스
// 웹 소켓 클라이언트 클래스 정의
class AsyncWebSocketClient : public std::enable_shared_from_this<AsyncWebSocketClient> {
websocket::stream<tcp::socket> ws_; // 웹 소켓 스트림
beast::flat_buffer buffer_; // 데이터 버퍼
public:
// 생성자
explicit AsyncWebSocketClient(net::io_context& ioc)
: ws_(net::make_strand(ioc)) {}
// 연결 설정
void connect(const std::string& host, const std::string& port) {
auto const results = net::ip::tcp::resolver(ws_.get_executor()).resolve(host, port);
net::async_connect(
ws_.next_layer(),
results.begin(),
results.end(),
[self = shared_from_this()](beast::error_code ec, tcp::resolver::results_type::endpoint_type) {
if (!ec) {
self->ws_.async_handshake(self->ws_.next_layer().remote_endpoint().address().to_string(), "/",
[self](beast::error_code ec) {
if (!ec) {
self->do_write();
}
});
}
});
}
private:
// 데이터 쓰기
void do_write() {
auto self = shared_from_this();
ws_.async_write(
net::buffer(std::string("Hello from client")),
[self](beast::error_code ec, std::size_t bytes_transferred) {
boost::ignore_unused(bytes_transferred);
if (!ec) {
self->do_read();
}
});
}
// 데이터 읽기
void do_read() {
auto self = shared_from_this();
ws_.async_read(buffer_,
[self](beast::error_code ec, std::size_t bytes_transferred) {
boost::ignore_unused(bytes_transferred);
if (!ec) {
std::cout << "서버로부터 수신한 메시지: " << beast::make_printable(self->buffer_.data()) << std::endl;
}
});
}
};
#endif // ASYNCWEBSOCKETCLIENT_H
main.cpp
#include <iostream>
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp>
#include <boost/config.hpp>
#include <memory>
#include <string>
#include <thread>
#include "AsyncWebSocketClient.h"
namespace net = boost::asio;
using tcp = net::ip::tcp;
int main() {
try {
net::io_context ioc{1};
// 클라이언트 객체 생성 및 서버에 연결
auto client = std::make_shared<AsyncWebSocketClient>(ioc);
client->connect("127.0.0.1", "12345");
// IO 컨텍스트 실행
ioc.run();
} catch (std::exception const& e) {
std::cerr << "예외 발생: " << e.what() << std::endl;
}
return EXIT_SUCCESS;
}
설명
위의 코드는 Boost.Beast를 사용하여 간단한 웹 소켓 클라이언트를 구현한 예제입니다. 이 클라이언트는 서버와의 웹 소켓 연결을 설정하고, 데이터를 주고받는 기능을 제공합니다.
- AsyncWebSocketClient 클래스:
AsyncWebSocketClient
클래스는 웹 소켓 클라이언트를 관리합니다. 웹 소켓 스트림을 사용하여 서버와 데이터를 주고받습니다.connect()
함수는 서버에 연결을 설정합니다.do_write()
함수는 서버로 데이터를 비동기적으로 씁니다.do_read()
함수는 서버로부터 데이터를 비동기적으로 읽습니다.
이제 열두 번째 날의 학습을 마쳤습니다. 웹 소켓의 기본 개념과 Boost.Beast를 사용하여 간단한 웹 소켓 서버와 클라이언트를 구현하는 방법을 학습했습니다.
질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "웹 소켓 클라이언트 개발"에 대해 학습하겠습니다.
'-----ETC----- > C++ 네트워크 프로그래밍 시리즈' 카테고리의 다른 글
[C++ 네트워크 프로그래밍] Day 14: 웹 소켓 서버 개발 (0) | 2024.08.01 |
---|---|
[C++ 네트워크 프로그래밍] Day 15: 멀티스레드 서버 개발 (Boost.Asio) (0) | 2024.08.01 |
[C++ 네트워크 프로그래밍] Day 13: 웹 소켓 클라이언트 개발 (0) | 2024.08.01 |
[C++ 네트워크 프로그래밍] Day 10: HTTP 서버 개발 (Boost.Beast) (0) | 2024.08.01 |
[C++ 네트워크 프로그래밍] Day 11: RESTful API 설계와 구현 (0) | 2024.08.01 |