본문 바로가기

& 프로그래밍/& ROS

ROS 서비스 서버 클라이언트 실행하기

어제 ROS 메시지 통신을 구현해보았다. 유튜브 구독을 예로 들면서 쉽게 접근했는데, 다들 이해가 되었길....

이번에는 서비스 통신을 구현해보려 한다. 메시지통신을 이해했다면 구조상 크게 달라지는 부분이 없으니 이번에도 어렵지 않게 배울 수 있을 것이다

 

서비스(service)

서비스 메시지통신은 서비스를 요청(request)하는 서비스 고객(service client)와 응답(response)를 담당하는 서비스 서버간에 양방향 통신을 의미한다

 

서비스서버(service server)

서비스서버는 요청을 입력받고 응답해주는 형태로, 서비스 메시지 통신의 서버역할을 한다. 요청과 응답은 모두 메시지로 되어 있으며, 서비스서버는 정해진 명령을 받아 수행하는 노드에 사용된다

 

서비스클라이언트(service client)

서비스클라이언트는 요청을 출력하고, 응답을 입력받는 형태로 서비스 메시지 통신의 클라이언트(고객)역할을 한다. 서비스 클라이언트는 정해진 명령을 지시하고 결과를 받는 노드에 사용된다

 

srv 파일

srv파일은 서비스에서 사용되는 메시지 파일로, 확장자는 .srv이다. 이전에 사용했던 .msg파일과 구조적 차이는 3개의 하이픈(---)이 사용된다는 것이다. 이 하이픈이 구분자 역할을 하여 상위 메시지가 서비스를 요청하는 메시지, 하위 메시지가 요청에 대한 응답을 수신하는 메시지로 사용된다

int64 A
int64 B
---
int64 result

 

이전시간에 배웠던 토픽 메시지통신은 publisher와 subscriber 개념으로 데이터를 일방적으로 받는 형태였다면, 서비스 메시지통신은 고객이 정보를 요청해야 송신자가 가지고 있는 데이터를 얻을 수 있는 구조이다.

 

일상에서 예를 들자면, 내가 커피가 마시고 싶어서 스타벅스를 갔다. 원하는 메뉴를 고르고 점원에게 주문을 한다(request). 점원은 고객에 주문대로 음료를 만들어 제공한다(response). 난 음료를 받았으니 스타벅스를 나왔다. 끝!

여기서 스타벅스는 server가 되고, 음료라는 service를 제공하기 위해 상시대기중인 셈이다. 나는 고객으로서 client가 되고, 원하는 service를 server에게 요청해서 응답을 받았다. 각자 목적을 달성했으니 서로의 연결은 끊어진다.

 

서비스 메시지 통신에 대한 용어와 개념을 이해했으니 본격적으로 실행해보도록 하자

 

1. 패키지 생성

서비스 서버와 클라이언트를 구성하기 위해 기본적인 패키지를 생성해준다

$ catkin_create_pkg yh_210609_1 message_generation std_msgs roscpp

패키지 생성에 대한 코드 구조가 궁금하다면 이전에 포스팅한 토픽 메시지 통신 글을 참고하면 된다

 

2. 패키지 설정파일 수정

패키지를 생성하면 기본 구성요소로 CMakeLists.txt, include, src, package.xml이 들어있다. 그중에서 package.xml에 들어가서 패키지에 대한 기본정보를 수정하면된다. 내가 만든 패키지라는 것을 설명해주는 부분이 가장 크다.

$ gedit package.xml

 

3. 서비스 파일 작성

서비스로 제공할 데이터를 구성하는 방법을 작성한다

# 패키지 폴더로 이동한 상태에서

$ mkdir srv
# srv라는 폴더를 생성한다
$ cd srv
# 생성한 폴더로 이동한다
$ gedit yh_srv_1.srv
# srv 파일을 생성한다

srv파일을 생성했으면, 아래와 같이 서비스 요청과 응답을 구분하여 작성해준다

int64 a
int64 b
---
int64 result

서비스파일이 왜 이렇게 구성되는지는 이미 위 용어정리에서 설명했으니 패스~

 

4. 서비스 노드 작성

클라이언트에게 요청받은 정보를 제공할 수 있도록 서비스 노드를 생성할 것이다. src 폴더로 들어가서 cpp파일을 만들어주자

/src$ gedit yh_server_1.cpp

#cpp파일에 아래와 같이 작성

#include "ros/ros.h"
#include "yh_210609_1/yh_srv_1.h"

