[C++] Boost ASIO를 이용한 안전한 TCP/IP 비동기 소켓 서버 예제
C++ Async Socket Server Example
server/client 프로그램을 개발하다 보면 간단하게 1:1 send, receive만 하면 편하겠지만 일반적으로 여러 클라이언트는 동시에 붙을 수 있고 서버는 동시에 여러 가지 일을 처리할 수 있는 비동기 서버가 필요한 경우가 더 많습니다.
비동기 소켓 서버를 간단히 만드는건 어찌어찌할 수 있지만 잘 만들기는 쉽지 않은데요,
이때 활용할 수 있는 검증된 라이브러리가 바로 Boost의 ASIO (Async Input Output) Library입니다.

Boost Library 다운로드 및 설치 - https://wendys.tistory.com/115
[C/C++] 윈도우10 Boost 최신버전 설치 및 사용법
Boost Library Download and Build C++ 필수 라이브러리 중 Boost Library에 대해 설치 및 사용법을 정리합니다. Boost는 공식 홈페이지에서 다운로드 가능하며, 주기적으로 업데이트가 되고 있습니다. https://ww..
wendys.tistory.com
아주아주 잘 되어있는 라이브러리이지만 비동기 소켓 프로그래밍 자체가 그리 간단하지는 않기 때문에 한번 만들어놓고 응용하여 사용할 수 있도록 클래스로 만들어서 사용 중인 것을 공유하고자 합니다.
Boost ASIO는 소켓뿐만 아니라 파이프 서버에도 응용이 가능합니다.
boost/asio.hpp, boost/bind.hpp 헤더를 사용합니다.
#include <boost/asio.hpp> #include <boost/bind.hpp>
비동기 소켓 헤더
#pragma once class socket_server_tcp { public: static const size_t buffer_size = 8192; public: socket_server_tcp(boost::asio::io_context &io_context); ~socket_server_tcp(); boost::asio::ip::tcp::socket& native_object() { return _socket; } void start_accept(); private: void read_complete( __in const boost::system::error_code& error, __in size_t bytes_received ); void write( __in const void* data, __in const size_t size ); void write_complete( __in const boost::system::error_code&, __in size_t ); void reset(); void read(); void accept_complete(const boost::system::error_code& error); private: int _sequence_number; char _receive_buffer[buffer_size]; boost::asio::ip::tcp::acceptor _acceptor; boost::asio::ip::tcp::socket _socket; };
비동기 소켓 예제
#include "stdafx.h" socket_server_tcp::socket_server_tcp(boost::asio::io_context &io_context) : _acceptor(io_context, boost::asio::ip::tcp::endpoint( boost::asio::ip::tcp::v4(), __socket_server_port ) ), _socket(io_context) { } socket_server_tcp::~socket_server_tcp() { } void socket_server_tcp::start_accept() { _acceptor.async_accept(_socket, boost::bind( &socket_server_tcp::accept_complete, this, boost::asio::placeholders::error) ); } void socket_server_tcp::accept_complete( const boost::system::error_code& error ) { if (!error) { __log_trace("a new connection was established."); read(); } else { __log_trace("error occured"); } } void socket_server_tcp::read() { memset(&_receive_buffer, '\0', sizeof(_receive_buffer)); _socket.async_read_some( boost::asio::buffer(_receive_buffer), boost::bind(&socket_server_tcp::read_complete, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred) ); } void socket_server_tcp::read_complete( __in const boost::system::error_code& error, __in size_t bytes_received ) { if (error) { if (error == boost::asio::error::eof) { __log_trace("client disconnection."); } else { __log_trace(error.message().c_str()); } reset(); } else { auto transactor = [this]( __in const void* data, __in const size_t size ) { bool send_empty_reply = true; if (data && size) { // // receive packet data parsing. // } if (send_empty_reply) { basic_socket_packet<> reply_packet; write(&reply_packet, sizeof(reply_packet)); } }; // // do transaction. // transactor(_receive_buffer, bytes_received); // // read next data. // read(); } } void socket_server_tcp::write( __in const void* data, __in const size_t size ) { boost::asio::async_write(_socket, boost::asio::buffer(data, size), boost::bind(&socket_server_tcp::write_complete, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred) ); } void socket_server_tcp::write_complete( __in const boost::system::error_code&, __in size_t ) { } void socket_server_tcp::reset() { if (_socket.is_open()) { _socket.close(); } start_accept(); }
생각보다 매우 간단해 보이지 않나요??
이제 여기서 우리가 할 작업은 모든 데이터가 들어왔음을 알리는 read_complete
함수에서 transactor
내부를 구현하기만 하면 됩니다.
예를 들어 소켓 데이터를 만들어놓고 프로토콜에 맞춰 통신을 하게 되는데요 샘플을 한번 보면 이런 식입니다.
소켓 프로토콜 예제
struct socket_data_type { int data_type; unsigned long data_size; char* data; }; auto transactor = [this]( __in const void* data, __in const size_t size ) { bool send_empty_reply = true; if (data && size) { const socket_data_type* socket_data = reinterpret_cast<const socket_data_type*>(data); if (0 == socket_data->data_type) { // // // } else { // // ... // socket_data_type reply_packet; reply_packet .data_type = 1; write(&reply_packet, sizeof(reply_packet)); send_empty_reply = false; } } if (send_empty_reply) { basic_socket_packet<> reply_packet; write(&reply_packet, sizeof(reply_packet)); } };
여기서 주의할 점은 _socket 값은 멤버로 구현 시 생성자에서 생성을 해주어야만 한다는 것입니다.
이렇게 소켓 클래스를 구현했다면 이제 어떻게 사용하는지 방법이 궁금하실 거라고 생각이 드네요
비동기 소켓을 사용한다 하더라도 하나의 소켓에 여러 클라이언트를 처리할 수 없기에 여러 개의 소켓을 생성해놓고 동시다발적으로 처리되는 것처럼 처리를 해야 합니다.
소켓을 몇 개를 생성할지는 각자의 환경을 고려하여 테스트 후 적용해야 되기 때문에 개수가 정해져 있지는 않습니다.
비동기 소켓 boost::asio 사용 예제
private: std::list<std::thread> _socket_threads;
// // run as socket server. // for (int i = 0; i < __max_socket_instances; i++) { _socket_threads.push_back(std::thread([&]() { boost::asio::io_context io_context; socket_server_tcp socket_server(io_context); socket_server.start_accept(); io_context.run(); })); }
'⌨ DEVELOPMENT > C++' 카테고리의 다른 글
[C/C++] InternetReadFile Simple File Download. (6) | 2020.03.04 |
---|---|
[Solved] Resolver error: The VS Code Server failed to start (3) | 2019.12.14 |
[C/C++] Get Process Name by Process Id 3가지 방법 (0) | 2019.12.12 |
[MFC] Dialog based - class name 지정하는 간단한 방법 (3) | 2019.09.08 |
[MFC] Dialog Load GIF image (5) | 2019.07.28 |
[C++] How to use GDIPlus Library in c++ (0) | 2019.07.28 |
[MFC] 다이얼로그의 윈도우 소멸자 호출 순서 (1) | 2019.07.28 |
[C/C++] string wstring format 사용하기 (1) | 2019.07.27 |
댓글
이 글 공유하기
다른 글
-
[C/C++] Get Process Name by Process Id 3가지 방법
[C/C++] Get Process Name by Process Id 3가지 방법
2019.12.12 -
[MFC] Dialog based - class name 지정하는 간단한 방법
[MFC] Dialog based - class name 지정하는 간단한 방법
2019.09.08 -
[MFC] Dialog Load GIF image
[MFC] Dialog Load GIF image
2019.07.28 -
[C++] How to use GDIPlus Library in c++
[C++] How to use GDIPlus Library in c++
2019.07.28
댓글을 사용할 수 없습니다.