1
2
3
4
5 package spellcast.client;
6
7 import java.io.IOException;
8 import java.net.InetAddress;
9 import java.net.Socket;
10 import java.util.ArrayList;
11
12 import org.apache.log4j.Logger;
13 import spellcast.event.ConnectionRequestEvent;
14 import spellcast.event.GameEvent;
15 import spellcast.net.NetConstants;
16 import spellcast.net.VersionDetails;
17 import spellcast.net.WizardSocket;
18 import spellcast.ui.SpellcastView;
19 import spellcast.util.TextUtil;
20
21 /***
22 * This class handles the socket functions for the client.
23 *
24 * @author Barrie Treloar
25 */
26 public class ClientConnectionHandler implements Runnable {
27 private SpellcastView view;
28 private Thread socketHandlerThread;
29 private boolean isConnected;
30 private InetAddress serverAddress;
31 private int port;
32 private String serverAddressString;
33 private String wizardName;
34 private String gender;
35 private WizardSocket wizardSocket;
36 private VersionDetails programVersionDetails;
37 private boolean cleanupAlreadyDone;
38
39 /***
40 * A multi-threaded accessed object. Ensure lock on <code>incomingEventQueueLock</code>
41 * is obtained before accessing.
42 */
43 private ArrayList incomingEventQueue = new ArrayList(10);
44 private Object incomingEventQueueLock = new Object();
45
46 /***
47 * The timeout for all socket operations is 300 milliseconds.
48 */
49 private static final int SOCKET_TIMEOUT = 300;
50
51 private static final Logger logger = Logger.getLogger("client.net");
52
53 public ClientConnectionHandler(
54 SpellcastView view,
55 VersionDetails programVersionDetails) {
56 this.view = view;
57 this.programVersionDetails = programVersionDetails;
58 }
59
60 public void run() {
61 GameEvent event = null;
62
63 connectToServer();
64
65 while (isConnected) {
66 Thread.yield();
67 try {
68 event = wizardSocket.receive();
69 if (event != null) {
70 addIncomingEvent(event);
71 }
72 }
73 catch (IOException e) {
74 logger.warn("Exception while reading message from server.", e);
75 isConnected = false;
76 }
77 }
78 cleanup();
79 }
80
81 /***
82 * Add an event to the incoming event queue.
83 * When an event is added it calls <code>notify</code> to
84 * allow any waiting threads to attempt to continue.
85 */
86 private void addIncomingEvent(GameEvent e) {
87 synchronized (incomingEventQueueLock) {
88 incomingEventQueue.add(e);
89 incomingEventQueueLock.notify();
90 }
91 }
92
93 /***
94 * Removes the first event from the incoming event queue and returns it.
95 * If there are no events in the queue this will <code>wait</code>
96 * until one is added when the server sends the client an event, at which point
97 * the thread will be notified.
98 * <p>
99 * If there is an IOException <code>cleanup</code> may call notify in which
100 * case the event will be null.
101 */
102 public GameEvent receive() {
103 GameEvent result = null;
104
105 synchronized (incomingEventQueueLock) {
106 if (incomingEventQueue.size() == 0) {
107 try {
108 incomingEventQueueLock.wait();
109 }
110 catch (InterruptedException e) {
111 logger.error(
112 "Unexpectedly interrupted while waiting in "
113 + "ClientConnectionHandler.receive",
114 e);
115 throw new RuntimeException(
116 "Unexpectedly interrupted while waiting in "
117 + "ServerConnectionHandler.receive");
118 }
119 }
120
121
122 if (incomingEventQueue.size() != 0) {
123 result = (GameEvent) incomingEventQueue.remove(0);
124 }
125 }
126 return result;
127 }
128
129 /***
130 * Send the event to the server.
131 */
132 public void send(GameEvent event) {
133 try {
134 if (isConnected) {
135 wizardSocket.send(event);
136 }
137 }
138 catch (IOException e) {
139 logger.warn("Exception whilst sending message.", e);
140 isConnected = false;
141 cleanup();
142 }
143 }
144
145 /***
146 * Connect to the Spellcast server.
147 */
148 private void connectToServer() {
149 try {
150 String hostString = TextUtil.getIPAddress(serverAddressString);
151 port = TextUtil.getPort(serverAddressString);
152 if (port == -1) {
153 port = NetConstants.SPELLCAST_NET_DEFAULT_PORT;
154 }
155 view.addMessage("Connecting to " + hostString + ":" + port + " ... ");
156 Socket s = new Socket(hostString, port);
157 s.setSoTimeout(SOCKET_TIMEOUT);
158 wizardSocket = new WizardSocket(programVersionDetails);
159 wizardSocket.setSocket(s);
160 wizardSocket.send(new ConnectionRequestEvent(wizardName, gender));
161 view.addMessage("established.\n");
162 view.connected(true);
163 isConnected = true;
164 }
165 catch (IOException e) {
166 isConnected = false;
167 logger.warn("Failed to connect to server.", e);
168 view.addMessage("failed.\n");
169 view.connected(false);
170 }
171 }
172
173 /***
174 * Request connection to the specified address and port.
175 *
176 * @param serverAddressString a server string of the form host[:port]
177 * @param wizardName the name of the client's wizard
178 * @param gender the gender of the client's wizard
179 */
180 public void connect(
181 String serverAddressString,
182 String wizardName,
183 String gender) {
184 this.serverAddressString = serverAddressString;
185 this.wizardName = wizardName;
186 this.gender = gender;
187 cleanupAlreadyDone = false;
188 socketHandlerThread = new Thread(this, "ClientToServer");
189 socketHandlerThread.start();
190 }
191
192 /***
193 * Request the network controller to disconnect from
194 * the existing connection.
195 */
196 public void disconnect() {
197 isConnected = false;
198 }
199
200 /***
201 * Waits for the socket handler thread to complete.
202 */
203 public void join() {
204 if (socketHandlerThread != null) {
205 try {
206 socketHandlerThread.join();
207 }
208 catch (InterruptedException e) {
209 logger.error("Unexepectedly Interrupted.", e);
210 }
211 }
212 }
213
214 /***
215 * Deallocate resources allocated used during <code>run</code>
216 * If this is a graceful cleanup the client state machine would have received
217 * a disconnect event for itself and terminated cleanly BEFORE cleanup is called.
218 * If this is not a graceful cleanup (because of IOExceptions) then the state
219 * machine will not have terminated yet and is possibly blocked waiting for
220 * an external event.
221 */
222 private synchronized void cleanup() {
223 if (!cleanupAlreadyDone) {
224 synchronized (incomingEventQueue) {
225 incomingEventQueue.notify();
226 }
227 if (wizardSocket != null) {
228 wizardSocket.close();
229 }
230 view.disconnected();
231 socketHandlerThread = null;
232 cleanupAlreadyDone = true;
233 }
234 }
235
236 }