PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1
22
23#include <atomic>
24#include <chrono>
25#include <csignal>
26#include <cstdlib>
27#include <iomanip>
28#include <iostream>
29#include <string>
30
31namespace {
32
34std::atomic<kcenon::pacs::network::dicom_server*> g_server{nullptr};
35
37std::atomic<bool> g_running{true};
38
43void signal_handler(int signal) {
44 std::cout << "\nReceived signal " << signal << ", shutting down...\n";
45 g_running = false;
46
47 auto* server = g_server.load();
48 if (server) {
49 server->stop();
50 }
51}
52
56void install_signal_handlers() {
57 std::signal(SIGINT, signal_handler);
58 std::signal(SIGTERM, signal_handler);
59#ifndef _WIN32
60 std::signal(SIGHUP, signal_handler);
61#endif
62}
63
68void print_usage(const char* program_name) {
69 std::cout << R"(
70Echo SCP - DICOM Connectivity Test Server
71
72Usage: )" << program_name << R"( <port> <ae_title> [options]
73
74Arguments:
75 port Port number to listen on (typically 104 or 11112)
76 ae_title Application Entity Title for this server (max 16 chars)
77
78Options:
79 --max-assoc <n> Maximum concurrent associations (default: 10)
80 --timeout <sec> Idle timeout in seconds (default: 300)
81 --help Show this help message
82
83Examples:
84 )" << program_name << R"( 11112 MY_PACS
85 )" << program_name << R"( 104 DICOM_SERVER --max-assoc 20
86
87Notes:
88 - Press Ctrl+C to stop the server gracefully
89 - The server responds to C-ECHO requests from any calling AE
90
91Exit Codes:
92 0 Normal termination
93 1 Error - Failed to start server
94)";
95}
96
107bool parse_arguments(
108 int argc,
109 char* argv[],
110 uint16_t& port,
111 std::string& ae_title,
112 size_t& max_associations,
113 uint32_t& idle_timeout) {
114
115 if (argc < 3) {
116 return false;
117 }
118
119 // Check for help flag
120 for (int i = 1; i < argc; ++i) {
121 if (std::string(argv[i]) == "--help" || std::string(argv[i]) == "-h") {
122 return false;
123 }
124 }
125
126 // Parse port
127 try {
128 int port_int = std::stoi(argv[1]);
129 if (port_int < 1 || port_int > 65535) {
130 std::cerr << "Error: Port must be between 1 and 65535\n";
131 return false;
132 }
133 port = static_cast<uint16_t>(port_int);
134 } catch (const std::exception&) {
135 std::cerr << "Error: Invalid port number '" << argv[1] << "'\n";
136 return false;
137 }
138
139 // Parse AE title
140 ae_title = argv[2];
141 if (ae_title.length() > 16) {
142 std::cerr << "Error: AE title exceeds 16 characters\n";
143 return false;
144 }
145
146 // Default values
147 max_associations = 10;
148 idle_timeout = 300;
149
150 // Parse optional arguments
151 for (int i = 3; i < argc; ++i) {
152 std::string arg = argv[i];
153
154 if (arg == "--max-assoc" && i + 1 < argc) {
155 try {
156 int val = std::stoi(argv[++i]);
157 if (val < 1) {
158 std::cerr << "Error: max-assoc must be positive\n";
159 return false;
160 }
161 max_associations = static_cast<size_t>(val);
162 } catch (const std::exception&) {
163 std::cerr << "Error: Invalid max-assoc value\n";
164 return false;
165 }
166 } else if (arg == "--timeout" && i + 1 < argc) {
167 try {
168 int val = std::stoi(argv[++i]);
169 if (val < 0) {
170 std::cerr << "Error: timeout cannot be negative\n";
171 return false;
172 }
173 idle_timeout = static_cast<uint32_t>(val);
174 } catch (const std::exception&) {
175 std::cerr << "Error: Invalid timeout value\n";
176 return false;
177 }
178 } else {
179 std::cerr << "Error: Unknown option '" << arg << "'\n";
180 return false;
181 }
182 }
183
184 return true;
185}
186
191std::string current_timestamp() {
192 auto now = std::chrono::system_clock::now();
193 auto time_t_now = std::chrono::system_clock::to_time_t(now);
194 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
195 now.time_since_epoch()) % 1000;
196
197 std::ostringstream oss;
198 oss << std::put_time(std::localtime(&time_t_now), "%Y-%m-%d %H:%M:%S");
199 oss << '.' << std::setfill('0') << std::setw(3) << ms.count();
200 return oss.str();
201}
202
211bool run_server(
212 uint16_t port,
213 const std::string& ae_title,
214 size_t max_associations,
215 uint32_t idle_timeout) {
216
217 using namespace kcenon::pacs::network;
218 using namespace kcenon::pacs::services;
219
220 std::cout << "\nStarting Echo SCP...\n";
221 std::cout << " AE Title: " << ae_title << "\n";
222 std::cout << " Port: " << port << "\n";
223 std::cout << " Max Associations: " << max_associations << "\n";
224 std::cout << " Idle Timeout: " << idle_timeout << " seconds\n\n";
225
226 // Configure server
227 server_config config;
228 config.ae_title = ae_title;
229 config.port = port;
230 config.max_associations = max_associations;
231 config.idle_timeout = std::chrono::seconds{idle_timeout};
232 config.implementation_class_uid = "1.2.826.0.1.3680043.2.1545.1";
233 config.implementation_version_name = "ECHO_SCP_001";
234
235 // Create server
236 dicom_server server{config};
237 g_server = &server;
238
239 // Register verification service (handles C-ECHO)
240 server.register_service(std::make_shared<verification_scp>());
241
242 // Set up callbacks for logging
243 server.on_association_established([](const association& assoc) {
244 std::cout << "[" << current_timestamp() << "] "
245 << "Association established from: " << assoc.calling_ae()
246 << " -> " << assoc.called_ae() << "\n";
247 });
248
249 server.on_association_released([](const association& assoc) {
250 std::cout << "[" << current_timestamp() << "] "
251 << "Association released: " << assoc.calling_ae() << "\n";
252 });
253
254 server.on_error([](const std::string& error) {
255 std::cerr << "[" << current_timestamp() << "] "
256 << "Error: " << error << "\n";
257 });
258
259 // Start server
260 auto result = server.start();
261 if (result.is_err()) {
262 std::cerr << "Failed to start server: " << result.error().message << "\n";
263 g_server = nullptr;
264 return false;
265 }
266
267 std::cout << "=================================================\n";
268 std::cout << " Echo SCP is running on port " << port << "\n";
269 std::cout << " Press Ctrl+C to stop\n";
270 std::cout << "=================================================\n\n";
271
272 // Wait for shutdown
273 server.wait_for_shutdown();
274
275 // Print final statistics
276 auto stats = server.get_statistics();
277 std::cout << "\n";
278 std::cout << "=================================================\n";
279 std::cout << " Server Statistics\n";
280 std::cout << "=================================================\n";
281 std::cout << " Total Associations: " << stats.total_associations << "\n";
282 std::cout << " Rejected Associations: " << stats.rejected_associations << "\n";
283 std::cout << " Messages Processed: " << stats.messages_processed << "\n";
284 std::cout << " Bytes Received: " << stats.bytes_received << "\n";
285 std::cout << " Bytes Sent: " << stats.bytes_sent << "\n";
286 std::cout << " Uptime: " << stats.uptime().count() << " seconds\n";
287 std::cout << "=================================================\n";
288
289 g_server = nullptr;
290 return true;
291}
292
293} // namespace
294
295int main(int argc, char* argv[]) {
296 std::cout << R"(
297 _____ ____ _ _ ___ ____ ____ ____
298 | ____/ ___| | | |/ _ \ / ___| / ___| _ \
299 | _|| | | |_| | | | | \___ \| | | |_) |
300 | |__| |___| _ | |_| | ___) | |___| __/
301 |_____\____|_| |_|\___/ |____/ \____|_|
302
303 DICOM Connectivity Test Server
304)" << "\n";
305
306 uint16_t port = 0;
307 std::string ae_title;
308 size_t max_associations = 0;
309 uint32_t idle_timeout = 0;
310
311 if (!parse_arguments(argc, argv, port, ae_title, max_associations, idle_timeout)) {
312 print_usage(argv[0]);
313 return 1;
314 }
315
316 // Install signal handlers
317 install_signal_handlers();
318
319 bool success = run_server(port, ae_title, max_associations, idle_timeout);
320
321 std::cout << "\nEcho SCP terminated\n";
322 return success ? 0 : 1;
323}
Multi-threaded DICOM server for handling multiple associations.
int main()
Definition main.cpp:84
@ error
Node returned an error.
DICOM Server configuration structures.
DICOM Verification SCP service (C-ECHO handler)