-
Notifications
You must be signed in to change notification settings - Fork 0
/
AbstractServer.java
executable file
·416 lines (365 loc) · 12.3 KB
/
AbstractServer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
// This file contains material supporting section 3.8 of the textbook:
// "Object Oriented Software Engineering" and is issued under the open-source
// license found at www.lloseng.com
import java.net.*;
import java.util.*;
import java.io.*;
/**
* The <code> AbstractServer </code> class maintains a thread that waits
* for connection attempts from clients. When a connection attempt occurs
* it creates a new <code> ConnectionToClient </code> instance which
* runs as a thread. When a client is thus connected to the
* server, the two programs can then exchange <code> Object </code>
* instances.<p>
*
* Method <code> handleMessageFromClient </code> must be defined by
* a concrete subclass. Several other hook methods may also be
* overriden.<p>
*
* Several public service methods are provided to applications that use
* this framework, and several hook methods are also available<p>
*
* Project Name: OCSF (Object Client-Server Framework)<p>
*
* @author Dr Robert Laganière
* @author Dr Timothy C. Lethbridge
* @author François Bélanger
* @author Paul Holden
* @version February 2001 (2.12)
* @see ocsf.server.ConnectionToClient
*/
public abstract class AbstractServer implements Runnable {
// INSTANCE VARIABLES *********************************************
/**
* The server socket: listens for clients who want to connect.
*/
private ServerSocket serverSocket = null;
/**
* The connection listener thread.
*/
private Thread connectionListener;
/**
* The port number
*/
private int port;
/**
* The server timeout while for accepting connections.
* After timing out, the server will check to see if a command to
* stop the server has been issued; it not it will resume accepting
* connections.
* Set to half a second by default.
*/
private int timeout = 500;
/**
* The maximum queue length; i.e. the maximum number of clients that
* can be waiting to connect.
* Set to 10 by default.
*/
private int backlog = 10;
/**
* The thread group associated with client threads. Each member of the
* thread group is a <code> ConnectionToClient </code>.
*/
private ThreadGroup clientThreadGroup;
/**
* Indicates if the listening thread is ready to stop. Set to
* false by default.
*/
private boolean readyToStop = false;
// CONSTRUCTOR ******************************************************
/**
* Constructs a new server.
*
* @param port the port number on which to listen.
*/
public AbstractServer(int port) {
this.port = port;
this.clientThreadGroup = new ThreadGroup("ConnectionToClient threads")
{
// All uncaught exceptions in connection threads will
// be sent to the clientException callback method.
public void uncaughtException(Thread thread, Throwable exception) {
clientException((ConnectionToClient)thread, exception);
}
};
}
// INSTANCE METHODS *************************************************
/**
* Begins the thread that waits for new clients.
* If the server is already in listening mode, this
* call has no effect.
*
* @exception IOException if an I/O error occurs
* when creating the server socket.
*/
final public void listen() throws IOException {
if (!isListening()) {
if (serverSocket == null) {
serverSocket = new ServerSocket(getPort(), backlog);
}
serverSocket.setSoTimeout(timeout);
readyToStop = false;
connectionListener = new Thread(this);
connectionListener.start();
}
}
/**
* Causes the server to stop accepting new connections.
*/
final public void stopListening() {
readyToStop = true;
}
/**
* Closes the server socket and the connections with all clients.
* Any exception thrown while closing a client is ignored.
* If one wishes to catch these exceptions, then clients
* should be individually closed before calling this method.
* The method also stops listening if this thread is running.
* If the server is already closed, this
* call has no effect.
*
* @exception IOException if an I/O error occurs while
* closing the server socket.
*/
final synchronized public void close() throws IOException {
if (serverSocket == null)
return;
stopListening();
try {
serverSocket.close();
}
finally {
// Close the client sockets of the already connected clients
Thread[] clientThreadList = getClientConnections();
for (int i=0; i<clientThreadList.length; i++) {
try {
((ConnectionToClient)clientThreadList[i]).close();
}
// Ignore all exceptions when closing clients.
catch(Exception ex) {}
}
serverSocket = null;
serverClosed();
}
}
/**
* Sends a message to every client connected to the server.
* This is merely a utility; a subclass may want to do some checks
* before actually sending messages to all clients. This method
* can be overriden, but if so it should still perform the general
* function of sending to all clients, perhaps after some kind
* of filtering is done. Any exception thrown while
* sending the message to a particular client is ignored.
*
* @param msg Object The message to be sent
*/
public void sendToAllClients(Object msg) {
Thread[] clientThreadList = getClientConnections();
for (int i=0; i<clientThreadList.length; i++) {
try {
((ConnectionToClient)clientThreadList[i]).sendToClient(msg);
}
catch (Exception ex) {}
}
}
// ACCESSING METHODS ------------------------------------------------
/**
* Returns true if the server is ready to accept new clients.
*
* @return true if the server is listening.
*/
final public boolean isListening() {
return (connectionListener != null);
}
/**
* Returns an array containing the existing
* client connections. This can be used by
* concrete subclasses to implement messages that do something with
* each connection (e.g. kill it, send a message to it etc.).
* Remember that after this array is obtained, some clients
* in this migth disconnect. New clients can also connect,
* these later will not appear in the array.
*
* @return an array of <code>Thread</code> containing
* <code>ConnectionToClient</code> instances.
*/
synchronized final public Thread[] getClientConnections() {
Thread[] clientThreadList = new Thread[clientThreadGroup.activeCount()];
clientThreadGroup.enumerate(clientThreadList);
return clientThreadList;
}
/**
* Counts the number of clients currently connected.
*
* @return the number of clients currently connected.
*/
final public int getNumberOfClients() {
return clientThreadGroup.activeCount();
}
/**
* Returns the port number.
*
* @return the port number.
*/
final public int getPort() {
return port;
}
/**
* Sets the port number for the next connection.
* The server must be closed and restarted for the port
* change to be in effect.
*
* @param port the port number.
*/
final public void setPort(int port) {
this.port = port;
}
/**
* Sets the timeout time when accepting connections.
* The default is half a second. This means that stopping the
* server may take up to timeout duration to actually stop.
* The server must be stopped and restarted for the timeout
* change to be effective.
*
* @param timeout the timeout time in ms.
*/
final public void setTimeout(int timeout) {
this.timeout = timeout;
}
/**
* Sets the maximum number of waiting connections accepted by the
* operating system. The default is 20.
* The server must be closed and restarted for the backlog
* change to be in effect.
*
* @param backlog the maximum number of connections.
*/
final public void setBacklog(int backlog) {
this.backlog = backlog;
}
// RUN METHOD -------------------------------------------------------
/**
* Runs the listening thread that allows clients to connect.
* Not to be called.
*/
final public void run() {
// call the hook method to notify that the server is starting
serverStarted();
try {
// Repeatedly waits for a new client connection, accepts it, and
// starts a new thread to handle data exchange.
while(!readyToStop) {
try {
// Wait here for new connection attempts, or a timeout
Socket clientSocket = serverSocket.accept();
// When a client is accepted, create a thread to handle
// the data exchange, then add it to thread group
synchronized(this) {
ConnectionToClient c = new ConnectionToClient(this.clientThreadGroup, clientSocket, this);
}
}
catch (InterruptedIOException exception) {
// This will be thrown when a timeout occurs.
// The server will continue to listen if not ready to stop.
}
}
// call the hook method to notify that the server has stopped
serverStopped();
}
catch (IOException exception) {
if (!readyToStop) {
// Closing the socket must have thrown a SocketException
listeningException(exception);
}
else {
serverStopped();
}
}
finally {
readyToStop = true;
connectionListener = null;
}
}
// METHODS DESIGNED TO BE OVERRIDDEN BY CONCRETE SUBCLASSES ---------
/**
* Hook method called each time a new client connection is
* accepted. The default implementation does nothing.
* @param client the connection connected to the client.
*/
protected void clientConnected(ConnectionToClient client) {}
/**
* Hook method called each time a client disconnects.
* The default implementation does nothing. The method
* may be overridden by subclasses but should remains synchronized.
*
* @param client the connection with the client.
*/
synchronized protected void clientDisconnected(ConnectionToClient client) {}
/**
* Hook method called each time an exception is thrown in a
* ConnectionToClient thread.
* The method may be overridden by subclasses but should remains
* synchronized.
*
* @param client the client that raised the exception.
* @param Throwable the exception thrown.
*/
synchronized protected void clientException(ConnectionToClient client, Throwable exception) {}
/**
* Hook method called when the server stops accepting
* connections because an exception has been raised.
* The default implementation does nothing.
* This method may be overriden by subclasses.
*
* @param exception the exception raised.
*/
protected void listeningException(Throwable exception) {}
/**
* Hook method called when the server starts listening for
* connections. The default implementation does nothing.
* The method may be overridden by subclasses.
*/
protected void serverStarted() {}
/**
* Hook method called when the server stops accepting
* connections. The default implementation
* does nothing. This method may be overriden by subclasses.
*/
protected void serverStopped() {}
/**
* Hook method called when the server is clased.
* The default implementation does nothing. This method may be
* overriden by subclasses. When the server is closed while still
* listening, serverStopped() will also be called.
*/
protected void serverClosed() {}
/**
* Handles a command sent from one client to the server.
* This MUST be implemented by subclasses, who should respond to
* messages.
* This method is called by a synchronized method so it is also
* implcitly synchronized.
*
* @param msg the message sent.
* @param client the connection connected to the client that
* sent the message.
*/
protected abstract void handleMessageFromClient(Object msg, ConnectionToClient client);
// METHODS TO BE USED FROM WITHIN THE FRAMEWORK ONLY ----------------
/**
* Receives a command sent from the client to the server.
* Called by the run method of <code>ConnectionToClient</code>
* instances that are watching for messages coming from the server
* This method is synchronized to ensure that whatever effects it has
* do not conflict with work being done by other threads. The method
* simply calls the <code>handleMessageFromClient</code> slot method.
*
* @param msg the message sent.
* @param client the connection connected to the client that
* sent the message.
*/
final synchronized void receiveMessageFromClient(Object msg, ConnectionToClient client) {
this.handleMessageFromClient(msg, client);
}
}
// End of AbstractServer Class