bool calculation(yh_210609_1::yh_srv_1::Request &req, yh_210609_1::yh_srv_1::Response &res)
{
        res.result = req.a + req.b;
        ROS_INFO("request : x = %ld, y = %ld",(long int)req.a, (long int)req.b);
        ROS_INFO("sending back response : %ld",(long int)res.result);

        return true;
}

int main(int argc, char **argv)
{
        ros::init(argc, argv, "yh_server_1");
        ros::NodeHandle nh;

        ros::ServiceServer server= nh.advertiseService("yh_service_1", calculation);

        ROS_INFO("ready srv server!!");

        ros::spin();

        return 0;
}

5. 클라이언트 노드 작성

서비스 서버 노드와 동일한 형식으로 만들어준다

#include "ros/ros.h"
#include "yh_210609_1/yh_srv_1.h"
#include <cstdlib>

int main(int argc, char **argv)
{
        ros::init(argc, argv, "yh_client_1");
        if(argc != 3)
        {
                ROS_INFO("cmd: rosrun yh_tuto_service srv_client arg0 arg1");
                ROS_INFO("arg0 : double number, arg1 : double number");
                return 1;
        }
        ros::NodeHandle nh;

        ros::ServiceClient client = nh.serviceClient<yh_210609_1::yh_srv_1>("yh_service_1");

        yh_210609_1::yh_srv_1 srv;

        srv.request.a = atoll(argv[1]);
        srv.request.b = atoll(argv[2]);

        if(client.call(srv))
        {
                ROS_INFO("send srv, srv.Request.a and b : %ld, %ld", (long int) srv.request.a, (long int)srv.request.b);
                ROS_INFO("receive srv, srv.Response.result : %ld",(long int)srv.response.result);
        }
        else
        {
                ROS_ERROR("Failed to call service");
                return 1;
        }
        return 0;
}                                                                     

일부 다른점이 있는데, 클라이언트가 서비스에서 제공하는 값을 입력하지 않거나 잘못 입력했을경우 값을 입력or수정하도록 안내하는 문구를 추가했고, 서비스 오류가 발생하면 에러처리가 되도록 추가적으로 설정해주었다

 

6. 빌드설정 파일 수정

패키지 생성시 제공되었던 CMakeLists.txt파일을 수정해준다

cmake_minimum_required(VERSION 3.0.2)
project(yh_210609_1)

## Compile as C++11, supported in ROS Kinetic and newer
# add_compile_options(-std=c++11)

## Find catkin macros and libraries
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
## is used, also find other catkin packages
find_package(catkin REQUIRED COMPONENTS
  message_generation
  roscpp
  std_msgs
)

add_service_files(FILES yh_srv_1.srv)

generate_messages(DEPENDENCIES std_msgs)

catkin_package(
LIBRARIES yh_210609_1
CATKIN_DEPENDS std_msgs roscpp
)

include_directories(${catkin_INCLUDE_DIRS})

add_executable(yh_server_1 src/yh_server_1.cpp)
add_executable(yh_client_1 src/yh_client_1.cpp)

add_dependencies(yh_server_1 ${${PROJECT_NAME}_EXPROTED_TARGETS} ${catkin_EXPORTED_TARGETS})
add_dependencies(yh_client_1 ${${PROJECT_NAME}_EXPROTED_TARGETS} ${catkin_EXPORTED_TARGETS})

target_link_libraries(yh_server_1 ${catkin_LIBRARIES})
target_link_libraries(yh_client_1 ${catkin_LIBRARIES})

 

7. 빌드

모든것이 준비되었으니 이제 빌드를 해보도록 하자

# ~/catkin_ws 폴더로 이동

$ catkin_make

$ rospack profile

$ roscore

# 새로운 터미널창을 열고
$ rosrun yh_210609_1 yh_server_1
# 서비스 노드를 실행하면 클라이언트 값을 받을때까지 대기한다

# 또 새로운 터미널을 열고
$ rosrun yh_210608_1 yh_client_1 2 3
# 클라이언트를 실행하면서 2와 3이라는 값을 출력

클라이언트가 2와 3을 제시했으니, 서비스서버에서 2는 a값, 3은 b값으로 대입하여 a+b = result 계산을 실행하여 그 결과를 다시 클라이언트에게 응답할 것이다

이렇게 서비스 서버 통신을 간략하게 구현해보았다. 꼭 계산식이 아니더라도 상황에 따라 필요한 값을 받고자할때 사용하는 패키지로 이해하고 적용시킨다면 좋을 것이다

 

내일은 액션 서버통신을 도전!