PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1
21#include "console_utils.h"
22#include "signal_handler.h"
23
28
29#include <atomic>
30#include <chrono>
31#include <cstdlib>
32#include <iomanip>
33#include <iostream>
34#include <memory>
35#include <string>
36
37namespace net = kcenon::pacs::network;
38namespace svc = kcenon::pacs::services;
39
40namespace {
41
46std::string current_timestamp() {
47 auto now = std::chrono::system_clock::now();
48 auto time_t_now = std::chrono::system_clock::to_time_t(now);
49 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
50 now.time_since_epoch()) % 1000;
51
52 std::ostringstream oss;
53 oss << std::put_time(std::localtime(&time_t_now), "%H:%M:%S");
54 oss << '.' << std::setfill('0') << std::setw(3) << ms.count();
55 return oss.str();
56}
57
58} // namespace
59
60int main(int argc, char* argv[]) {
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}
int main()
Definition main.cpp:34
DICOM Association management per PS3.8.
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.
Console output utilities for developer samples.
Multi-threaded DICOM server for handling multiple associations.
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.
DICOM Server configuration structures.
Cross-platform signal handling for graceful shutdown.
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.
DICOM Verification SCP service (C-ECHO handler)