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를 사용하여 동기화 통신 프로그래밍을 만들 수 있게 되었습니다!

무더운 여름날 김유정역 강촌레일파크 [2019.07.17]


 평소에 레일바이크를 타보고 싶었던 적이 있었는데 마침 친구와 단둘이 여행을 할 수 있는 기회가 생겨 친구와 함께 레일바이크에 도전해 보게 되었습니다. 마침 춘천 인근에 있는 김유정역에 레일바이크가 있다는 이야기를 듣고 찾아가 보았습니다.




레일바이크의 시작점에서..



자전거 타듯이 페달만 열심히 저어주면 됩니다.



레일바이크 옆으로 펼쳐지는 풍경이 정말 대단합니다.



레일바이크 종착지에는 이렇게 쉼터가 마련되어 있습니다.

여기서 바라보는 풍경도 상당히 좋더군요.



종착지에서 강촌역까지는 특수열차를 타고 이동합니다.



열차에 올라서 바라보는 풍경도 꽤 장관입니다.



무궁화호를 타고서는 보기 힘든 광경들이 이렇게 지붕 없는 객차에서는 새로운 풍경으로 비쳐집니다.



어느덧 강촌역 인근에 도착



바닥을 보니 생각보다 높은 위치더군요



오랜만에 본 옛 강촌역의 모습

비록 열차는 정차하지 않지만 그 당시의 모습만은 고이 간직하고 있군요.



또오리 강촌!


바다가 보이는 간이역 월내역에서 기차가 서던 마지막날 [2019.07.14]


 지난 달에 동해선 덕하역을 다녀간지 한 달만에 동해선 부산 구간은 또 한번의 격변기를 맞이하게 되었습니다. 오늘로써 일광역부터 태화강역까지 기존의 철길에서 새로운 철길로 이사를 가게 되면서 이제는 지금까지 기차가 지나다니던 그 모습들은 역사속으로 사라졌기 때문이지요.


 오늘은 그러한 역사속 모습을 마지막까지 간직하고 있던 월내역 인근을 돌아다녀 보았습니다. 지금의 월내역은 기존역 역을 철거하고 임시로 세워져 있지만 여전히 무궁화호가 정차하던 역이었는데 오늘을 끝으로 더이상 무궁화호는 정차하지 않게 됩니다. 다만 앞으로 이설될 새로운 월내역은 고가역이 되어 바다가 훨씬 더 잘 보일 것으로 보입니다!



지난달에 방문하였던 덕하역 인근 구간을 지날 때 였는데

놀랍게도 이번에는 새로 이설한 고가 구간을 통과하는 겁니다!

아래의 이설 전 고가는 벌써부터 철거에 들어가 있더군요.


혹시 이설 바로 전날 이 곳의 모습이 궁금하시다면 아래의 포스팅을 확인해주세요!


철길도 이사를 간다? 덕하역 이설 전날 풍경(2) [2019.06,16]

https://elecs.tistory.com/327




5개월만에 다시 월내역에 방문하는군요. 승강장에는 벌써 잡초가 무성합니다.



승강장 너머 고가철로 끝에는 새로운 월내역이 건설중입니다.



아직 광역전철이 개통하기까지 1년 넘게 남았는데 벌써 폐쇄된다니



새로운 월내역과 기존의 철길 모습입니다.

역 내에는 굴삭기가 개통 준비를 서두르고 있습니다.



남창역 방향을 향하여 찍은 모습입니다. 아마 내일이면 더이상 열차는 다니지 않습니다.



고가와 건널목.. 공존할 수 없는 관계일까요?



저 멀리 월내역의 임시승강장이 보입니다.



기존 철교와 새로 지어지고 있는 철교 사이의 자전거도로입니다.

한적하게 낚시를 하시는 분이 보입니다.



옛 철교 뒤로 내일부터 새로 개통될 철교가 보입니다.

이런 구도 쉽지 않은데!



