1 /* |
|
2 * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. |
|
8 * |
|
9 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
12 * version 2 for more details (a copy is included in the LICENSE file that |
|
13 * accompanied this code). |
|
14 * |
|
15 * You should have received a copy of the GNU General Public License version |
|
16 * 2 along with this work; if not, write to the Free Software Foundation, |
|
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
18 * |
|
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
20 * or visit www.oracle.com if you need additional information or have any |
|
21 * questions. |
|
22 */ |
|
23 |
|
24 |
|
25 /* |
|
26 * @test |
|
27 * @key stress |
|
28 * |
|
29 * @summary converted from VM testbase nsk/stress/network/network004. |
|
30 * VM testbase keywords: [stress, slow, nonconcurrent, quick] |
|
31 * VM testbase readme: |
|
32 * DESCRIPTION |
|
33 * This test transfers huge amount of data between 2 Java virtual machines |
|
34 * using the TCP/IP protocol, and checks if those data are transfered correctly. |
|
35 * Both client and server VMs run on the same local computer and attach TCP/IP |
|
36 * sockets to the local host, or to the loopback domain ``localhost'' |
|
37 * (having IP address 127.0.0.1). |
|
38 * In this test, 128 client/server connections are established. Once a |
|
39 * connection is established, client passes a large data parcel to server, |
|
40 * and server reads that parcel and checks if it is same as expected |
|
41 * (byte-to-byte equality is desired). Then server passes (some other) parcel |
|
42 * to the client, and client reads and verifies those bytes. This ping-pong |
|
43 * game is repeated 128 times; and after that each pair of sockets checks if |
|
44 * there are no extra bytes accudentally passed through their connection. |
|
45 * Parcels lengths and contents are chosen randomly, and average |
|
46 * parcel length is 128 bytes. So totally, each pair of sockets passes ~16Kb of |
|
47 * data to each other, and thus ~32Kb of data are transfered by each sockets |
|
48 * pair. Totally, ~4Mb of data are transfered by all client/server pairs. |
|
49 * COMMENTS |
|
50 * The production Solaris_JDK_1.3-b12 Server VM intermittently crashes under |
|
51 * this test, even when client part of the test is executed with Client HS: |
|
52 * >>>> java -server network004 java |
|
53 * # |
|
54 * # HotSpot Virtual Machine Error, Unexpected Signal 10 |
|
55 * # Please report this error at |
|
56 * # http://java.sun.com/cgi-bin/bugreport.cgi |
|
57 * # |
|
58 * # Error ID: 4F533F534F4C415249530E43505007D9 01 |
|
59 * # |
|
60 * # Problematic Thread: prio=5 tid=0x214418 nid=0x103 runnable |
|
61 * # |
|
62 * (ErrorID == "os_solaris.cpp, 2009") |
|
63 * If the client part of the test is executed with Server HS, the |
|
64 * production Solaris_JDK_1.3-b12 Server VM intermittently fails |
|
65 * this test due to timeout: |
|
66 * >>>> time java -server network004 'java -server -showversion' |
|
67 * java version "1.3" |
|
68 * Java(TM) 2 Runtime Environment, Standard Edition (build Solaris_JDK_1.3-b12) |
|
69 * Java HotSpot(TM) Server VM (build 1.3-b12, mixed mode) |
|
70 * # Client #96: java.io.InterruptedIOException: Read timed out |
|
71 * # Client VM has crashed: exit status=97 |
|
72 * # Test failed. |
|
73 * 156.0u 117.0s 7:06 63% 0+0k 0+0io 0pf+0w |
|
74 * Test was fixed: |
|
75 * added WAITTIME parameter defined timeout for TCP/IP sockets in minutes |
|
76 * |
|
77 * @library /vmTestbase |
|
78 * /test/lib |
|
79 * @run driver jdk.test.lib.FileInstaller . . |
|
80 * @build nsk.stress.network.network004 |
|
81 * @run main/othervm PropertyResolvingWrapper |
|
82 * nsk.stress.network.network004 |
|
83 * "${test.jdk}/bin/java ${test.vm.opts} ${test.java.opts}" 5 |
|
84 */ |
|
85 |
|
86 package nsk.stress.network; |
|
87 |
|
88 import java.io.BufferedReader; |
|
89 import java.io.IOException; |
|
90 import java.io.InputStream; |
|
91 import java.io.InputStreamReader; |
|
92 import java.io.OutputStream; |
|
93 import java.io.PrintStream; |
|
94 import java.net.InetAddress; |
|
95 import java.net.ServerSocket; |
|
96 import java.net.Socket; |
|
97 import java.net.UnknownHostException; |
|
98 import java.util.Random; |
|
99 import java.util.StringTokenizer; |
|
100 |
|
101 /** |
|
102 * This test transfers huge amount of data between 2 Java virtual machines |
|
103 * using the TCP/IP protocol, and checks if those data are transfered correctly. |
|
104 * Both client and server VMs run on the same local computer and attach TCP/IP |
|
105 * sockets to the local host, or to the loopback domain ``<code>localhost</code>'' |
|
106 * (having IP address <code>127.0.0.1</code>). |
|
107 * <p> |
|
108 * <p>In this test, 128 client/server connections are established. Once a |
|
109 * connection is established, client passes a large data parcel to server, |
|
110 * and server reads that parcel and checks if it is same as expected |
|
111 * (byte-to-byte equality is desired). Then server passes (some other) parcel |
|
112 * to the client, and client reads and verifies those bytes. This ping-pong |
|
113 * game is repeated 128 times; and after that each pair of sockets checks if |
|
114 * there are no extra bytes accudentally passed through their connection. |
|
115 * <p> |
|
116 * <p>Parcels lengths and contents are chosen randomly, and average |
|
117 * parcel length is 128 bytes. So totally, each pair of sockets passes ~16Kb of |
|
118 * data to each other, and thus ~32Kb of data are transfered by each sockets |
|
119 * pair. Totally, ~4Mb of data are transfered by all client/server pairs. |
|
120 */ |
|
121 public class network004 { |
|
122 /** |
|
123 * Timeout for TCP/IP sockets (currently set to 1 min). |
|
124 */ |
|
125 private static int SO_TIMEOUT;// = 2*60*1000; |
|
126 |
|
127 /** |
|
128 * Maximal number of connections this test should open simultaneously. |
|
129 */ |
|
130 private final static int MAX_CONNECTIONS = 128; |
|
131 |
|
132 /** |
|
133 * Check few more connections to make sure that MAX_CONNECTIONS are safe. |
|
134 */ |
|
135 private final static int CONNECTIONS_RESERVE = 10; |
|
136 |
|
137 /** |
|
138 * Number of parcels to be sent/recieved. |
|
139 */ |
|
140 private final static int DATA_PARCELS = 128; |
|
141 |
|
142 /** |
|
143 * Maximal length of data parcel to be sent/recieved |
|
144 * (it equals to 256 bytes now). |
|
145 */ |
|
146 private final static int MAX_PARCEL = 1 << 8; |
|
147 |
|
148 /** |
|
149 * Either actually display optional reports or not. |
|
150 */ |
|
151 static private final boolean DEBUG_MODE = false; |
|
152 |
|
153 /** |
|
154 * How many IP sockets can we open simultaneously? |
|
155 * Check if <code>MAX_CONNECTIONS</code> connections |
|
156 * can be open simultaneously. |
|
157 */ |
|
158 private static int detectOSLimitation() { |
|
159 final int CONNECTIONS_TO_TRY = MAX_CONNECTIONS + CONNECTIONS_RESERVE; |
|
160 ServerSocket ssoc[] = new ServerSocket[CONNECTIONS_TO_TRY]; |
|
161 display("--- Trying to open " + CONNECTIONS_TO_TRY + " connections:"); |
|
162 int i; |
|
163 for (i = 0; i < CONNECTIONS_TO_TRY; i++) |
|
164 try { |
|
165 ssoc[i] = new ServerSocket(0); |
|
166 display("--- Open: ssoc[" + i + "] = " + ssoc[i]); |
|
167 } catch (IOException ioe) { |
|
168 display("--- OOPS! -- failed to open connection #" + i); |
|
169 break; |
|
170 } |
|
171 display("--- Could open " + |
|
172 (i < CONNECTIONS_TO_TRY ? "only " : "") + i + " connections."); |
|
173 display("--- Closing them:"); |
|
174 for (int j = 0; j < i; j++) |
|
175 try { |
|
176 ssoc[j].close(); |
|
177 } catch (IOException ioe) { |
|
178 throw new Error("FATAL error while loading the test: " + ioe); |
|
179 } |
|
180 display("--- OK."); |
|
181 int safeConnections = i - CONNECTIONS_RESERVE; |
|
182 if (safeConnections < 1) |
|
183 safeConnections = 1; |
|
184 if (safeConnections < MAX_CONNECTIONS) { |
|
185 complain("------------------------- CAUTION: -------------------"); |
|
186 complain("While checking the OS limitations, the test found that"); |
|
187 complain("only " + i + " TCP/IP socket connections could be safely open"); |
|
188 complain("simultaneously. However, possibility to open at least"); |
|
189 complain("" + MAX_CONNECTIONS + "+" + CONNECTIONS_RESERVE |
|
190 + " connections were expected."); |
|
191 complain(""); |
|
192 complain("So, the test will check only " + safeConnections + " connection" |
|
193 + (safeConnections == 1 ? "" : "s") + " which seem"); |
|
194 complain("safe to be open simultaneously."); |
|
195 complain("------------------------------------------------------"); |
|
196 } |
|
197 return safeConnections; |
|
198 } |
|
199 |
|
200 //----------------------------------------------------------------// |
|
201 |
|
202 /** |
|
203 * Re-calls to the method <code>run(args[],out)</code> actually |
|
204 * performing the test. After <code>run(args[],out)</code> stops, |
|
205 * follow JDK-like convention for exit codes. I.e.: stop with |
|
206 * exit status 95 if the test has passed, or with status 97 if |
|
207 * the test has failed. |
|
208 * |
|
209 * @see #run(String[], PrintStream) |
|
210 */ |
|
211 public static void main(String args[]) { |
|
212 int exitCode = run(args, System.out); |
|
213 System.exit(exitCode + 95); |
|
214 // JCK-like exit status. |
|
215 } |
|
216 |
|
217 /** |
|
218 * Parse command-line parameters stored into <code>args[]</code> array, |
|
219 * then perform the test. I.e.: start the server thread at the same VM |
|
220 * this method runs, then start the other client VM, and verify data |
|
221 * transfer through TCP/IP connection between those different virtual |
|
222 * machines. |
|
223 * <p> |
|
224 * <p>There should be 1 or 2 command-line parameters: |
|
225 * <br> |
|
226 * <code>java network004 <i>java_command</i> |
|
227 * [<i>IP-address</i> | <i>host_name</i> | localhost ]</code> |
|
228 * <br>where parameters are: |
|
229 * <br> |
|
230 * <code><i>java_command</i></code> - how to start java, |
|
231 * e.g.: ``<code>c:\jdk1.3\bin\java -classic</code>'' |
|
232 * <br> |
|
233 * <code>waittime</code> - timeout for TCP/IP sockets in minutes |
|
234 * <br> |
|
235 * <code><i>IP-address</i></code> - local hots's address, or 127.0.0.1 |
|
236 * <br> |
|
237 * <code><i>host_name</i></code> - local host's domain name, or the |
|
238 * keyword ``<code>localhost</code>'' |
|
239 * <br> |
|
240 * <code>localhost</code> - placeholder for the IP-address 127.0.0.1 |
|
241 * <p> |
|
242 * <p>Usually, <code><i>java_command</i></code> should point to the same |
|
243 * Java machine just executing this test. However, every compatible Java 2 |
|
244 * implementation is appropriate. |
|
245 * <p> |
|
246 * <p>If optional parameter is ommited, the test invokes the method |
|
247 * <code>InetAddress.getLocalHost()</code> to get the domain name and |
|
248 * IP-address of the local computer. |
|
249 */ |
|
250 public static int run(String args[], PrintStream out) { |
|
251 network004.out = out; |
|
252 |
|
253 // |
|
254 // Get the Internet address of the local machine. |
|
255 // |
|
256 InetAddress address = null; |
|
257 try { |
|
258 switch (args.length) { |
|
259 case 2: |
|
260 address = InetAddress.getLocalHost(); |
|
261 break; |
|
262 case 3: |
|
263 address = InetAddress.getByName(args[2]); |
|
264 break; |
|
265 default: |
|
266 complain("Illegal arguments number; execute:"); |
|
267 complain(" java network004 $JAVA_COMMAND " + |
|
268 "[$IP_ADDRESS | $HOST_NAME | localhost]"); |
|
269 return 2; // FAILED |
|
270 } |
|
271 } catch (UnknownHostException exception) { |
|
272 complain(exception.toString()); |
|
273 return 2; // FAILED |
|
274 } |
|
275 display("Host: " + address); |
|
276 |
|
277 // |
|
278 // Detect if it is safe to open MAX_CONNETIONS simultaneously: |
|
279 // |
|
280 final int CONNECTIONS = detectOSLimitation(); |
|
281 |
|
282 // |
|
283 // Start the server thread on the same VM (which executes this method). |
|
284 // |
|
285 Server server[] = new Server[CONNECTIONS]; |
|
286 for (int i = 0; i < CONNECTIONS; i++) { |
|
287 try { |
|
288 server[i] = new Server(address); |
|
289 } catch (Exception exception) { |
|
290 complain("Server #" + i + ": " + exception); |
|
291 return 2; |
|
292 } |
|
293 display("Server #" + i + ": " + server[i]); |
|
294 server[i].start(); |
|
295 } |
|
296 |
|
297 // |
|
298 // Start the client process on different VM. |
|
299 // |
|
300 String command = args[0] + " " + network004.class.getName() + "$Client"; |
|
301 try { |
|
302 SO_TIMEOUT = Integer.parseInt(args[1]) * 60 * 1000; |
|
303 } catch (NumberFormatException e) { |
|
304 complain("Wrong timeout argument: " + e); |
|
305 return 2; |
|
306 } |
|
307 |
|
308 Runtime runtime = Runtime.getRuntime(); |
|
309 |
|
310 Process client = null; |
|
311 IORedirector redirectOut = null; |
|
312 IORedirector redirectErr = null; |
|
313 |
|
314 try { |
|
315 // Start clients on different JVM: |
|
316 client = runtime.exec(command); |
|
317 |
|
318 // Provide clients with access to stderr and stdout: |
|
319 InputStream clientOut = client.getInputStream(); |
|
320 InputStream clientErr = client.getErrorStream(); |
|
321 redirectOut = new IORedirector(clientOut, DEBUG_MODE ? out : null); |
|
322 redirectErr = new IORedirector(clientErr, out); |
|
323 redirectOut.start(); |
|
324 redirectErr.start(); |
|
325 |
|
326 // Pass parameters to clients (number of connections, and IP adresses and ports): |
|
327 PrintStream clientIn = new PrintStream(client.getOutputStream()); |
|
328 clientIn.println(CONNECTIONS); |
|
329 for (int i = 0; i < CONNECTIONS; i++) |
|
330 clientIn.println(server[i].getIPAddress() + " " + server[i].getPort()); |
|
331 clientIn.flush(); |
|
332 clientIn.close(); |
|
333 |
|
334 } catch (Exception exception) { |
|
335 complain("Failed to start client: " + exception); |
|
336 return 2; |
|
337 } |
|
338 |
|
339 // |
|
340 // Wait until the server and client both stop. |
|
341 // |
|
342 boolean testFailed = false; |
|
343 try { |
|
344 client.waitFor(); |
|
345 // Let I/O redirectors to flush: |
|
346 if (redirectOut.isAlive()) |
|
347 redirectOut.join(); |
|
348 if (redirectErr.isAlive()) |
|
349 redirectErr.join(); |
|
350 |
|
351 // If client has crashed, also terminate the server (to avoid hangup). |
|
352 int clientStatus = client.exitValue(); |
|
353 if (clientStatus != 95) { |
|
354 complain("Client VM has failed: exit status=" + clientStatus); |
|
355 testFailed = true; |
|
356 } |
|
357 |
|
358 // Client has finished OK; wait for the server. |
|
359 for (int i = 0; i < CONNECTIONS; i++) { |
|
360 display("Server: waiting for #" + i); |
|
361 while (server[i].isAlive()) |
|
362 server[i].join(); |
|
363 if (server[i].exception != null) { |
|
364 complain("Server thread #" + i + ": " + server[i].exception); |
|
365 testFailed = true; |
|
366 } |
|
367 } |
|
368 |
|
369 } catch (Exception exception) { |
|
370 complain("Test interrupted: " + exception); |
|
371 testFailed = true; |
|
372 } |
|
373 |
|
374 if (testFailed) |
|
375 complain("Test failed."); |
|
376 else |
|
377 display("Test passed."); |
|
378 return testFailed ? 2 : 0; |
|
379 } |
|
380 |
|
381 //----------------------------------------------------------------// |
|
382 |
|
383 /** |
|
384 * Log stream for error messages and/or (optional) execution trace. |
|
385 */ |
|
386 private static PrintStream out; |
|
387 |
|
388 /** |
|
389 * Print error message. |
|
390 */ |
|
391 private static synchronized void complain(Object message) { |
|
392 out.println("# " + message); |
|
393 out.flush(); |
|
394 } |
|
395 |
|
396 /** |
|
397 * Display optional report: comment ca va? |
|
398 */ |
|
399 private static synchronized void display(Object report) { |
|
400 if (DEBUG_MODE) |
|
401 out.println(report.toString()); |
|
402 out.flush(); |
|
403 } |
|
404 |
|
405 //----------------------------------------------------------------// |
|
406 |
|
407 /** |
|
408 * Server thread should reply to data parcels sent by Client VM. |
|
409 */ |
|
410 private static class Server extends Thread { |
|
411 /** |
|
412 * The socket to listen for a client. |
|
413 */ |
|
414 private ServerSocket serverSocket; |
|
415 |
|
416 /** |
|
417 * Display the server socket. |
|
418 */ |
|
419 public String toString() { |
|
420 return serverSocket.toString(); |
|
421 } |
|
422 |
|
423 /** |
|
424 * Server's IP-address in the form ``<code><i>x.y.u.z</i></code>'', |
|
425 * or ``<code>127.0.0.1</code>'' for loopback connection. |
|
426 */ |
|
427 public String getIPAddress() { |
|
428 return serverSocket.getInetAddress().getHostAddress(); |
|
429 } |
|
430 |
|
431 /** |
|
432 * Which port is this socket listening? |
|
433 */ |
|
434 int getPort() { |
|
435 return serverSocket.getLocalPort(); |
|
436 } |
|
437 |
|
438 /** |
|
439 * Find some free port at the given <code>address</code> |
|
440 * and attach new server to hear that port. |
|
441 */ |
|
442 public Server(InetAddress address) throws IOException { |
|
443 int someFreePort = 0; |
|
444 int backlog = 50; // default for new ServerSocket(port) |
|
445 serverSocket = new ServerSocket(someFreePort, backlog, address); |
|
446 } |
|
447 |
|
448 /** |
|
449 * Exception just arisen while the server was working, |
|
450 * or <code>null</code> if it was OK with the server. |
|
451 */ |
|
452 Exception exception = null; |
|
453 |
|
454 /** |
|
455 * Accept connection, then reply to client's parcels. |
|
456 */ |
|
457 public void run() { |
|
458 try { |
|
459 Socket socket = serverSocket.accept(); |
|
460 socket.setSoTimeout(SO_TIMEOUT); |
|
461 // display("Server: " + socket); |
|
462 |
|
463 InputStream istream = socket.getInputStream(); |
|
464 OutputStream ostream = socket.getOutputStream(); |
|
465 |
|
466 Random random = new Random(getPort()); |
|
467 |
|
468 for (int i = 0; i < DATA_PARCELS; i++) { |
|
469 Parcel etalon = new Parcel(random); |
|
470 |
|
471 Parcel sample = new Parcel(istream); // read |
|
472 if (!sample.equals(etalon)) { |
|
473 complain("Server thread for port #" |
|
474 + getPort() + " got unexpected parcel:\n" |
|
475 + "sample=" + sample + "\n" |
|
476 + "etalon=" + etalon); |
|
477 throw new TestFailure( |
|
478 "server has read unexpected parcel"); |
|
479 } |
|
480 |
|
481 etalon.send(ostream); |
|
482 ostream.flush(); |
|
483 } |
|
484 |
|
485 int datum = istream.read(); // wait for client close() |
|
486 if (datum >= 0) |
|
487 throw new TestFailure( |
|
488 "server has read ambigous byte: " + datum); |
|
489 |
|
490 ostream.close(); // implies: socket.close(); |
|
491 |
|
492 } catch (Exception oops) { |
|
493 exception = oops; |
|
494 } |
|
495 } |
|
496 |
|
497 } |
|
498 |
|
499 //----------------------------------------------------------------// |
|
500 |
|
501 /** |
|
502 * Client VM should send data parcels to Server VM and |
|
503 * recieve and verify the server's replies. |
|
504 */ |
|
505 private static class Client extends Thread { |
|
506 /** |
|
507 * This thread uses the single client socket. |
|
508 */ |
|
509 private Socket socket; |
|
510 |
|
511 /** |
|
512 * Address and port of this socket. |
|
513 */ |
|
514 public String toString() { |
|
515 return socket.toString(); |
|
516 } |
|
517 |
|
518 /** |
|
519 * Did the thread failed? If yes, what is the failure's reason. |
|
520 */ |
|
521 Exception exception = null; |
|
522 |
|
523 /** |
|
524 * Connect client socket on the given <code>address</code> |
|
525 * and <code>port</code>. |
|
526 */ |
|
527 Client(InetAddress address, int port) throws IOException { |
|
528 socket = new Socket(address, port); |
|
529 socket.setSoTimeout(SO_TIMEOUT); |
|
530 } |
|
531 |
|
532 /** |
|
533 * What is the port number this socket is listening for? |
|
534 */ |
|
535 int getPort() { |
|
536 return socket.getPort(); |
|
537 } |
|
538 |
|
539 /** |
|
540 * Establish connection, then read/respond <code>DATA_PARCELS</code> parcels |
|
541 * of random data. Set initial seed for pseudo-random numbers generator |
|
542 * to the value of the local port number. |
|
543 * |
|
544 * @see #DATA_PARCELS |
|
545 * @see #getPort() |
|
546 */ |
|
547 public void run() { |
|
548 try { |
|
549 InputStream istream = socket.getInputStream(); |
|
550 OutputStream ostream = socket.getOutputStream(); |
|
551 |
|
552 Random random = new Random(getPort()); |
|
553 |
|
554 for (int i = 0; i < DATA_PARCELS; i++) { |
|
555 Parcel etalon = new Parcel(random); |
|
556 etalon.send(ostream); |
|
557 ostream.flush(); |
|
558 |
|
559 Parcel sample = new Parcel(istream); // read |
|
560 if (!sample.equals(etalon)) { |
|
561 complain("Client thread for port #" |
|
562 + getPort() + " got unexpected parcel:\n" |
|
563 + "sample=" + sample + "\n" |
|
564 + "etalon=" + etalon); |
|
565 throw new TestFailure( |
|
566 "parcel context is unexpected to client"); |
|
567 } |
|
568 } |
|
569 |
|
570 if (istream.available() > 0) { |
|
571 int datum = istream.read(); |
|
572 throw new TestFailure( |
|
573 "client has read ambigous byte: " + datum); |
|
574 } |
|
575 ostream.close(); // implies: socket.close() |
|
576 |
|
577 } catch (Exception oops) { |
|
578 exception = oops; |
|
579 } |
|
580 } |
|
581 |
|
582 /** |
|
583 * Establish connections to lots of server sockets, atack servers with |
|
584 * huge data parcels, and check if it replies correctly. The addresses |
|
585 * and port numbers for server sockets are passed through <code>stdin</code>. |
|
586 * The input stream must consist of the stipulated number (up to 128+1) of |
|
587 * lines containing the pair of symbolic server domain name and the port number, |
|
588 * like: |
|
589 * <br> actual_number_of_sockets |
|
590 * <br> address_1 port_1 |
|
591 * <br> address_2 port_2 |
|
592 * <br> . . . |
|
593 * <br> address_N port_N |
|
594 * <br>where N must equal to the actual_number_of_sockets. |
|
595 */ |
|
596 public static void main(String args[]) { |
|
597 // ---- Parse stdin for the list of server sockets: ---- // |
|
598 BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); |
|
599 |
|
600 final int CONNECTIONS; |
|
601 try { |
|
602 String line = in.readLine(); |
|
603 if (line == null) { |
|
604 complain("Client expects paramenets passed through stdin:"); |
|
605 complain(" actual_number_of_sockets"); |
|
606 complain(" IP-address_1 port_1"); |
|
607 complain(" IP-address_2 port_2"); |
|
608 complain(" . . ."); |
|
609 complain(" IP-address_N port_N"); |
|
610 exit(2); // FAILED |
|
611 } |
|
612 CONNECTIONS = Integer.parseInt(line); |
|
613 } catch (IOException ioe) { |
|
614 complain("Client failed to read the actual number of CONNECTIONS"); |
|
615 throw new RuntimeException(ioe.toString()); |
|
616 } |
|
617 |
|
618 Client client[] = new Client[CONNECTIONS]; |
|
619 for (int i = 0; i < CONNECTIONS; i++) |
|
620 try { |
|
621 String line = in.readLine(); |
|
622 if (line == null) { |
|
623 complain("Client: failed to read address/port for client #" + i); |
|
624 exit(3); |
|
625 } |
|
626 |
|
627 StringTokenizer tokenz = new StringTokenizer(line); |
|
628 if (tokenz.countTokens() != 2) { |
|
629 complain("Client: illegal input string: " + line); |
|
630 exit(3); |
|
631 } |
|
632 String serverName = (String) tokenz.nextElement(); |
|
633 InetAddress address = InetAddress.getByName(serverName); |
|
634 int port = Integer.parseInt((String) tokenz.nextElement()); |
|
635 |
|
636 client[i] = new Client(address, port); |
|
637 |
|
638 display("Client #" + i + ": " + client[i]); |
|
639 |
|
640 } catch (IOException ioe) { |
|
641 complain("Client #" + i + ": " + ioe); |
|
642 exit(3); |
|
643 } |
|
644 |
|
645 // ---- Start testing: ---- // |
|
646 |
|
647 for (int i = 0; i < CONNECTIONS; i++) |
|
648 client[i].start(); |
|
649 |
|
650 int status = 0; |
|
651 for (int i = 0; i < CONNECTIONS; i++) { |
|
652 display("Client: waiting for #" + i); |
|
653 while (client[i].isAlive()) |
|
654 yield(); |
|
655 if (client[i].exception != null) { |
|
656 complain("Client #" + i + ": " + client[i].exception); |
|
657 status = 2; |
|
658 } |
|
659 } |
|
660 |
|
661 exit(status); |
|
662 } |
|
663 |
|
664 /** |
|
665 * Print error message. |
|
666 */ |
|
667 private static synchronized void complain(Object message) { |
|
668 System.err.println("# " + message); |
|
669 System.err.flush(); |
|
670 } |
|
671 |
|
672 /** |
|
673 * Display execution trace. |
|
674 */ |
|
675 private static synchronized void display(Object message) { |
|
676 if (!DEBUG_MODE) |
|
677 return; |
|
678 System.out.println(message.toString()); |
|
679 System.out.flush(); |
|
680 } |
|
681 |
|
682 /** |
|
683 * Exit with JCK-like status. |
|
684 */ |
|
685 private static void exit(int exitCode) { |
|
686 System.exit(exitCode + 95); |
|
687 } |
|
688 |
|
689 } |
|
690 |
|
691 /** |
|
692 * Two of such threads should redirect <code>out</code> and <code>err</code> |
|
693 * streams of client VM. |
|
694 */ |
|
695 private static class IORedirector extends Thread { |
|
696 /** |
|
697 * Source stream. |
|
698 */ |
|
699 InputStream in; |
|
700 /** |
|
701 * Destination stream. |
|
702 */ |
|
703 OutputStream out; |
|
704 |
|
705 /** |
|
706 * Redirect <code>in</code> to <code>out</code>. |
|
707 */ |
|
708 public IORedirector(InputStream in, OutputStream out) { |
|
709 this.in = in; |
|
710 this.out = out; |
|
711 } |
|
712 |
|
713 /** |
|
714 * Read input stream until the EOF, and write everithing to output stream. |
|
715 * If output stream is assigned to <code>null</code>, do not print anything, |
|
716 * but read the input stream anywhere. |
|
717 */ |
|
718 public void run() { |
|
719 try { |
|
720 for (; ; ) { |
|
721 int symbol = in.read(); |
|
722 if (symbol < 0) |
|
723 break; // EOF |
|
724 if (out != null) |
|
725 out.write(symbol); |
|
726 } |
|
727 |
|
728 if (out != null) |
|
729 out.flush(); |
|
730 |
|
731 } catch (Exception exception) { |
|
732 throw new TestFailure("IORedirector exception: " + exception); |
|
733 } |
|
734 } |
|
735 } |
|
736 |
|
737 //----------------------------------------------------------------// |
|
738 |
|
739 /** |
|
740 * A data parcel to be sent/recieved between Client VM and Server thread. |
|
741 * When data parcel is sent, first 4 bytes are transfered which encode the |
|
742 * <code>int</code> number equal to size of the parcel minus 1. I.e.: if |
|
743 * number of data bytes in the parcel's contents is <code>N</code>, then |
|
744 * the first 4 bytes encode the number <code>N-1</code>. After that, the |
|
745 * parcel's contents bytes are transered. |
|
746 */ |
|
747 static class Parcel { |
|
748 private byte[] parcel; |
|
749 |
|
750 /** |
|
751 * Display all bytes as integer values from 0 to 255; |
|
752 * or return ``<tt>null</tt>'' if this Parcel is not |
|
753 * yet initialized. |
|
754 */ |
|
755 public String toString() { |
|
756 if (parcel == null) |
|
757 return "null"; |
|
758 String s = "{"; |
|
759 for (int i = 0; i < parcel.length; i++) |
|
760 s += (i > 0 ? ", " : "") + ((int) parcel[i] & 0xFF); |
|
761 return s + "}"; |
|
762 } |
|
763 |
|
764 /** |
|
765 * Generate new <code>parcel[]</code> array using the given |
|
766 * <code>random</code> numbers generator. Client and Server |
|
767 * threads should use identical <code>random</code> generators, |
|
768 * so that those threads could generate equal data parcels and |
|
769 * check the parcel just transfered. |
|
770 */ |
|
771 public Parcel(Random random) { |
|
772 int size = random.nextInt(MAX_PARCEL) + 1; |
|
773 parcel = new byte[size]; |
|
774 for (int i = 0; i < size; i++) |
|
775 parcel[i] = (byte) random.nextInt(256); |
|
776 } |
|
777 |
|
778 /** |
|
779 * Read exactly <code>size</code> bytes from the <code>istream</code> |
|
780 * if possible, or throw <code>TestFailure</code> if unexpected end of |
|
781 * <code>istream</code> occurs. |
|
782 */ |
|
783 private static byte[] readBytes(int size, InputStream istream) |
|
784 throws IOException { |
|
785 |
|
786 byte data[] = new byte[size]; |
|
787 for (int i = 0; i < size; i++) { |
|
788 int datum = istream.read(); |
|
789 if (datum < 0) |
|
790 throw new TestFailure( |
|
791 "unexpected EOF: have read: " + i + " bytes of " + size); |
|
792 data[i] = (byte) datum; |
|
793 } |
|
794 return data; |
|
795 } |
|
796 |
|
797 /** |
|
798 * Read 4 bytes from <code>istream</code> and threat them to encode |
|
799 * size of data parcel following these 4 bytes. |
|
800 */ |
|
801 private static int getSize(InputStream istream) throws IOException { |
|
802 byte data[] = readBytes(4, istream); |
|
803 int data0 = (int) data[0] & 0xFF; |
|
804 int data1 = (int) data[1] & 0xFF; |
|
805 int data2 = (int) data[2] & 0xFF; |
|
806 int data3 = (int) data[3] & 0xFF; |
|
807 int sizeWord = data0 + (data1 << 8) + (data2 << 16) + (data3 << 24); |
|
808 int size = sizeWord + 1; |
|
809 if (size <= 0) |
|
810 throw new TestFailure("illegal size: " + size); |
|
811 return size; |
|
812 } |
|
813 |
|
814 /** |
|
815 * Send 4 bytes encoding actual size of the parcel just to be transfered. |
|
816 */ |
|
817 private static void putSize(OutputStream ostream, int size) |
|
818 throws IOException { |
|
819 |
|
820 if (size <= 0) |
|
821 throw new TestFailure("illegal size: " + size); |
|
822 |
|
823 int sizeWord = size - 1; |
|
824 byte data[] = new byte[4]; |
|
825 data[0] = (byte) sizeWord; |
|
826 data[1] = (byte) (sizeWord >> 8); |
|
827 data[2] = (byte) (sizeWord >> 16); |
|
828 data[3] = (byte) (sizeWord >> 24); |
|
829 ostream.write(data); |
|
830 } |
|
831 |
|
832 /** |
|
833 * Recieve data parcel. |
|
834 */ |
|
835 public Parcel(InputStream istream) throws IOException { |
|
836 int size = getSize(istream); |
|
837 parcel = readBytes(size, istream); |
|
838 } |
|
839 |
|
840 /** |
|
841 * Send <code>this</code> data parcel. |
|
842 */ |
|
843 public void send(OutputStream ostream) throws IOException { |
|
844 int size = parcel.length; |
|
845 putSize(ostream, size); |
|
846 ostream.write(parcel); |
|
847 } |
|
848 |
|
849 /** |
|
850 * Check byte-to-byte equality between <code>this</code> and the |
|
851 * <code>other</code> parcels. |
|
852 */ |
|
853 public boolean equals(Parcel other) { |
|
854 if (this.parcel.length != other.parcel.length) |
|
855 return false; |
|
856 int size = parcel.length; |
|
857 for (int i = 0; i < size; i++) |
|
858 if (this.parcel[i] != other.parcel[i]) |
|
859 return false; |
|
860 return true; |
|
861 } |
|
862 |
|
863 } |
|
864 |
|
865 /** |
|
866 * Server or Client may throw this exception to report the test failure. |
|
867 */ |
|
868 static class TestFailure extends RuntimeException { |
|
869 /** |
|
870 * Report particular <code>purpose</code> of the test failure. |
|
871 */ |
|
872 public TestFailure(String purpose) { |
|
873 super(purpose); |
|
874 } |
|
875 |
|
876 } |
|
877 |
|
878 } |
|