PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
main.cpp File Reference

Level 2 Sample: Echo Server - DICOM network communication fundamentals. More...

#include "console_utils.h"
#include "signal_handler.h"
#include <kcenon/pacs/network/dicom_server.h>
#include <kcenon/pacs/network/server_config.h>
#include <kcenon/pacs/network/association.h>
#include <kcenon/pacs/services/verification_scp.h>
#include <atomic>
#include <chrono>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <memory>
#include <string>
Include dependency graph for main.cpp:

Go to the source code of this file.

Functions

int main (int argc, char *argv[])
 

Detailed Description

Level 2 Sample: Echo Server - DICOM network communication fundamentals.

This sample demonstrates DICOM networking concepts:

  • Server configuration (AE Title, port, timeouts)
  • Association management (connection, negotiation, release)
  • Service Class Provider (SCP) pattern
  • C-ECHO operation (Verification service)

After completing this sample, you will understand how to:

  1. Configure a DICOM server with proper network parameters
  2. Register SCP services to handle DIMSE requests
  3. Set up callbacks for association lifecycle events
  4. Implement graceful shutdown handling
See also
DICOM PS3.7 Section 9.1 - C-ECHO Service
DICOM PS3.8 - Network Communication Support

Definition in file main.cpp.

Function Documentation

◆ main()

int main ( int argc,
char * argv[] )

Definition at line 60 of file main.cpp.

