C++에서 Boost ASIO를 사용하여 TCP 동기화(Blocked) 통신 프로그래밍

공대생의 팁 2019.07.21 19:58

 지난 포스팅에서 C++에서 제공하는 Boost 라이브러리의 통신 라이브러리인 ASIO를 사용하여 비동기 통신 프로그래밍을 하는 방법에 대해 소개를 하였던 적이 있었습니다.



C++에서 Boost ASIO를 사용하여 TCP 비동기(Unblocked) 통신 프로그래밍

https://elecs.tistory.com/314


 서버-클라이언트 통신 시스템을 구축하던 도중 Java에서 사용하던 객체 통신 프로그래밍 방식이 상당히 편했던 기억이 있어 이를 C++에서도 편하게 사용할 수 있는 방법을 찾았던 것이어서 이를 정리하였던 글이었는데 생각보다 많은 분들께서 도움을 받으셨다는 답변을 받았습니다.


 그래서 이번에는 ASIO 통신 프로그래밍에서 다룰 만한 내용으로 동기화(Synchronization, Blocked) 프로그래밍 방식에 대해 이야기 해보고자 합니다.





 위 그림은 ASIO 프로그래밍의 동기화 통신 방식의 흐름도를 나타낸 것입니다. 이전 포스팅에서 다루었던 비동기화 통신 방식과는 달리 동기화 통신은 상대로부터 response를 대기하는 과정이 추가됩니다.

 비동기 모드의 경우 Handler를 등록해두면 response의 수신 여부와 관계 없이 서버는 프로그램의 동작을 멈추지 않고 별개의 동작을 수행할 수 있습니다. 반면 동기화 통신의 경우 상대방의 response가 들어오고 나서 동작하는 방식이므로 서로가 통신이 확인이 되었는지의 여부를 확인할 수 있다는 장점이 있습니다.


 먼저 프로세스는 socket에 자신의 IP 정보를 기록한 후 이를 io_service(Boost 1.66 버전 이후는 io_context)를 통해 운영체제 커널에 정보를 등록합니다. 이후 운영체제가 다른 컴퓨터로부터 응답을 수신하게 되면 이를 프로세스의 socket에게 전달하고 프로세스는 전달 받은 정보를 가공하는 방식으로 통신이 이루어집니다.


자세한 내용은 아래의 소스코드를 통해 알아보도록 하겠습니다.



 프로그램을 실행하기에 앞서 자신의 컴퓨터에 Boost 라이브러리를 설치합니다. Ubuntu를 기준으로 다음과 같이 실행합니다.


$ sudo apt install libboost-all-dev



 이제 어러분의 컴퓨터에는 Boost 라이브러리가 설치되었습니다. 다음으로 아래의 소스코드를 작성해줍니다.


sync_server.cc


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include<cstdlib>
#include<iostream>
#include<boost/asio.hpp>
 
using namespace std;
using boost::asio::ip::tcp;
 
class session
{
public:
    session(tcp::socket& sock)
    //boost 1.66이후 (Ubuntu 18.10 이후) 버전의 경우 io_context를 사용
    //session(boost::asio::io_context& io_service)
    : socket_(sock){
        start();
    }
 
    void start(){
        try{
            while(1){
                boost::system::error_code error;
                size_t length = socket_.read_some(boost::asio::buffer(data_), error);
                if(error == boost::asio::error::eof){
                    //클라이언트로 부터 정보를 모두 받았으므로 종료한다.
                    break;
                }else if(error){
                    throw boost::system::system_error(error);
                }
                cout << "Message from client: " << data_ << endl;
                boost::asio::write(socket_, boost::asio::buffer(data_, length));
            }
        }catch(exception& e){
            cerr << "Exception in server: " << e.what() << endl;
        }
    }
 
    tcp::socket& socket(){return socket_;}
 
private:
    tcp::socket& socket_;
    enum { max_length = 1024 };
    char data_[max_length];
};
 