넓디넓은 4차선 도로에 건널목 하나가 떡하니 서 있습니다.



원래 여기는 2차선 작은 도로였는데 인근에 아파트가 건설되면서 4차선으로 확장되었습니다.



아마도 이 철길이 이설될 예정이라서 불편해도 미리 길을 확장해둔 듯 합니다.



그 덕에 기존의 관리원님들이 계시던 건물을 허러내고 저렇게 컨테이너로 건물이 대체된 상황입니다.



아마 이보다 더 큰 길에 건널목이 있던게 9년전 경춘선 이설 직전 화랑대역 인근 건널목이었을겁니다.

그땐 아마 6차선이었을겁니다.



어느덧 건널목은 우렁차게 울리기 시작하고



도로위를 부지런히 다니던 차량들이 잠시 정차합니다.



장안2건널목의 마지막날에도 이렇게 기차는 순식간에 지나갑니다.



이제 관리원님들께서도 내일부터는 이 곳에서 더이상 볼 수 없게 됩니다.



다시 월내역을 돌아옵니다.

아까 고가 위에 보았던 굴삭기가 여전히 분주히 작업을 진행하고 있습니다.



내일부터는 이 출입구도 폐쇄되겠지요



앞으로 이 곳에 오기 위해서는 좌천역에서 운행하는 대체 버스를 타고 와야 할 듯 합니다.



마지막날인 오늘도 승객들은 이 곳에서 열차를 기다립니다.



지금은 사라졌지만 광역전철이 개통하면 서생역에도 열차가 정차하게 됩니다.



어느덧 열차는 월내역으로 들어옵니다.



이 역의 마지막을 아는지 탑승객들이 기차를 바라보며 셀카를 찍습니다.



이제 열차에 탑승합니다.



방금전에 들렀던 곳을 이렇게 사진으로 님기기는 처음입니다.



방금 뵙고 갔던 관리원님도 마지막날까지 열심히 일을 하고 계시는군요.



덕하역 인근에 이설된 고가 인근의 모습입니다.

저 곳에서 이 열차가 달리는 임시철교를 만들고 있던게 떠오르는군요.


과연 다음 여행지는 어디가 될까요?




리눅스(우분투)에서 USB가 동작하지 않을 때 수동으로 연결하기(Failed to open the USB device!)

공대생의 팁 2019.07.05 14:45


 PrimeSense사에서 제작한 RD 1.09 RGB-D 카메라를 USB 3.0에 꽃아서 openni2 환경에서 실행을 해보려 하던 도중 다음과 같은 문제가 발생하였습니다.


No matching device found.... waiting for devices. Reason: openni2_wrapper::OpenNI2Device::OpenNI2Device(const string&) @ /tmp/buildd/ros-indigo-openni2-camera-0.2.2-0trusty-20141015-0837/src/openni2_device.cpp @ 74 : Initialize failed


    Could not open "1d27/0609@1/12": Failed to open the USB device!


보아하니 RD 1.09 버전에서는 USB 2.0 버전만 지원이 되서 발생하는 문제로 추측됩니다. 즉, 디바이스 노드를 생성하지 못하는 상황으로 추측할 수 있습니다. 이 경우 USB 기기를 수동으로 설정하기 위해서는 다음과 같이 진행해줍니다.


 1. 먼저 현재 리눅스(우분투)에 연결된 USB 드라이버의 리스트를 출력합니다.


$ lsusb


 위의 명령어를 입력하시면 다음과 같이 현재 컴퓨터에 연결된 USB 기기들의 리스트가 나타납니다.



 위 목록을 보았을 때 자신의 기기가 어떤 것인지를 파악합니다. 저의 경우 1d27:0609로 써있는 부분으로 추정됩니다.


 2. udev를 사용하여 USB 드라이버를 수동으로 연결하는 규칙을 만들어줍니다. 여기서 .rules의 이름은 자신이 파악하기 쉬운 이름으로 붙여줍니다. (ex: 40-libopenni2-0.rules)


