1. 서비스 서버 만들기
이제 Python 패키지에 서비스 서버 파일을 만듭니다.
파일 위치는 다음과 같습니다.
my_first_package/my_first_package/my_service_server.py
먼저 서버 파일을 생성합니다.
cd ~/ros2_study/src/my_first_package/my_first_package
touch my_service_server.py

my_service_server.py에 아래와 같은 소스 코드를 작성하시기 바랍니다.
from my_first_package_msgs.srv import MultiSpawn
import rclpy as rp
from rclpy.node import Node
class MultiSpawning(Node):
def __init__(self):
super().__init__('multi_spawn')
self.server = self.create_service(
MultiSpawn,
'multi_spawn',
self.callback_service
)
def callback_service(self, request, response):
print('Request : ', request)
response.x = [1., 2., 3.]
response.y = [10., 20., 30.]
response.theta = [100., 200., 300.]
return response
def main(args=None):
rp.init(args=args)
multi_spawn = MultiSpawning()
rp.spin(multi_spawn)
rp.shutdown()
if __name__ == '__main__':
main()

2. 코드 설명
a. 서비스 타입 import
from my_first_package_msgs.srv import MultiSpawn
사용자 정의 서비스 MultiSpawn을 Python 코드에서 사용하기 위해 import 합니다.
b. 서비스 서버 생성
self.server = self.create_service(
MultiSpawn,
'multi_spawn',
self.callback_service
)
이 코드는 /multi_spawn 서비스를 생성합니다.
구조는 다음과 같습니다.
MultiSpawn → 서비스 타입
'multi_spawn' → 서비스 이름
self.callback_service → 요청이 들어왔을 때 실행할 함수
c. 요청 확인
print('Request : ', request)
서비스 클라이언트가 보낸 요청 값을 터미널에서 확인하기 위한 코드입니다.
d. 응답 작성
response.x = [1., 2., 3.]
response.y = [10., 20., 30]
response.theta = [100., 200., 300.]
응답 필드는 .srv 파일의 아래쪽에 정의한 값과 이름이 같아야 합니다.
float64[] x
float64[] y
float64[] theta
3. setup.py에 서비스 서버 등록하기
my_first_package/setup.py의 entry_points에 다음 항목을 추가합니다.
'my_service_server = my_first_package.my_service_server:main',
이전 실습에서 작성한 내용을 포함해서 수정한 결과는 아래와 같습니다.
entry_points={
'console_scripts': [
'my_first_node = my_first_package.my_first_node:main',
'my_publisher = my_first_package.my_publisher:main',
'my_subscriber = my_first_package.my_subscriber:main',
'turtle_cmd_and_pose = my_first_package.turtle_cmd_and_pose:main',
'my_service_server = my_first_package.my_service_server:main',
],
},
이 설정을 해야 다음 명령으로 실행할 수 있습니다.
ros2 run my_first_package my_service

4. 서비스 서버 빌드하기
cd ~/ros2_study
colcon build --packages-select my_first_package

환경 적용도 다시 합니다
source install/local_setup.bash
또는
sl

5. 서비스 서버 실행하기
터미널에서 다음 명령을 실행합니다.
ros2 run my_first_package my_service_server
실행 후에는 특별한 출력이 없어도 정상입니다.
서비스 서버는 요청이 들어올 때까지 대기하게 됩니다.

6. 서비스 목록 확인하기
서비스 서버가 실행된 상태에서 다른 터미널에서 아래의 명령어를 실행하여 서비스 목록을 확인합니다.
ros2 service list -t

위의 실행결과에서
/multi_spawn [my_first_package_msgs/srv/MultiSpawn] 항목이 출력되었습니다.
이 결과는 /multi_spawn이라는 서비스가 실행 중이며, 타입은 my_first_package_msgs/srv/MultiSpawn이라는 뜻입니다.
7. 서비스 요청 보내기
이제 터미널에서 직접 서비스를 호출합니다.
ros2 service call /multi_spawn my_first_package_msgs/srv/MultiSpawn "{num: 1}"
정상적으로 요청이 들어가면 서버 터미널에는 다음과 비슷한 출력이 보입니다.

클라이언트 터미널에도 아래와 같은 응답이 출력됩니다.

이 예제에서는 num 값과 관계없이 고정된 리스트를 응답하지만, 실제로는 num에 따라 좌표 개수를 다르게 생성하도록 만들 수 있습니다.
8. 서비스 서버에서 다른 서비스 클라이언트 호출하기
다음 단계는 서비스 서버 안에서 또 다른 서비스 클라이언트를 사용하는 구조입니다.
여기서는 turtlesim의 기본 서비스인 /turtle1/teleport_absolute를 호출합니다.
이 서비스는 거북이를 특정 좌표로 순간 이동시키는 기능입니다.
먼저 import를 추가합니다.
from turtlesim.srv import TeleportAbsolute

그리고 클래스 초기화 부분에 클라이언트를 생성합니다.
self.teleport = self.create_client(
TeleportAbsolute,
'/turtle1/teleport_absolute'
)