class server
{
public:
    server(boost::asio::io_service& io_service, short port)
    //boost 1.66이후 (Ubuntu 18.10 이후) 버전의 경우 io_context를 사용
    //server(boost::asio::io_context& io_service, short port)
    : io_service_(io_service), acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
    {
        while(1){
            //boost 1.66이후 (Ubuntu 18.10 이후) 버전의 경우 io_context를 사용
            //new session(acceptor_.accept());
 
            //sever의 소켓 생성
            tcp::socket sock(io_service);
            //client로부터 접속 대기
            acceptor_.accept(sock);
            new session(sock);
        }
    }
private:
    boost::asio::io_service& io_service_;
    //boost 1.66이후 (Ubuntu 18.10 이후) 버전의 경우 io_context를 사용
    //boost::asio::io_context &io_service_;
    tcp::acceptor acceptor_;
};
 
int main(int argc, char* argv[]){
    try{
        if(argc != 2){
            cerr << "Usage: server <port>" << endl;
            return 1;
        }
        boost::asio::io_service io_service;
        //boost 1.66이후 (Ubuntu 18.10 이후) 버전의 경우 io_context를 사용
        //boost::asio::io_context io_service;
        server s(io_service, atoi(argv[1]));
 
    }catch(exception& e){
        cerr << "Exception: " << e.what() << endl;
    }
 
    return 0;
}
cs



sync_client.cc


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include<cstdlib>
#include<iostream>
#include<boost/asio.hpp>
 
using boost::asio::ip::tcp;
using namespace std;
 
class client
{
public:
    client(boost::asio::io_service& io_service,
    //client(boost::asio::io_context& io_service,
    const string& host, const string& port) : socket_(io_service)
    {
        tcp::resolver resolver(io_service);
        //서버에 접속을 시도한다.
        boost::asio::connect(socket_, resolver.resolve({host, port}));
 
        string msg = "Hello, world!";
        size_t msg_length = msg.length();
 
        //서버로 데이터를 전송
        boost::asio::write(socket_, boost::asio::buffer(msg, msg_length));
 
        char reply[max_length];
        //서버로부터 데이터를 수신
        size_t reply_length = boost::asio::read(socket_, boost::asio::buffer(reply, msg_length));
        cout << "Reply is: " << reply << endl;
        
    }
 
private:
    tcp::socket socket_;
    enum { max_length = 1024 };
};
 
int main(int argc, char* argv[]){
    try{
        if(argc != 3){
            cerr << "Usage: client <host> <port>" << endl;
            return 1;
        }
 
        //boost 1.66이후 (Ubuntu 18.10 이후) 버전의 경우 io_context를 사용
        //boost::asio::io_context &io_service;
        boost::asio::io_service io_service;
        client c(io_service, argv[1], argv[2]);
 
    }catch(exception& e){
        cerr<<"Exception: " << e.what() << endl;
    }
 
    return 0;
}
cs



CMakeLists.txt


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
cmake_minimum_required(VERSION 3.0)
add_compile_options(-std=c++11)
 
project(asio_sync)
find_package(Boost REQUIRED system)
find_package(Threads)
 
include_directories(${Boost_INCLUDE_DIR})
 
add_executable(client
    sync_client.cc
)
 
add_executable(server
    sync_server.cc
)
 
target_link_libraries(client
    ${Boost_LIBRARIES}
    ${CMAKE_THREAD_LIBS_INIT}
)
 
target_link_libraries(server
    ${Boost_LIBRARIES}
)
cs



Linux 환경에서 다음과 같이 프로그램을 컴파일합니다.


$ mkdir build

$ cd build

$ cmake ..

$ make


 이제 우리가 만든 프로그램을 실행하여봅니다.



Server

1
2
$ ./server 2222
Message from client: Hello, world!
cs



Client


1
2
$./client 127.0.0.1 2222
Reply is: Hello, world!
cs



 축하합니다! 이제 여러분들도 Boost 라이브러리의 ASIO를 사용하여 동기화 통신 프로그래밍을 만들 수 있게 되었습니다!