1 package be
.nikiroo
.utils
.serial
.server
;
3 import java
.io
.IOException
;
4 import java
.net
.ServerSocket
;
5 import java
.net
.Socket
;
6 import java
.net
.UnknownHostException
;
7 import java
.util
.ArrayList
;
10 import javax
.net
.ssl
.SSLServerSocket
;
11 import javax
.net
.ssl
.SSLServerSocketFactory
;
12 import javax
.net
.ssl
.SSLSocket
;
13 import javax
.net
.ssl
.SSLSocketFactory
;
15 import be
.nikiroo
.utils
.TraceHandler
;
18 * This class implements a simple server that can listen for connections and
19 * send/receive objects.
21 * Note: this {@link Server} has to be discarded after use (cannot be started
26 abstract class Server
implements Runnable
{
27 static private final String
[] ANON_CIPHERS
= getAnonCiphers();
29 private final String name
;
30 private final boolean ssl
;
31 private final Object lock
= new Object();
32 private final Object counterLock
= new Object();
34 private ServerSocket ss
;
37 private boolean started
;
38 private boolean exiting
= false;
41 private long bytesReceived
;
42 private long bytesSent
;
44 private TraceHandler tracer
= new TraceHandler();
47 * Create a new {@link ConnectActionServer} to handle a request.
50 * the socket to service
54 abstract ConnectActionServer
createConnectActionServer(Socket s
);
57 * Create a new server that will start listening on the network when
58 * {@link Server#start()} is called.
61 * the port to listen on, or 0 to assign any unallocated port
62 * found (which can later on be queried via
63 * {@link Server#getPort()}
65 * use a SSL connection (or not)
68 * in case of I/O error
69 * @throws UnknownHostException
70 * if the IP address of the host could not be determined
71 * @throws IllegalArgumentException
72 * if the port parameter is outside the specified range of valid
73 * port values, which is between 0 and 65535, inclusive
75 public Server(int port
, boolean ssl
) throws IOException
{
76 this((String
) null, port
, ssl
);
80 * Create a new server that will start listening on the network when
81 * {@link Server#start()} is called.
84 * the server name (only used for debug info and traces)
86 * the port to listen on
88 * use a SSL connection (or not)
91 * in case of I/O error
92 * @throws UnknownHostException
93 * if the IP address of the host could not be determined
94 * @throws IllegalArgumentException
95 * if the port parameter is outside the specified range of valid
96 * port values, which is between 0 and 65535, inclusive
98 public Server(String name
, int port
, boolean ssl
) throws IOException
{
102 this.ss
= createSocketServer(port
, ssl
);
104 if (this.port
== 0) {
105 this.port
= this.ss
.getLocalPort();
110 * The traces handler for this {@link Server}.
112 * @return the traces handler
114 public TraceHandler
getTraceHandler() {
119 * The traces handler for this {@link Server}.
122 * the new traces handler
124 public void setTraceHandler(TraceHandler tracer
) {
125 if (tracer
== null) {
126 tracer
= new TraceHandler(false, false, false);
129 this.tracer
= tracer
;
133 * The name of this {@link Server} if any.
135 * Used for traces and debug purposes only.
137 * @return the name or NULL
139 public String
getName() {
144 * Return the assigned port.
146 * @return the assigned port
148 public int getPort() {
153 * The total amount of bytes received.
155 * @return the amount of bytes received
157 public long getBytesReceived() {
158 return bytesReceived
;
162 * The total amount of bytes sent.
164 * @return the amount of bytes sent
166 public long getBytesSent() {
171 * Start the server (listen on the network for new connections).
173 * Can only be called once.
175 * This call is asynchronous, and will just start a new {@link Thread} on
176 * itself (see {@link Server#run()}).
178 public void start() {
179 new Thread(this).start();
183 * Start the server (listen on the network for new connections).
185 * Can only be called once.
187 * You may call it via {@link Server#start()} for an asynchronous call, too.
191 ServerSocket ss
= null;
192 boolean alreadyStarted
= false;
193 synchronized (lock
) {
195 if (!started
&& ss
!= null) {
198 alreadyStarted
= started
;
202 if (alreadyStarted
) {
203 tracer
.error(name
+ ": cannot start server on port " + port
204 + ", it is already started");
209 tracer
.error(name
+ ": cannot start server on port " + port
210 + ", it has already been used");
215 tracer
.trace(name
+ ": server starting on port " + port
+ " ("
216 + (ssl ?
"SSL" : "plain text") + ")");
219 for (String cipher
: getAnonCiphers()) {
220 if (!ciphers
.isEmpty()) {
225 tracer
.trace("Available SSL ciphers: " + ciphers
);
227 while (started
&& !exiting
) {
229 final Socket s
= ss
.accept();
230 new Thread(new Runnable() {
233 ConnectActionServer action
= null;
235 action
= createConnectActionServer(s
);
239 if (action
!= null) {
240 bytesReceived
+= action
.getBytesReceived();
241 bytesSent
+= action
.getBytesSent();
248 // Will be covered by @link{Server#stop(long)} for timeouts
249 while (counter
> 0) {
252 } catch (Exception e
) {
259 } catch (Exception e
) {
269 tracer
.trace(name
+ ": client terminated on port " + port
);
274 * Will stop the server, synchronously and without a timeout.
277 tracer
.trace(name
+ ": stopping server");
285 * the maximum timeout to wait for existing actions to complete,
286 * or 0 for "no timeout"
288 * wait for the server to be stopped before returning
289 * (synchronous) or not (asynchronous)
291 public void stop(final long timeout
, final boolean wait
) {
295 new Thread(new Runnable() {
305 * Stop the server (synchronous).
308 * the maximum timeout to wait for existing actions to complete,
309 * or 0 for "no timeout"
311 private void stop(long timeout
) {
312 tracer
.trace(name
+ ": server stopping on port " + port
);
313 synchronized (lock
) {
314 if (started
&& !exiting
) {
318 new ConnectActionClientObject(createSocket(null, port
, ssl
))
321 while (ss
!= null && timeout
> 0 && timeout
> time
) {
325 } catch (Exception e
) {
327 counter
= 0; // will stop the main thread
334 // return only when stopped
335 while (started
|| exiting
) {
338 } catch (InterruptedException e
) {
344 * Change the number of currently serviced actions.
347 * the number to increase or decrease
349 * @return the current number after this operation
351 private int count(int change
) {
352 synchronized (counterLock
) {
359 * This method will be called on errors.
361 * By default, it will only call the trace handler (so you may want to call
362 * super {@link Server#onError} if you override it).
367 protected void onError(Exception e
) {
372 * Create a {@link Socket}.
375 * the host to connect to
377 * the port to connect to
379 * TRUE for SSL mode (or FALSE for plain text mode)
381 * @return the {@link Socket}
383 * @throws IOException
384 * in case of I/O error
385 * @throws UnknownHostException
386 * if the host is not known
387 * @throws IllegalArgumentException
388 * if the port parameter is outside the specified range of valid
389 * port values, which is between 0 and 65535, inclusive
391 static Socket
createSocket(String host
, int port
, boolean ssl
)
395 s
= SSLSocketFactory
.getDefault().createSocket(host
, port
);
396 if (s
instanceof SSLSocket
) {
397 // Should always be the case
398 ((SSLSocket
) s
).setEnabledCipherSuites(ANON_CIPHERS
);
401 s
= new Socket(host
, port
);
408 * Create a {@link ServerSocket}.
411 * the port to accept connections on
413 * TRUE for SSL mode (or FALSE for plain text mode)
415 * @return the {@link ServerSocket}
417 * @throws IOException
418 * in case of I/O error
419 * @throws UnknownHostException
420 * if the IP address of the host could not be determined
421 * @throws IllegalArgumentException
422 * if the port parameter is outside the specified range of valid
423 * port values, which is between 0 and 65535, inclusive
425 static ServerSocket
createSocketServer(int port
, boolean ssl
)
429 ss
= SSLServerSocketFactory
.getDefault().createServerSocket(port
);
430 if (ss
instanceof SSLServerSocket
) {
431 // Should always be the case
432 ((SSLServerSocket
) ss
).setEnabledCipherSuites(ANON_CIPHERS
);
435 ss
= new ServerSocket(port
);
442 * Return all the supported ciphers that do not use authentication.
444 * @return the list of such supported ciphers
446 public static String
[] getAnonCiphers() {
447 List
<String
> anonCiphers
= new ArrayList
<String
>();
448 for (String cipher
: ((SSLSocketFactory
) SSLSocketFactory
.getDefault())
449 .getSupportedCipherSuites()) {
450 if (cipher
.contains("_anon_")) {
451 anonCiphers
.add(cipher
);
455 return anonCiphers
.toArray(new String
[] {});