$ sudo vi /etc/udev/rule.d/my_rule.rules


3. 다음과 같이 입력해 주시고 ':wq'를 입력하여 저장해줍니다. ATTR의 'idProduct'와 'idVendor'는 위에서 lsusb를 입력하였을 때 나왔던 ID를 각각 입력해주시고 GROUP의 경우 자신에 맞는 그룹을 설정해줍니다. 저의 경우 카메라를 사용하므로 'video'로 설정하였습니다.


SUBSYSTEM=="usb", ATTR{idProduct}=="0609", ATTR{idVendor}=="1d27", MODE:="0666", OWNER:="root", GROUP:="video"


4. 여기까지 진행하신 후 다시 기기를 사용해보신다면 정상적으로 연결되는 것을 확인하실 수 있습니다.


 혹시 udev에 대해 자세히 알고 싶으신 분은 아래의 블로그 글을 읽어보시기를 추천드립니다!

https://mokga.tistory.com/54

C++에서 Graphviz로 그래프 이미지 그리기

공대생의 팁 2019.07.04 01:04


 자료구조를 공부하거나 특정한 환경을 그림으로 표현하고자 하는 경우가 있습니다. 특히 위상수학 같이 이산수학이 사용되는 분야에서는 각 객체들의 상호관계를 나타내야 하는 것이 중요하지요. 이러한 특성을 이미지로 나타낸 것을 흔히 그래프(Graph)라고 부릅니다.



 위의 그림은 같은 Depth를 유지하는 것을 목적으로 설계된 B+ 트리를 나타냅니다. 각 노드(혹은 Vertex)가 화살표(혹은 Edge)로 연결되어 있습니다. 특정한 프로그램의 알고리즘을 위와 같이 그림으로 표현한 것을 그래프라고 합니다.


 그렇다면 과연 우리들이 직접 설계한 프로그램을 바로 그림으로 나타낼 수 있는 방법이 있을까요? C++의 Boost library에서는 그래프를 생성할 수 있는 Graphviz를 지원합니다. 이산수학의 그래프 이론을 잘 알고 계시다면 Graphviz를 사용 하는 것이 상당히 쉬워질 것입니다.


 Graphviz를 사용하기에 앞서서 간단한 그래프 이론에 대해 설명을 드리겠습니다. 예를들어 다음과 같은 그래프가 있다고 가정합니다.




 위 그래프는 Graphviz로 생성한 4개의 Vertex와 4개의 Edge를 가지고 있는 그래프입니다. 한 눈으로 봐도 각 vertex인 A, B, C와 D가 각각 edge로 어떻게 연결되었는지 한 번에 알 수 있습니다. 이를 인접 행렬(Adjacency matrix)로 나타내면 다음과 같습니다.



 각 행과 열의 첫 번째 부터 A, B, C, D로 보았을 때 나타낸 인접 행렬의 모습입니다. 첫째 열의 경우 [0 1 0 1] 로 나타내어졌는데 각각 A는 두 번째인 B와 4 번째인 D와 연결된 부분을 1로 나타낸 것입니다. 나머지 열의 경우는 직접 위의 그림과 비교해 보신다면 쉽게 아실 수 있으실 겁니다!


 지금까지 우리는 무방향 그래프(Undirectional graph)를 설명하였습니다. 다음으로는 Edge에 방향성이 포함되어 있는 방향 그래프(Directional graph)를 보도록 하겠습니다.



 위에서 보았던 그래프에 화살표로 방향이 추가된 그래프입니다. 이 그래프 또한 인접 행렬로 다음과 같이 나타낼 수 있습니다.



 위에서 보았던 인접 행렬과 다른 점은 각 열은 출발 vertex, 각 행은 도착 vertex를 나타낸 것입니다. 즉 A의 경우 B와 D의 방향으로 향하고 있기 때문에 첫 번째 열의 경우 [0 1 0 1] 순서로 나타나는 것입니다. 반면, C의 경우 무방향 그래프의 경우 A와 같은 [0 1 0 1]이었으나 B로는 연결 방향이 존재하지 않기 때문에 [0 0 0 1]로 나타내는 것입니다. 또한 방향 그래프는 인접 리스트(Adjacency list)로 아래와 같이 나타낼 수 있습니다.



 위에서 보았던 방향 그래프를 생각하시면 이해하기 쉬우실 겁니다. 각각의 Vertex인 A와 B, C, D를 생성한 후 각 Vertex가 향하는 방향의 Vertex를 연결 리스트(Linked list)로 이어주는 것이지요. 연결되는 순서는 상관 없습니다. 즉 [A → B → C] 로 표기하거나 [A → C → B]로 표기하여도 모두 같은 값을 나타낸다고 보시면 되겠습니다. 여기서 D로부터 시작하는 방향은 존재하지 않으므로 D는 그 어떤 Vertex와 연결되있지 않음을 위에서 보실 수 있습니다.


 그러면 이번에는 위에서 설명한 그래프들을 직접 소스코드로 구현해봅시다. 먼저 Boost library를 컴퓨터에 설치해 줍니다.