60 {
61 kcenon::pacs::samples::print_header("Echo Server - Level 2 Sample");
62
63 // ===========================================================================
64 // Part 1: Server Configuration
65 // ===========================================================================
66 // DICOM servers require several network parameters:
67 // - AE Title: Application Entity identifier (max 16 characters)
68 // This is like a hostname in DICOM networking
69 // - Port: TCP port to listen on (11112 is a common non-privileged port)
70 // - Max PDU Length: Maximum Protocol Data Unit size for data transfer
71 // - Timeouts: Connection and idle timeouts for resource management
72 // - Max Associations: Limit concurrent connections to prevent overload
73
74 kcenon::pacs::samples::print_section("Part 1: Server Configuration");
75
76 std::cout << "DICOM servers use these key parameters:\n";
77 std::cout << " - AE Title: Application Entity identifier (like hostname)\n";
78 std::cout << " - Port: TCP listening port\n";
79 std::cout << " - Max PDU: Maximum data unit size per transfer\n";
80 std::cout << " - Timeouts: Connection and idle timeouts\n\n";
81
82 // Parse optional port argument
83 uint16_t port = 11112; // Default port
84 if (argc > 1) {
85 try {
86 int port_arg = std::stoi(argv[1]);
87 if (port_arg > 0 && port_arg < 65536) {
88 port = static_cast<uint16_t>(port_arg);
89 }
90 } catch (const std::exception&) {
91 // Keep default port if parsing fails
92 }
93 }
94
95 // Create server configuration
96 net::server_config config;
97 config.ae_title = "ECHO_SCP"; // Our AE Title
98 config.port = port; // Listening port
99 config.max_associations = 10; // Concurrent connections
100 config.idle_timeout = std::chrono::seconds(60); // Idle disconnect
101 config.max_pdu_size = 16384; // 16KB PDU size
102
103 // Implementation identification (optional but recommended for interoperability)
104 // The Implementation Class UID uniquely identifies your software implementation
105 config.implementation_class_uid = "1.2.410.200001.1.1";
106 config.implementation_version_name = "PACS_SAMPLE_2.0";
107
108 kcenon::pacs::samples::print_table("Server Configuration", {
109 {"AE Title", config.ae_title},
110 {"Port", std::to_string(config.port)},
111 {"Max Associations", std::to_string(config.max_associations)},
112 {"Idle Timeout", std::to_string(config.idle_timeout.count()) + " seconds"},
113 {"Max PDU Size", std::to_string(config.max_pdu_size) + " bytes"}
114 });
115
116 kcenon::pacs::samples::print_success("Part 1 complete - Configuration ready!");
117
118 // ===========================================================================
119 // Part 2: Create Server and Register Services
120 // ===========================================================================
121 // DICOM servers host one or more Service Class Providers (SCPs).
122 // Each SCP handles specific DIMSE (DICOM Message Service Element) operations:
123 // - Verification SCP: Handles C-ECHO (connectivity test)
124 // - Storage SCP: Handles C-STORE (image storage)
125 // - Query SCP: Handles C-FIND (data query)
126 // - Retrieve SCP: Handles C-GET/C-MOVE (data retrieval)
127 //
128 // For this sample, we only register the Verification SCP to handle C-ECHO.
129 // C-ECHO is the simplest DICOM service - like a "ping" for DICOM.
130
131 kcenon::pacs::samples::print_section("Part 2: Server & Service Setup");
132
133 std::cout << "DICOM Service Class Provider (SCP) types:\n";
134 std::cout << " - Verification SCP: C-ECHO (connectivity test)\n";
135 std::cout << " - Storage SCP: C-STORE (receive images)\n";
136 std::cout << " - Query SCP: C-FIND (search data)\n";
137 std::cout << " - Retrieve SCP: C-GET/C-MOVE (retrieve data)\n\n";
138
139 // Create server instance
140 auto server = std::make_unique<net::dicom_server>(config);
141
142 // Register Verification SCP (C-ECHO handler)
143 // The Verification SOP Class UID is: 1.2.840.10008.1.1
144 // This is the standard UID for the Verification service
145 auto verification_service = std::make_shared<svc::verification_scp>();
146 server->register_service(verification_service);
147
148 std::cout << "Registered services:\n";
149 for (const auto& sop_class : server->supported_sop_classes()) {
150 if (sop_class == "1.2.840.10008.1.1") {
151 std::cout << " - Verification SCP (1.2.840.10008.1.1)\n";
152 } else {
153 std::cout << " - " << sop_class << "\n";
154 }
155 }
156
157 kcenon::pacs::samples::print_success("Part 2 complete - Verification SCP registered!");
158
159 // ===========================================================================
160 // Part 3: Event Callbacks
161 // ===========================================================================
162 // DICOM associations go through a lifecycle:
163 // 1. Association Request (A-ASSOCIATE-RQ): Client requests connection
164 // 2. Association Accept (A-ASSOCIATE-AC): Server accepts connection
165 // 3. DIMSE Exchange: Client sends C-ECHO-RQ, server responds C-ECHO-RSP
166 // 4. Association Release (A-RELEASE): Clean disconnection
167 //
168 // The server provides callbacks for monitoring these events.
169 // This is useful for logging, statistics, and debugging.
170
171 kcenon::pacs::samples::print_section("Part 3: Event Handlers");
172
173 std::cout << "DICOM Association lifecycle:\n";
174 std::cout << " 1. A-ASSOCIATE-RQ -> Client requests connection\n";
175 std::cout << " 2. A-ASSOCIATE-AC -> Server accepts\n";
176 std::cout << " 3. DIMSE exchange -> C-ECHO, C-STORE, etc.\n";
177 std::cout << " 4. A-RELEASE -> Clean disconnection\n\n";
178
179 // Track connection statistics
180 std::atomic<int> connection_count{0};
181 std::atomic<int> total_connections{0};
182
183 // Set up association established callback
184 // Called when a new association is successfully negotiated
185 server->on_association_established(
186 [&connection_count, &total_connections](const net::association& assoc) {
187 connection_count++;
188 total_connections++;
189 std::cout << "[" << current_timestamp() << "] "
192 << assoc.calling_ae() << " -> " << assoc.called_ae()
193 << " (active: " << connection_count << ")\n";
194 }
195 );
196
197 // Set up association released callback
198 // Called when an association is gracefully released
199 server->on_association_released(
200 [&connection_count](const net::association& assoc) {
201 connection_count--;
202 std::cout << "[" << current_timestamp() << "] "
205 << assoc.calling_ae() << " disconnected"
206 << " (active: " << connection_count << ")\n";
207 }
208 );
209
210 // Set up error callback
211 // Called when network or protocol errors occur
212 server->on_error(
213 [](const std::string& error_msg) {
214 std::cerr << "[" << current_timestamp() << "] "
217 << error_msg << "\n";
218 }
219 );
220
221 kcenon::pacs::samples::print_success("Part 3 complete - Event handlers registered!");
222
223 // ===========================================================================
224 // Part 4: Start Server
225 // ===========================================================================
226 // The server starts listening on the configured port.
227 // It runs in the background, handling associations in a thread pool.
228 // The main thread waits for a shutdown signal (Ctrl+C).
229
230 kcenon::pacs::samples::print_section("Part 4: Running Server");
231
232 // Install signal handler for graceful shutdown (Ctrl+C)
233 // scoped_signal_handler automatically cleans up when destroyed
234 kcenon::pacs::samples::scoped_signal_handler sig_handler([&server]() {
235 std::cout << "\n" << kcenon::pacs::samples::colors::yellow
236 << "Shutdown signal received..."
238 server->stop();
239 });
240
241 // Start the server (non-blocking)
242 auto start_result = server->start();
243 if (start_result.is_err()) {
244 kcenon::pacs::samples::print_error("Failed to start server: " +
245 start_result.error().message);
246 return 1;
247 }
248
249 // Print server running banner
251 "Echo Server Running",
252 "",
253 "Test with DCMTK:",
254 " echoscu -aec ECHO_SCP localhost " + std::to_string(config.port),
255 "",
256 "Test multiple connections:",
257 " for i in {1..5}; do echoscu -aec ECHO_SCP localhost " +
258 std::to_string(config.port) + "; done",
259 "",
260 "Press Ctrl+C to stop"
261 });
262
263 // Wait for shutdown signal
264 // This blocks until Ctrl+C is pressed or server->stop() is called
265 sig_handler.wait();
266
267 // ===========================================================================
268 // Part 5: Statistics and Cleanup
269 // ===========================================================================
270 // After shutdown, we can retrieve server statistics for monitoring.
271 // The server automatically cleans up all resources.
272
273 kcenon::pacs::samples::print_section("Final Statistics");
274
275 auto stats = server->get_statistics();
276
277 kcenon::pacs::samples::print_table("Server Statistics", {
278 {"Total Associations", std::to_string(stats.total_associations)},
279 {"Rejected Associations", std::to_string(stats.rejected_associations)},
280 {"Messages Processed", std::to_string(stats.messages_processed)},
281 {"Bytes Received", std::to_string(stats.bytes_received)},
282 {"Bytes Sent", std::to_string(stats.bytes_sent)},
283 {"Uptime", std::to_string(stats.uptime().count()) + " seconds"}
284 });
285
286 // Summary
288 "Congratulations! You have learned:",
289 "",
290 "1. Server Configuration - AE Title, port, timeouts",
291 "2. Service Registration - SCP services for DIMSE handling",
292 "3. Association Lifecycle - Connect, exchange, release",
293 "4. Event Callbacks - Monitoring and logging",
294 "5. Graceful Shutdown - Clean resource cleanup",
295 "",
296 "Next step: Level 3 - Storage Server (C-STORE SCP)"
297 });
298
299 kcenon::pacs::samples::print_success("Echo Server terminated successfully.");
300
301 return 0;
302}
DICOM Association management class.
std::string_view calling_ae() const noexcept
Get calling AE title.
std::string_view called_ae() const noexcept
Get called AE title.
void wait()
Block until shutdown signal is received.
constexpr const char * reset
Reset all formatting.
constexpr const char * red
constexpr const char * yellow
constexpr const char * green
constexpr const char * cyan
void print_error(std::string_view message)
Print an error message.
void print_table(std::string_view title, const std::vector< std::pair< std::string, std::string > > &rows)
Print a key-value table.
void print_header(std::string_view title)
Print a header/title with decorative border.
void print_success(std::string_view message)
Print a success message.
void print_section(std::string_view title)
Print a section header.
void print_box(const std::vector< std::string > &lines)
Print text in a decorative box.
Configuration for DICOM server.
size_t max_associations
Maximum concurrent associations (0 = unlimited)
std::chrono::seconds idle_timeout
Idle timeout for associations (0 = no timeout)
uint32_t max_pdu_size
Maximum PDU size for data transfer.
std::string implementation_version_name
Implementation Version Name.
uint16_t port
Port to listen on (default: 11112, standard alternate DICOM port)
std::string ae_title
Application Entity Title for this server (16 chars max)
std::string implementation_class_uid
Implementation Class UID.

References kcenon::pacs::network::server_config::ae_title, kcenon::pacs::network::association::called_ae(), kcenon::pacs::network::association::calling_ae(), kcenon::pacs::samples::colors::cyan, kcenon::pacs::samples::colors::green, kcenon::pacs::network::server_config::idle_timeout, kcenon::pacs::network::server_config::implementation_class_uid, kcenon::pacs::network::server_config::implementation_version_name, kcenon::pacs::network::server_config::max_associations, kcenon::pacs::network::server_config::max_pdu_size, kcenon::pacs::network::server_config::port, kcenon::pacs::samples::print_box(), kcenon::pacs::samples::print_error(), kcenon::pacs::samples::print_header(), kcenon::pacs::samples::print_section(), kcenon::pacs::samples::print_success(), kcenon::pacs::samples::print_table(), kcenon::pacs::samples::colors::red, kcenon::pacs::samples::colors::reset, kcenon::pacs::samples::scoped_signal_handler::wait(), and kcenon::pacs::samples::colors::yellow.

Here is the call graph for this function: