[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
아주아주 잘 되어있는 라이브러리이지만 비동기 소켓 프로그래밍 자체가 그리 간단하지는 않기 때문에 한번 만들어놓고 응용하여 사용할 수 있도록 클래스로 만들어서 사용 중인 것을 공유하고자 합니다.
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