$ sudo apt install libboost-all-deb graphviz


 다음으로 아래와 같은 소스코드를 입력합니다.


graphviz.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
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <cstdio>
 
using namespace std;
 
struct VertexP { string tag; };
struct EdgeP { string symbol; };
struct GraphP { string orientation; };
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, VertexP, EdgeP, GraphP, boost::listS> ListGraph;
 
int main()
{
    ListGraph g(GraphP{"Example"});
    boost::dynamic_properties dp;
    dp.property("node_id", get(&VertexP::tag,g));
 
    ofstream dot_file("graph.dot");
    string dcmd = "dot -Tpng graph.dot -o graph.png";
 
    // Create the vertexes
    ListGraph::vertex_descriptor a = add_vertex(VertexP{"A"},g);
    ListGraph::vertex_descriptor b = add_vertex(VertexP{"B"},g);
    ListGraph::vertex_descriptor c = add_vertex(VertexP{"C"},g);
    ListGraph::vertex_descriptor d = add_vertex(VertexP{"D"},g);
 
    // Create the edges
    // A → B
    add_edge(a, b, g);
    // B → D
    add_edge(a, d, g);
    // B → C
    add_edge(b, c, g);
    // C → D
    add_edge(c, d, g);
 
    // Print graph
    write_graphviz_dp(dot_file, g, dp);
 
    std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(dcmd.c_str(), "r"), pclose);
    if (!pipe) {
        throw runtime_error("popen() failed!");
    }
 
    return 0;
}
 
cs



 여기서 소스코드의 중요한 부분을 하나씩 설명해 드리겠습니다.



typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, VertexP, EdgeP, GraphP, boost::listS> ListGraph;


 위에서 설명드렸던 인접 리스트(Adjacency list)를 만들수 있도록 해주는 Boost library입니다. Graphviz로 그래프를 만들어주기 위해 사용됩니다.


14
15
16
    ListGraph g(GraphP{"Example"});
    boost::dynamic_properties dp;
    dp.property("node_id", get(&VertexP::tag,g));
cs


하나의 인접 리스트 변수 g를 선언합니다. 인접 리스트의 이름은 "Example" 입니다. dynamic_properties는 Graphviz에서 Adjacency list의 내용을 설정해 주기 위해 사용되는 변수입니다. 각 vertex의 이름은 VertexP 구조에 저장되므로 16번 줄을 작성하여 Adjacency list 변수 g에 선언해줍니다.


18
19
    ofstream dot_file("graph.dot");
    string dcmd = "dot -Tpng graph.dot -o graph.png";
cs


 Graphviz를 생성해 주기 위해 파일을 생성합니다. 파일의 이름은 "graph.dot"입니다. Graphviz를 이미지로 생성시켜주는 과정은 Terminal의 명령어로 이루어지므로 명령어를 설정해 줍니다. 명령어는 다음과 같습니다.


