1
2
3
4
5 package spellcast.ui;
6
7 import java.io.ByteArrayInputStream;
8 import java.io.IOException;
9 import java.io.InterruptedIOException;
10 import java.io.ObjectInputStream;
11 import java.net.DatagramPacket;
12 import java.net.DatagramSocket;
13 import java.net.InetAddress;
14 import java.util.ArrayList;
15 import java.util.Iterator;
16 import java.util.List;
17
18 import javax.swing.SwingUtilities;
19 import org.apache.log4j.Logger;
20 import spellcast.net.NetConstants;
21 import spellcast.net.ServerStatus;
22
23 class AvailableServerListUpdater implements Runnable {
24 /***
25 * The datagram packet size is 1024 bytes. Any packet exceeding this length
26 * will be truncated.
27 */
28 private static final int PACKET_SIZE = 1024;
29
30 /***
31 * The sleep for this many milliseconds before running the loop again.
32 * On each cycle we check for any new messages from servers and update the
33 * connection list.
34 */
35 private static final int SEND_REQUEST_SLEEP_TIMEOUT = 1 * 1000;
36
37 /***
38 * Once the list has been updated this many times a new request is sent
39 * to query for new servers. This gives us time to receive the responses
40 * from servers during each cycle.
41 */
42 private static final int SEND_REQUEST_AFTER_UPDATE_NUMBER = 5;
43
44 private DatagramSocket socket;
45 private int numberOfUpdatesDone;
46 private Thread serverListThread;
47 private ServerListTableModel serverList;
48 private boolean isRunning;
49
50 private static final Logger logger = Logger.getLogger("client.connect");
51 private static final Logger logger_net = Logger.getLogger("client.connect.net");
52
53 AvailableServerListUpdater(ServerListTableModel serverList) {
54 this.serverList = serverList;
55 try {
56 InetAddress bindAddress = InetAddress.getLocalHost();
57 socket = new DatagramSocket(0, bindAddress);
58 socket.setSoTimeout(100);
59 }
60 catch (Exception ex) {
61 logger.error(
62 "Could not create DatagramSocket for ServerStatusRequest/Responses",
63 ex);
64 }
65 }
66
67 public void run() {
68 while (isRunning) {
69 updateAvailableServers();
70 try {
71 Thread.sleep(SEND_REQUEST_SLEEP_TIMEOUT);
72 }
73 catch (InterruptedException ex) {
74 logger.error("Unexepectedly Interrupted.", ex);
75 }
76 }
77 }
78
79 public void start() {
80 isRunning = true;
81 numberOfUpdatesDone = 0;
82 serverListThread = new Thread(this, "ServerListUpdater");
83 serverListThread.start();
84 }
85
86 public void stop() {
87 isRunning = false;
88 }
89
90 private void updateAvailableServers() {
91 if ((numberOfUpdatesDone % SEND_REQUEST_AFTER_UPDATE_NUMBER) == 0) {
92 numberOfUpdatesDone = 0;
93 sendServerStatusRequest();
94 }
95 numberOfUpdatesDone++;
96
97 ServerStatus ss = null;
98 ArrayList tempList = new ArrayList(2);
99 while ((ss = receiveServerStatusResponse()) != null) {
100 logger_net.info(
101 "\nReceived response\n"
102 + "Address: "
103 + ss.getServerIPAddress()
104 + "\n"
105 + "Game Name: "
106 + ss.getGameName()
107 + "\n");
108 tempList.add(ss);
109 }
110 if (tempList.size() != 0) {
111 SwingUtilities.invokeLater(new DoUpdateOnSwingThread(serverList, tempList));
112 }
113 }
114
115 /***
116 * Sends a server list request on the broadcast address and SPELLCAST_NET_SERVERLIST_PORT.
117 * The packet sent contains 1 byte, as there is no information of value in the
118 * payload of the message.
119 */
120 private void sendServerStatusRequest() {
121 try {
122 InetAddress broadcast =
123 InetAddress.getByName(NetConstants.SPELLCAST_NET_BROADCAST_INETADDRESS);
124 DatagramPacket send =
125 new DatagramPacket(
126 new byte[1],
127 1,
128 broadcast,
129 NetConstants.SPELLCAST_NET_SERVERLIST_PORT);
130 socket.send(send);
131 logger_net.debug(
132 "\nLocal Address = "
133 + socket.getLocalAddress()
134 + "\n"
135 + "Local Port = "
136 + socket.getLocalPort()
137 + "\n");
138 }
139 catch (Exception ex) {
140 logger.error("Exception occurred.", ex);
141 }
142 }
143
144 /***
145 * Receive any server status requests. If there is a problem with
146 * receiving the message then a null is returned and the error logged.
147 */
148 private ServerStatus receiveServerStatusResponse() {
149 ServerStatus result = null;
150 DatagramPacket response =
151 new DatagramPacket(new byte[PACKET_SIZE], PACKET_SIZE);
152 boolean messageReceived = false;
153
154 try {
155 socket.receive(response);
156 logger_net.debug("Response Size = " + response.getLength());
157 messageReceived = true;
158 }
159 catch (InterruptedIOException ix) {
160 messageReceived = false;
161 }
162 catch (IOException ex) {
163 logger.error("Failed on socket.receive.", ex);
164 }
165
166 if (messageReceived && response.getLength() != 0) {
167 try {
168 ObjectInputStream ois =
169 new ObjectInputStream(new ByteArrayInputStream(response.getData()));
170 Object obj = ois.readObject();
171 if (!(obj instanceof ServerStatus)) {
172 logger.error("ServerStatus not received. Received " + obj.getClass().getName());
173 }
174 else {
175 result = (ServerStatus) obj;
176 }
177 }
178 catch (Exception ex) {
179 logger.error("Failed to decode ServerStatus from message.", ex);
180 }
181 }
182 return result;
183 }
184
185 /***
186 * This inner class is updates the server list on Swings Thread.
187 * This class is runnable so that <code>SwingUtilities.invokeLater</code>
188 * can run the necessary UI changes on Swing's thread.
189 * This class requires the server list table model that is to be updated
190 * as well as a list of the servers to add to the model. This class creates a copy
191 * of the list of servers.
192 */
193 class DoUpdateOnSwingThread implements Runnable {
194 private ServerListTableModel serverList;
195 private ArrayList newServers;
196
197 DoUpdateOnSwingThread(ServerListTableModel serverList, List newServers) {
198 this.serverList = serverList;
199 this.newServers = new ArrayList(newServers);
200 }
201
202 public void run() {
203 Iterator i = newServers.iterator();
204 while (i.hasNext()) {
205 ServerStatus ss = (ServerStatus) i.next();
206 serverList.add(ss);
207 }
208 }
209 }
210 }