이 영역을 누르면 첫 페이지로 이동
웬디의 기묘한 이야기 블로그의 첫 페이지로 이동

웬디의 기묘한 이야기

페이지 맨 위로 올라가기

웬디의 기묘한 이야기

C/C++ Windows Hooking 개발자의 블로그 입니다! 이곳은 개발 외에도 저의 취미들이 공유되는 기묘한 이야기가 펼쳐집니다.

[C++] Boost ASIO를 이용한 안전한 TCP/IP 비동기 소켓 서버 예제

  • 2019.08.07 18:02
  • ⌨ DEVELOPMENT/C++
반응형

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();
    }));
}
클라이언트는 보통 비동기로 하지 않아도 충분하기때문에 connect > send > receive > disconnect 형태로 구현해도 상관없습니다.
간단한 소켓 예제는 MSDN을 참고하셔도 충분할 것 같습니다.

 

 

반응형

'⌨ 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

댓글

이 글 공유하기

  • 구독하기

    구독하기

  • 카카오톡

    카카오톡

  • 라인

    라인

  • 트위터

    트위터

  • Facebook

    Facebook

  • 카카오스토리

    카카오스토리

  • 밴드

    밴드

  • 네이버 블로그

    네이버 블로그

  • Pocket

    Pocket

  • Evernote

    Evernote

다른 글

  • [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
다른 글 더 둘러보기

정보

웬디의 기묘한 이야기 블로그의 첫 페이지로 이동

웬디의 기묘한 이야기

  • 웬디의 기묘한 이야기의 첫 페이지로 이동

검색

메뉴

  • 홈
  • 태그
  • 방명록
  • 이야기

카테고리

  • 분류 전체보기 (204)
    • MY STORY (2)
    • 📸 WALKING WITH YOU (85)
      • 아이슬란드 신혼여행 이야기 (14)
      • 대한민국 구석구석 (62)
      • CONTAX N1 + T* 28-80mm (4)
      • SAMSUNG NX3000 (1)
      • 어느 멋진 날 (4)
    • ⌨ DEVELOPMENT (80)
      • BOOK:Review (1)
      • AI (13)
      • C++ (26)
      • Python (10)
      • WIndows Hooking (9)
      • Windows Kernel (3)
      • Design Pattern (3)
      • Debugging (9)
      • Tools (0)
      • Project (1)
      • Android (1)
      • 상업용 무료폰트 (4)
    • OS (4)
      • News (0)
      • Windows 일반 (4)
    • 모바일 (2)
      • 모바일 게임 (2)
    • 멘사 퍼즐 (9)
    • 생활 꿀 TIP (7)
      • 건강 (3)
      • 일상 (2)
    • 물생활 (8)
      • 골든볼 라미네지 롱핀 (8)
    • IT 기기 (2)
    • BLOG (4)
      • TISTORY BLOG TIP (3)

최근 글

인기 글

댓글

공지사항

아카이브

태그

  • c
  • AI
  • c++
  • 카페
  • 아이슬란드
  • windbg
  • 해외여행
  • 신혼여행

나의 외부 링크

  • kernel undocument api
  • 지구 관찰자의 일기
  • 지구와 지구곰

정보

WENDYS의 웬디의 기묘한 이야기

웬디의 기묘한 이야기

WENDYS

블로그 구독하기

  • 구독하기
  • RSS 피드

방문자

  • 전체 방문자
  • 오늘
  • 어제

티스토리

  • 티스토리 홈
  • 이 블로그 관리하기
  • 글쓰기
Powered by Tistory / Kakao. © WENDYS. Designed by Fraccino.

티스토리툴바