$ dot -Tpng graph.dot -o graph.png


 위 명령어는 생성되는 이미지의 확장명은 png로 설정하며 파일명은 graph.png로 저장하겠다는 의미입니다.


21
22
23
24
25
    // Create the vertexes
    ListGraph::vertex_descriptor a = add_vertex(VertexP{"A"},g);
    ListGraph::vertex_descriptor b = add_vertex(VertexP{"B"},g);
    ListGraph::vertex_descriptor c = add_vertex(VertexP{"C"},g);
    ListGraph::vertex_descriptor d = add_vertex(VertexP{"D"},g);
cs


 Vertex들을 생성해줍니다. vertex_descriptor에 해당 vertex를 저장해 줍니다.


27
28
29
30
31
32
33
34
35
// Create the edges
// A → B
add_edge(a, b, g);
// B → D
add_edge(a, d, g);
// B → C
add_edge(b, c, g);
// C → D
add_edge(c, d, g);
cs


 바로 이 부분이 제가 위에서 말씀드렸던 인접 리스트의 방식을 나타낸 것입니다. 각 vertex와 연결하고자 하는 vertex를 위의 순서대로 입력하시면 우리가 원하는 방식대로 edge가 연결됩니다.


37
38
    // Print graph
    write_graphviz_dp(dot_file, g, dp);
cs


 지금까지 우리들이 만들었던 그래프를 Graphviz로 만듭니다. 이를 실행하면 dot 파일이 생성됩니다.


1
2
3
4
    std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(dcmd.c_str(), "r"), pclose);
    if (!pipe) {
        throw runtime_error("popen() failed!");
    }
cs


 dot 파일로 생성된 Graphviz를 이미지로 만들기 위해 실행되는 부분입니다. 이 과정은 Graphviz 프로그램에서 직접 실행되기 때문에 위와 같이 설정해 주시면 직접 사용자가 dot 명령어를 입력하지 않고도 이미지가 생성됩니다.


여기까지 완성되었다면 이제 컴파일을 해줍니다. 다음과 같은 파일을 만들어줍니다.


Makefile

1
2
3
4
5
CC = g++
CFLAGS = -g -Wall -std=c++11
 
output:graphviz.cc
    $(CC) $(CFLAGS) -o output graphviz.cc
cs


make로 컴파일을 해줍니다.


$ make


이제 컴파일이 실행된 폴더를 확인해보시면 'output' 이라는 이름의 실행파일이 생성됩니다. 이제 실행해봅니다.


$ ./output


 그러면 다음과 같은 이미지 파일이 생성되 있는 것을 확인하실 수 있습니다.



 다음으로 이번에는 방향 그래프를 만들어 보겠습니다. 위 코드에서 다음 부분만 바꿔주시면 되겠습니다.




typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, VertexP, EdgeP, GraphP, boost::listS> ListGraph;


 그러면 다음과 같은 방향 그래프가 생성됩니다.



 이번에는 생성된 그래프를 회전 시켜보겠습니다. 다음과 같은 코드를 추가시켜주세요.


14
15
16
17
    ListGraph g(GraphP{"Example"});
    boost::dynamic_properties dp;
    dp.property("node_id", get(&VertexP::tag,g));
    dp.property("rankdir", boost::make_constant_property<ListGraph*>(string("LR")));
cs


그러면 다음과 같이 그래프가 90도 회전합니다.



만약 위의 vertex의 순서를 반대로 하고자 하시는 분이라면 소스코드를 다음과 같이 고쳐주세요.


14
15
16
17
    ListGraph g(GraphP{"Example"});
    boost::dynamic_properties dp;
    dp.property("node_id", get(&VertexP::tag,g));
    dp.property("rankdir", boost::make_constant_property<ListGraph*>(string("RL")));
cs


 그러면 vertex의 순서가 좌우 반전으로 다음과 같이 출력됩니다.