DefaultClient.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. /*
  2. * Copyright (c) 2011 jMonkeyEngine
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are
  7. * met:
  8. *
  9. * * Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. *
  12. * * Redistributions in binary form must reproduce the above copyright
  13. * notice, this list of conditions and the following disclaimer in the
  14. * documentation and/or other materials provided with the distribution.
  15. *
  16. * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
  17. * may be used to endorse or promote products derived from this software
  18. * without specific prior written permission.
  19. *
  20. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  22. * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  23. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  24. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  25. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  26. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  27. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  28. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  29. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  30. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31. */
  32. package com.jme3.network.base;
  33. import com.jme3.network.ClientStateListener.DisconnectInfo;
  34. import com.jme3.network.*;
  35. import com.jme3.network.kernel.Connector;
  36. import com.jme3.network.message.ClientRegistrationMessage;
  37. import com.jme3.network.message.DisconnectMessage;
  38. import java.nio.ByteBuffer;
  39. import java.util.*;
  40. import java.util.concurrent.CopyOnWriteArrayList;
  41. import java.util.concurrent.CountDownLatch;
  42. import java.util.logging.Level;
  43. import java.util.logging.Logger;
  44. /**
  45. * A default implementation of the Client interface that delegates
  46. * its network connectivity to a kernel.Connector.
  47. *
  48. * @version $Revision$
  49. * @author Paul Speed
  50. */
  51. public class DefaultClient implements Client
  52. {
  53. static Logger log = Logger.getLogger(DefaultClient.class.getName());
  54. // First two channels are reserved for reliable and
  55. // unreliable. Note: channels are endpoint specific so these
  56. // constants and the handling need not have anything to do with
  57. // the same constants in DefaultServer... which is why they are
  58. // separate.
  59. private static final int CH_RELIABLE = 0;
  60. private static final int CH_UNRELIABLE = 1;
  61. private ThreadLocal<ByteBuffer> dataBuffer = new ThreadLocal<ByteBuffer>();
  62. private int id = -1;
  63. private boolean isRunning = false;
  64. private CountDownLatch connecting = new CountDownLatch(1);
  65. private String gameName;
  66. private int version;
  67. private MessageListenerRegistry<Client> messageListeners = new MessageListenerRegistry<Client>();
  68. private List<ClientStateListener> stateListeners = new CopyOnWriteArrayList<ClientStateListener>();
  69. private List<ErrorListener<? super Client>> errorListeners = new CopyOnWriteArrayList<ErrorListener<? super Client>>();
  70. private Redispatch dispatcher = new Redispatch();
  71. private List<ConnectorAdapter> channels = new ArrayList<ConnectorAdapter>();
  72. public DefaultClient( String gameName, int version )
  73. {
  74. this.gameName = gameName;
  75. this.version = version;
  76. }
  77. public DefaultClient( String gameName, int version, Connector reliable, Connector fast )
  78. {
  79. this( gameName, version );
  80. setPrimaryConnectors( reliable, fast );
  81. }
  82. protected void setPrimaryConnectors( Connector reliable, Connector fast )
  83. {
  84. if( reliable == null )
  85. throw new IllegalArgumentException( "The reliable connector cannot be null." );
  86. if( isRunning )
  87. throw new IllegalStateException( "Client is already started." );
  88. if( !channels.isEmpty() )
  89. throw new IllegalStateException( "Channels already exist." );
  90. channels.add(new ConnectorAdapter(reliable, dispatcher, dispatcher, true));
  91. if( fast != null ) {
  92. channels.add(new ConnectorAdapter(fast, dispatcher, dispatcher, false));
  93. } else {
  94. // Add the null adapter to keep the indexes right
  95. channels.add(null);
  96. }
  97. }
  98. protected void checkRunning()
  99. {
  100. if( !isRunning )
  101. throw new IllegalStateException( "Client is not started." );
  102. }
  103. public void start()
  104. {
  105. if( isRunning )
  106. throw new IllegalStateException( "Client is already started." );
  107. // Start up the threads and stuff for the
  108. // connectors that we have
  109. for( ConnectorAdapter ca : channels ) {
  110. if( ca == null )
  111. continue;
  112. ca.start();
  113. }
  114. // Send our connection message with a generated ID until
  115. // we get one back from the server. We'll hash time in
  116. // millis and time in nanos.
  117. // This is used to match the TCP and UDP endpoints up on the
  118. // other end since they may take different routes to get there.
  119. // Behind NAT, many game clients may be coming over the same
  120. // IP address from the server's perspective and they may have
  121. // their UDP ports mapped all over the place.
  122. //
  123. // Since currentTimeMillis() is absolute time and nano time
  124. // is roughtly related to system start time, adding these two
  125. // together should be plenty unique for our purposes. It wouldn't
  126. // hurt to reconcile with IP on the server side, though.
  127. long tempId = System.currentTimeMillis() + System.nanoTime();
  128. // Set it true here so we can send some messages.
  129. isRunning = true;
  130. ClientRegistrationMessage reg;
  131. reg = new ClientRegistrationMessage();
  132. reg.setId(tempId);
  133. reg.setGameName(getGameName());
  134. reg.setVersion(getVersion());
  135. reg.setReliable(true);
  136. send(CH_RELIABLE, reg, false);
  137. // Send registration messages to any other configured
  138. // connectors
  139. reg = new ClientRegistrationMessage();
  140. reg.setId(tempId);
  141. reg.setReliable(false);
  142. for( int ch = CH_UNRELIABLE; ch < channels.size(); ch++ ) {
  143. if( channels.get(ch) == null )
  144. continue;
  145. send(ch, reg, false);
  146. }
  147. }
  148. protected void waitForConnected()
  149. {
  150. if( isConnected() )
  151. return;
  152. try {
  153. connecting.await();
  154. } catch( InterruptedException e ) {
  155. throw new RuntimeException( "Interrupted waiting for connect", e );
  156. }
  157. }
  158. public boolean isConnected()
  159. {
  160. return id != -1 && isRunning;
  161. }
  162. public int getId()
  163. {
  164. return id;
  165. }
  166. public String getGameName()
  167. {
  168. return gameName;
  169. }
  170. public int getVersion()
  171. {
  172. return version;
  173. }
  174. public void send( Message message )
  175. {
  176. if( message.isReliable() || channels.get(CH_UNRELIABLE) == null ) {
  177. send(CH_RELIABLE, message, true);
  178. } else {
  179. send(CH_UNRELIABLE, message, true);
  180. }
  181. }
  182. protected void send( int channel, Message message, boolean waitForConnected )
  183. {
  184. checkRunning();
  185. if( waitForConnected ) {
  186. // Make sure we aren't still connecting
  187. waitForConnected();
  188. }
  189. ByteBuffer buffer = dataBuffer.get();
  190. if( buffer == null ) {
  191. buffer = ByteBuffer.allocate( 65536 + 2 );
  192. dataBuffer.set(buffer);
  193. }
  194. buffer.clear();
  195. // Convert the message to bytes
  196. buffer = MessageProtocol.messageToBuffer(message, buffer);
  197. // Since we share the buffer between invocations, we will need to
  198. // copy this message's part out of it. This is because we actually
  199. // do the send on a background thread.
  200. byte[] temp = new byte[buffer.remaining()];
  201. System.arraycopy(buffer.array(), buffer.position(), temp, 0, buffer.remaining());
  202. buffer = ByteBuffer.wrap(temp);
  203. channels.get(channel).write(buffer);
  204. }
  205. public void close()
  206. {
  207. checkRunning();
  208. closeConnections( null );
  209. }
  210. protected void closeConnections( DisconnectInfo info )
  211. {
  212. if( !isRunning )
  213. return;
  214. // Send a close message
  215. // Tell the thread it's ok to die
  216. for( ConnectorAdapter ca : channels ) {
  217. if( ca == null )
  218. continue;
  219. ca.close();
  220. }
  221. // Wait for the threads?
  222. // Just in case we never fully connected
  223. connecting.countDown();
  224. fireDisconnected(info);
  225. isRunning = false;
  226. }
  227. public void addClientStateListener( ClientStateListener listener )
  228. {
  229. stateListeners.add( listener );
  230. }
  231. public void removeClientStateListener( ClientStateListener listener )
  232. {
  233. stateListeners.remove( listener );
  234. }
  235. public void addMessageListener( MessageListener<? super Client> listener )
  236. {
  237. messageListeners.addMessageListener( listener );
  238. }
  239. public void addMessageListener( MessageListener<? super Client> listener, Class... classes )
  240. {
  241. messageListeners.addMessageListener( listener, classes );
  242. }
  243. public void removeMessageListener( MessageListener<? super Client> listener )
  244. {
  245. messageListeners.removeMessageListener( listener );
  246. }
  247. public void removeMessageListener( MessageListener<? super Client> listener, Class... classes )
  248. {
  249. messageListeners.removeMessageListener( listener, classes );
  250. }
  251. public void addErrorListener( ErrorListener<? super Client> listener )
  252. {
  253. errorListeners.add( listener );
  254. }
  255. public void removeErrorListener( ErrorListener<? super Client> listener )
  256. {
  257. errorListeners.remove( listener );
  258. }
  259. protected void fireConnected()
  260. {
  261. for( ClientStateListener l : stateListeners ) {
  262. l.clientConnected( this );
  263. }
  264. }
  265. protected void fireDisconnected( DisconnectInfo info )
  266. {
  267. for( ClientStateListener l : stateListeners ) {
  268. l.clientDisconnected( this, info );
  269. }
  270. }
  271. /**
  272. * Either calls the ErrorListener or closes the connection
  273. * if there are no listeners.
  274. */
  275. protected void handleError( Throwable t )
  276. {
  277. // If there are no listeners then close the connection with
  278. // a reason
  279. if( errorListeners.isEmpty() ) {
  280. log.log( Level.SEVERE, "Termining connection due to unhandled error", t );
  281. DisconnectInfo info = new DisconnectInfo();
  282. info.reason = "Connection Error";
  283. info.error = t;
  284. closeConnections(info);
  285. return;
  286. }
  287. for( ErrorListener l : errorListeners ) {
  288. l.handleError( this, t );
  289. }
  290. }
  291. protected void dispatch( Message m )
  292. {
  293. // Pull off the connection management messages we're
  294. // interested in and then pass on the rest.
  295. if( m instanceof ClientRegistrationMessage ) {
  296. // Then we've gotten our real id
  297. this.id = (int)((ClientRegistrationMessage)m).getId();
  298. log.log( Level.INFO, "Connection established, id:{0}.", this.id );
  299. connecting.countDown();
  300. fireConnected();
  301. return;
  302. } if( m instanceof DisconnectMessage ) {
  303. // Can't do too much else yet
  304. String reason = ((DisconnectMessage)m).getReason();
  305. log.log( Level.SEVERE, "Connection terminated, reason:{0}.", reason );
  306. DisconnectInfo info = new DisconnectInfo();
  307. info.reason = reason;
  308. closeConnections(info);
  309. }
  310. // Make sure client MessageListeners are called single-threaded
  311. // since it could receive messages from the TCP and UDP
  312. // thread simultaneously.
  313. synchronized( this ) {
  314. messageListeners.messageReceived( this, m );
  315. }
  316. }
  317. protected class Redispatch implements MessageListener<Object>, ErrorListener<Object>
  318. {
  319. public void messageReceived( Object source, Message m )
  320. {
  321. dispatch( m );
  322. }
  323. public void handleError( Object source, Throwable t )
  324. {
  325. // Only doing the DefaultClient.this to make the code
  326. // checker happy... it compiles fine without it but I
  327. // don't like red lines in my editor. :P
  328. DefaultClient.this.handleError( t );
  329. }
  330. }
  331. }