Panda3D
Loading...
Searching...
No Matches
directd.cxx
Go to the documentation of this file.
1/**
2 * PANDA 3D SOFTWARE
3 * Copyright (c) Carnegie Mellon University. All rights reserved.
4 *
5 * All use of this software is subject to the terms of the revised BSD
6 * license. You should have received a copy of this license along
7 * with this source code in a file named "LICENSE."
8 *
9 * @file directd.cxx
10 * @author skyler
11 * @date 2002-04-08
12 * Based on test_tcp_*.* by drose.
13 */
14
15// This define tells the windows headers to include job objects:
16#define _WIN32_WINNT 0x0500
17
18#include "directd.h"
19/*#include "pandaFramework.h"
20#include "queuedConnectionManager.h"*/
21
22// #include <process.h> #include <Windows.h> #include "pandabase.h"
23
24// #include "queuedConnectionManager.h" #include "queuedConnectionListener.h"
25// #include "queuedConnectionReader.h" #include "connectionWriter.h"
26#include "netAddress.h"
27#include "connection.h"
28#include "datagramIterator.h"
29#include "netDatagram.h"
30
31#include "pset.h"
32
33#if !defined(CPPPARSER) && !defined(LINK_ALL_STATIC) && !defined(BUILDING_DIRECT_DIRECTD)
34 #error Buildsystem error: BUILDING_DIRECT_DIRECTD not defined
35#endif
36
37using std::cerr;
38using std::cout;
39using std::endl;
40using std::string;
41
42namespace {
43 // ...This section is part of the old stuff from the original
44 // implementation. The new stuff that uses job objects doesn't need this
45 // stuff:
46
47 // The following is from an MSDN example:
48
49 #define TA_FAILED 0
50 #define TA_SUCCESS_CLEAN 1
51 #define TA_SUCCESS_KILL 2
52 #define TA_SUCCESS_16 3
53
54 BOOL CALLBACK
55 TerminateAppEnum(HWND hwnd, LPARAM lParam) {
56 DWORD dwID;
57 GetWindowThreadProcessId(hwnd, &dwID);
58 if(dwID == (DWORD)lParam) {
59 PostMessage(hwnd, WM_CLOSE, 0, 0);
60 }
61 return TRUE;
62 }
63
64 /*
65 DWORD WINAPI TerminateApp(DWORD dwPID, DWORD dwTimeout)
66
67 Purpose:
68 Shut down a 32-Bit Process
69
70 Parameters:
71 dwPID
72 Process ID of the process to shut down.
73
74 dwTimeout
75 Wait time in milliseconds before shutting down the process.
76
77 Return Value:
78 TA_FAILED - If the shutdown failed.
79 TA_SUCCESS_CLEAN - If the process was shutdown using WM_CLOSE.
80 TA_SUCCESS_KILL - if the process was shut down with
81 TerminateProcess().
82 */
83 DWORD WINAPI
84 TerminateApp(DWORD dwPID, DWORD dwTimeout) {
85 HANDLE hProc;
86 DWORD dwRet;
87
88 // If we can't open the process with PROCESS_TERMINATE rights, then we
89 // give up immediately.
90 hProc = OpenProcess(SYNCHRONIZE|PROCESS_TERMINATE, FALSE, dwPID);
91 if(hProc == nullptr) {
92 return TA_FAILED;
93 }
94
95 // TerminateAppEnum() posts WM_CLOSE to all windows whose PID matches your
96 // process's.
97 EnumWindows((WNDENUMPROC)TerminateAppEnum, (LPARAM)dwPID);
98
99 // Wait on the handle. If it signals, great. If it times out, then you
100 // kill it.
101 if(WaitForSingleObject(hProc, dwTimeout)!=WAIT_OBJECT_0) {
102 dwRet=(TerminateProcess(hProc,0)?TA_SUCCESS_KILL:TA_FAILED);
103 } else {
104 dwRet = TA_SUCCESS_CLEAN;
105 }
106 CloseHandle(hProc);
107
108 return dwRet;
109 }
110
111 /*
112 Start an application with the command line cmd.
113 returns the process id of the new process/application.
114 */
115 DWORD
116 StartApp(const string& cmd) {
117 DWORD pid=0;
118 STARTUPINFO si;
119 PROCESS_INFORMATION pi;
120 ZeroMemory(&si, sizeof(STARTUPINFO));
121 si.cb = sizeof(STARTUPINFO);
122 ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
123 if (CreateProcess(nullptr, (char*)cmd.c_str(),
124 0, 0, 1, NORMAL_PRIORITY_CLASS,
125 0, 0, &si, &pi)) {
126 pid=pi.dwProcessId;
127 CloseHandle(pi.hProcess);
128 CloseHandle(pi.hThread);
129 } else {
130 nout<<"CreateProcess failed: "<<cmd<<endl;
131 }
132 return pid;
133 }
134
135}
136
137DirectD::DirectD() :
138 _reader(&_cm, 1), _writer(&_cm, 0), _listener(&_cm, 0),
139 _jobObject(0), _shutdown(false), _useOldStuff(false) {
140}
141
142DirectD::~DirectD() {
143 // Close all the connections:
144 ConnectionSet::iterator ci;
145 for (ci = _connections.begin(); ci != _connections.end(); ++ci) {
146 _cm.close_connection((*ci));
147 }
148 _connections.clear();
149
150 kill_all();
151}
152
153int
154DirectD::client_ready(const string& server_host, int port,
155 const string& cmd) {
156 std::stringstream ss;
157 ss<<"!"<<cmd;
158 send_one_message(server_host, port, ss.str());
159 return 0;
160}
161
162int
163DirectD::tell_server(const string& server_host, int port,
164 const string& cmd) {
165 send_one_message(server_host, port, cmd);
166 return 0;
167}
168
169bool
170DirectD::wait_for_servers(int count, int timeout_ms) {
171 if (count <= 0) {
172 return true;
173 }
174 // The timeout is a rough estimate, we may wait slightly longer.
175 const int wait_ms=200;
176 int cycles=timeout_ms/wait_ms;
177 while (cycles--) {
178 check_for_new_clients();
179 check_for_lost_connection();
180 /*
181 The following can be more generalized with a little work.
182 check_for_datagrams() could take a handler function as an arugment, maybe.
183 */
184 // check_for_datagrams(); Process all available datagrams.
185 while (_reader.data_available()) {
186 NetDatagram datagram;
187 if (_reader.get_data(datagram)) {
188 cout << count << ": Server at " << datagram.get_address()
189 << " is ready." << endl;
190 datagram.dump_hex(nout);
191 // handle_datagram(datagram);
192 DatagramIterator di(datagram);
193 string s=di.get_string();
194 if (s=="r" && !--count) {
195 return true;
196 }
197 }
198 }
199
200 // Yield the timeslice before we poll again.
201 // PR_Sleep(PR_MillisecondsToInterval(wait_ms));
202 Sleep(wait_ms);
203 }
204 // We've waited long enough, assume they're not going to be ready in the
205 // time we want them:
206 return false;
207}
208
209int
210DirectD::server_ready(const string& client_host, int port) {
211 send_one_message(client_host, port, "r");
212 return 0;
213}
214
215
216void
217DirectD::start_app(const string& cmd) {
218 nout<<"start_app(cmd="<<cmd<<")"<<endl;
219 if (_useOldStuff) {
220 _pids.push_back(StartApp(cmd));
221 nout<<" pid="<<_pids.back()<<endl;
222 } else {
223 if (!_jobObject) {
224 _jobObject=CreateJobObject(0, 0);
225 if (!_jobObject) {
226 nout<<"CreateProcess failed: no _jobObject: "<<GetLastError()<<endl;
227 return;
228 }
229 }
230 DWORD pid=0;
231 STARTUPINFO si;
232 PROCESS_INFORMATION pi;
233 ZeroMemory(&si, sizeof(STARTUPINFO));
234 si.cb = sizeof(STARTUPINFO);
235 ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
236 if (CreateProcess(nullptr, (char*)cmd.c_str(),
237 0, 0, 1, NORMAL_PRIORITY_CLASS | CREATE_SUSPENDED,
238 0, 0, &si, &pi)) {
239 // The process must be created with CREATE_SUSPENDED to give us a chance
240 // to get the handle into our sgJobObject before the child processes
241 // starts sub-processes.
242 if (!AssignProcessToJobObject(_jobObject, pi.hProcess)) {
243 // ...The assign failed.
244 cerr<<"StartJob AssignProcessToJobObject Error: "<<GetLastError()<<endl;
245 }
246 CloseHandle(pi.hProcess); //?????
247 // Because we called CreateProcess with the CREATE_SUSPEND flag, we must
248 // explicitly resume the processes main thread.
249 if (ResumeThread(pi.hThread) == -1) {
250 cerr<<"StartJob ResumeThread Error: "<<GetLastError()<<endl;
251 }
252 CloseHandle(pi.hThread);
253 } else {
254 nout<<"StartJob CreateProcess failed: "<<cmd<<endl;
255 }
256 }
257}
258
259void
260DirectD::kill_app(int index) {
261 if (_useOldStuff) {
262 int i = _pids.size() - 1 - index % _pids.size();
263 PidStack::iterator pi = _pids.begin() + i;
264 if (pi!=_pids.end()) {
265 nout<<"trying kill "<<(*pi)<<endl;
266 TerminateApp((*pi), 1000);
267 _pids.erase(pi);
268 }
269 } else {
270 cerr<<"kill_app(index) not implemented, calling kill_all() instead."<<endl;
271 kill_all();
272 }
273}
274
275void
276DirectD::kill_all() {
277 if (_useOldStuff) {
278 PidStack::reverse_iterator pi;
279 for (pi = _pids.rbegin(); pi != _pids.rend(); ++pi) {
280 nout<<"trying kill "<<(*pi)<<endl;
281 TerminateApp((*pi), 1000);
282 }
283 _pids.clear();
284 } else {
285 if (!_jobObject) {
286 cerr<<"kill_all(): No open _jobObject"<<endl;
287 } else if (!TerminateJobObject(_jobObject, 0)) {
288 cerr<<"kill_all() TerminateJobObject Error: "<<GetLastError()<<endl;
289 }
290 CloseHandle(_jobObject);
291 _jobObject=0;
292 }
293}
294
295void
296DirectD::send_command(const string& cmd) {
297 NetDatagram datagram;
298 datagram.add_string(cmd);
299 // Send the datagram.
300 ConnectionSet::iterator ci;
301 for (ci = _connections.begin(); ci != _connections.end(); ++ci) {
302 _writer.send(datagram, (*ci));
303 }
304}
305
306void
307DirectD::handle_datagram(NetDatagram& datagram){
308 DatagramIterator di(datagram);
309 string cmd=di.get_string();
310 handle_command(cmd);
311}
312
313void
314DirectD::handle_command(const string& cmd) {
315 nout<<"DirectD::handle_command: "<<cmd<<endl;
316}
317
318void
319DirectD::send_one_message(const string& host_name,
320 int port,
321 const string& message) {
322 NetAddress host;
323 if (!host.set_host(host_name, port)) {
324 nout << "Unknown host: " << host_name << "\n";
325 }
326
327 const int timeout_ms=5000;
328 PT(Connection) c = _cm.open_TCP_client_connection(host, timeout_ms);
329 if (c.is_null()) {
330 nout << "No connection.\n";
331 return;
332 }
333
334 nout << "Successfully opened TCP connection to " << host_name
335 << " on port "
336 << c->get_address().get_port() << " and IP "
337 << c->get_address() << "\n";
338
339 // _reader.add_connection(c);
340
341 NetDatagram datagram;
342 datagram.add_string(message);
343 _writer.send(datagram, c);
344
345 // PR_Sleep(PR_MillisecondsToInterval(200)); wait_for_servers(1, 10*1000);
346 // _reader.remove_connection(c);
347 _cm.close_connection(c);
348}
349
350int
351DirectD::connect_to(const string& host_name, int port) {
352 NetAddress host;
353 if (!host.set_host(host_name, port)) {
354 nout << "Unknown host: " << host_name << "\n";
355 }
356
357 const int timeout_ms=5000;
358 PT(Connection) c = _cm.open_TCP_client_connection(host, timeout_ms);
359 if (c.is_null()) {
360 nout << "No connection.\n";
361 return 0;
362 }
363
364 nout << "Successfully opened TCP connection to " << host_name
365 << " on port "
366 << c->get_address().get_port() << " and IP "
367 << c->get_address() << "\n";
368
369 _reader.add_connection(c);
370 _connections.insert(c);
371 return c->get_address().get_port();
372}
373
374void
375DirectD::disconnect_from(const string& host_name, int port) {
376 nout<<"disconnect_from(\""<<host_name<<", port="<<port<<")"<<endl;
377 for (ConnectionSet::iterator i=_connections.begin(); i != _connections.end(); ++i) {
378 nout<<" found "<<(*i)->get_address().get_ip_string()<<", port "<<(*i)->get_address().get_port()<<endl;
379 if ((*i)->get_address().get_ip_string()==host_name) {
380 nout<<" disconnecting."<<endl;
381 _reader.remove_connection((*i));
382 _cm.close_connection((*i));
383 _connections.erase(i);
384 break;
385 }
386 }
387}
388
389void
390DirectD::check_for_lost_connection() {
391 while (_cm.reset_connection_available()) {
392 PT(Connection) c;
393 if (_cm.get_reset_connection(c)) {
394 nout<<"Lost connection from "<<c->get_address()<<endl;
395 _connections.erase(c);
396 _cm.close_connection(c);
397 }
398 }
399}
400
401void
402DirectD::check_for_datagrams(){
403 // Process all available datagrams.
404 while (_reader.data_available()) {
405 NetDatagram datagram;
406 if (_reader.get_data(datagram)) {
407 nout << "Got datagram " /*<< datagram <<*/ "from "
408 << datagram.get_address() << endl;
409 datagram.dump_hex(nout);
410 handle_datagram(datagram);
411 }
412 }
413}
414
415void
416DirectD::listen_to(int port, int backlog) {
417 PT(Connection) rendezvous = _cm.open_TCP_server_rendezvous(port, backlog);
418 if (rendezvous.is_null()) {
419 nout << "Cannot grab port " << port << ".\n";
420 exit(1);
421 }
422 nout << "Listening for connections on port " << port << "\n";
423 _listener.add_connection(rendezvous);
424}
425
426void
427DirectD::check_for_new_clients() {
428 while (_listener.new_connection_available()) {
429 PT(Connection) rv;
430 NetAddress address;
431 PT(Connection) new_connection;
432 if (_listener.get_new_connection(rv, address, new_connection)) {
433 nout << "Got connection from " << address << "\n";
434 _reader.add_connection(new_connection);
435 _connections.insert(new_connection);
436 }
437 }
438}
PT(Connection) open_TCP_client_connection(const std bool close_connection(const PT(Connection) &connection)
Terminates a UDP or TCP socket previously opened.
bool remove_connection(Connection *connection)
Removes a socket from the list of sockets being monitored.
bool add_connection(Connection *connection)
Adds a new socket to the list of sockets the ConnectionReader will monitor.
bool send(const Datagram &datagram, const PT(Connection) &connection, bool block=false)
Enqueues a datagram for transmittal on the indicated socket.
Represents a single TCP or UDP socket for input or output.
Definition connection.h:29
A class to retrieve the individual data elements previously stored in a Datagram.
std::string get_string()
Extracts a variable-length string.
void dump_hex(std::ostream &out, unsigned int indent=0) const
Writes a representation of the entire datagram contents, as a sequence of hex (and ASCII) values.
Definition datagram.cxx:44
void add_string(const std::string &str)
Adds a variable-length string to the datagram.
Definition datagram.I:219
void send_command(const std::string &cmd)
Send the same command string to all current connections.
Definition directd.cxx:296
int tell_server(const std::string &server_host, int port, const std::string &cmd)
Tell the server to do the command cmd.
Definition directd.cxx:163
int server_ready(const std::string &client_host, int port)
Call this function from the server when import ShowbaseGlobal is nearly finished.
Definition directd.cxx:210
int client_ready(const std::string &server_host, int port, const std::string &cmd)
Call this function from the client when import ShowbaseGlobal is nearly finished.
Definition directd.cxx:154
void disconnect_from(const std::string &server_host, int port)
This is the counterpart to connect_to().
Definition directd.cxx:375
void listen_to(int port, int backlog=8)
Call listen_to in the server.
Definition directd.cxx:416
bool wait_for_servers(int count, int timeout_ms=2 *60 *1000)
Call this function from the client after calling <count> client_ready() calls.
Definition directd.cxx:170
int connect_to(const std::string &server_host, int port)
Call connect_to from client for each server.
Definition directd.cxx:351
Represents a network address to which UDP packets may be sent or to which a TCP socket may be bound.
Definition netAddress.h:25
bool set_host(const std::string &hostname, int port)
Sets the address up to refer to a particular port on a particular host.
A specific kind of Datagram, especially for sending across or receiving from a network.
Definition netDatagram.h:40
const NetAddress & get_address() const
Retrieves the host from which the datagram was read, or to which it is scheduled to be sent.
bool get_new_connection(PT(Connection) &rendezvous, NetAddress &address, PT(Connection) &new_connection)
If a previous call to new_connection_available() returned true, this function will return information...
bool new_connection_available()
Returns true if a new connection was recently established; the connection information may then be ret...
bool reset_connection_available() const
Returns true if one of the readers/writers/listeners reported a connection reset recently.
bool get_reset_connection(PT(Connection) &connection)
If a previous call to reset_connection_available() returned true, this function will return informati...
bool get_data(NetDatagram &result)
If a previous call to data_available() returned true, this function will return the datagram that has...
bool data_available()
Returns true if a datagram is available on the queue; call get_data() to extract the datagram.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.