서비스 요청 객체도 생성합니다.
self.req_teleport = TeleportAbsolute.Request()

콜백 함수 안에서는 다음처럼 요청 값을 설정하고 서비스를 호출합니다.
def callback_service(self, request, response):
self.req_teleport.x = 1.
self.teleport.call_async(self.req_teleport)
return response

이 코드는 /multi_spawn 서비스가 호출되었을 때 내부에서 /turtle1/teleport_absolute 서비스를 다시 호출하는 구조입니다.
9. 서비스 안에서 서비스 클라이언트를 사용하는 이유
실제 로봇 시스템에서는 하나의 서비스 요청이 여러 동작을 수행하는 경우가 많습니다.
예를 들어 드론 배송 시스템을 생각해 보겠습니다.
배송 시작 서비스 호출
→ 드론 arm
→ 이륙
→ 웨이포인트 이동
→ 그리퍼 열기
→ 복귀
→ 착륙
사용자는 하나의 서비스만 호출하지만, 내부에서는 여러 서비스나 액션이 순차적으로 실행될 수 있습니다.
이번 예제의 구조도 같은 개념입니다.
/multi_spawn 호출
→ 서버 콜백 실행
→ 내부에서 /turtle1/teleport_absolute 서비스 호출
즉, 서비스 서버가 단순 응답만 하는 것이 아니라 다른 ROS 2 기능을 제어하는 컨트롤러 역할을 하게 됩니다.
10. 최종 구조 정리
이번 내용까지 포함하면 전체 구조는 다음과 같습니다.
my_first_package_msgs
├── msg
│ └── CmdAndPoseVel.msg
└── srv
└── MultiSpawn.srv
my_first_package
├── turtle_cmd_and_pose.py
├── my_publisher.py
└── my_service_server.py
노드와 토픽 관계는 다음과 같습니다.
my_publisher
↓ /turtle1/cmd_vel
turtlesim
↓ /turtle1/pose
turtle_cmd_pose
↓ /cmd_and_pose
topic echo 또는 다른 노드
서비스 관계는 다음과 같습니다.
ros2 service call
↓
/multi_spawn
↓
my_service_server
↓
/turtle1/teleport_absolute
↓
turtlesim
11. 실무 관점에서 보는 핵심 포인트
이번 예제는 단순히 turtlesim을 움직이는 실습처럼 보이지만, 실제 ROS 2 시스템 구조와 매우 비슷합니다.
토픽은 지속적인 데이터 흐름에 적합합니다.
센서 데이터
위치 데이터
속도 명령
상태 모니터링
영상 프레임
서비스는 명확한 요청과 응답이 필요한 작업에 적합합니다.
초기화
설정 변경
로봇 생성
좌표 이동
상태 요청
특정 작업 실행
따라서 실제 프로젝트에서는 보통 다음처럼 섞어서 사용합니다.
토픽: 계속 흘러야 하는 데이터
서비스: 한 번 요청하고 결과를 받아야 하는 작업
액션: 오래 걸리고 중간 피드백이 필요한 작업
예를 들어 드론에서는 다음처럼 설계할 수 있습니다.
/vehicle_odometry → 토픽
/vehicle_status → 토픽
/mission_start → 서비스
/gripper_open → 서비스
/navigate_to_waypoint → 액션
12. 자주 발생하는 오류
a. 서비스가 목록에 안 보이는 경우
ros2 service list -t
에서 /multi_spawn이 안 보이면 다음을 확인하셔야 합니다.
1. my_service_server 노드가 실행 중인지
2. setup.py entry_points에 등록했는지
3. colcon build 후 source를 다시 했는지
4. 서비스 이름을 잘못 입력하지 않았는지
b. 서비스 타입을 못 찾는 경우
ros2 interface show my_first_package_msgs/srv/MultiSpawn
에서 오류가 나면 메시지 패키지 설정 문제일 가능성이 높습니다.
확인할 파일은 다음 두 개입니다.
CMakeLists.txt
package.xml
특히 CMakeLists.txt에 아래 코드가 있어야 합니다.
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/CmdAndPoseVel.msg"
"srv/MultiSpawn.srv"
)
c. Python import 오류
다음 코드에서 오류가 날 수 있습니다.
from my_first_package_msgs.srv import MultiSpawn
이 경우 대부분 빌드 또는 source 문제입니다.
해결 순서는 다음과 같습니다.
cd ~/ros2_study
colcon build
source install/local_setup.bash
그래도 안 되면 캐시를 지우고 다시 빌드합니다.
rm -rf build install log
colcon build
source install/local_setup.bash
'강좌 > ROS2' 카테고리의 다른 글
| 서비스 정의 만들기 (0) | 2026.05.21 |
|---|---|
| 토픽, 서비스, 액션 정리 (0) | 2026.05.19 |
| ROS2 사용자 정의 메세지 만들기 #3 (0) | 2026.05.09 |
| ROS2 사용자 정의 메세지 만들기 #2 (0) | 2026.05.09 |
| ROS2 사용자 정의 메세지 만들기 #1 (0) | 2026.05.09 |
