ServerLoop.hx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. /*
  2. * Copyright (c) 2005, The haXe Project Contributors
  3. * All rights reserved.
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions are met:
  6. *
  7. * - Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * - Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. *
  13. * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
  14. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  15. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  16. * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
  17. * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  18. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  19. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  20. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  21. * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  22. * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
  23. * DAMAGE.
  24. */
  25. package neko.net;
  26. private typedef ServerClient<ClientData> = {
  27. var sock : Socket;
  28. var buffer : haxe.io.Bytes;
  29. var bufbytes : Int;
  30. var data : ClientData;
  31. }
  32. /**
  33. This class enables you to quickly create a custom server that can
  34. serve several clients in parallel. This server is using a single
  35. thread and process so the server itself processing is not parallel.
  36. Non-blocking sockets are used to ensure that a slow client does not
  37. block the others.
  38. **/
  39. class ServerLoop<ClientData> {
  40. /**
  41. Each client has an associated buffer. This is the initial buffer size which
  42. is set to 128 bytes by default.
  43. **/
  44. public static var DEFAULT_BUFSIZE = 128;
  45. /**
  46. Each client has an associated buffer. This is the maximum buffer size which
  47. is set to 64K by default. When that size is reached and some data can't be processed,
  48. the client is disconnected.
  49. **/
  50. public static var MAX_BUFSIZE = (1 << 16);
  51. /**
  52. This is the value of number client requests that the server socket
  53. listen for. By default this number is 10 but can be increased for
  54. servers supporting a large number of simultaneous requests.
  55. **/
  56. public var listenCount : Int;
  57. /**
  58. See [update].
  59. **/
  60. public var updateTime : Float;
  61. var newData : Socket -> ClientData;
  62. var socks : Array<Socket>;
  63. public var clients : List<ClientData>;
  64. /**
  65. Creates a server instance. The [newData] methods must return
  66. the data associated with the Client.
  67. **/
  68. public function new( ?newData ) {
  69. this.newData = if( newData == null ) function(_) { return null; } else newData;
  70. clients = new List();
  71. socks = new Array();
  72. listenCount = 10;
  73. updateTime = 1;
  74. }
  75. /**
  76. Closes the client connection and removes it from the client List.
  77. **/
  78. public function closeConnection( s : Socket ) : Bool {
  79. var cl : ServerClient<ClientData> = untyped s.__client;
  80. if( cl == null || !clients.remove(cl.data) )
  81. return false;
  82. socks.remove(s);
  83. try s.close() catch( e : Dynamic ) { };
  84. clientDisconnected(cl.data);
  85. return true;
  86. }
  87. /**
  88. The [update] method is called after each socket event has been
  89. processed or when [updateTime] has been reached. It can be used
  90. to perform time-regular tasks such as pings. By default [updateTime]
  91. is set to one second.
  92. **/
  93. public function update() {
  94. }
  95. /**
  96. This method is called after a client has been disconnected.
  97. **/
  98. public function clientDisconnected( d : ClientData ) {
  99. }
  100. /**
  101. This method can be used instead of writing directly to the socket.
  102. It ensures that all the data is correctly sent. If an error occurs
  103. while sending the data, no exception will occur but the client will
  104. be gracefully disconnected.
  105. **/
  106. public function clientWrite( s : Socket, buf : haxe.io.Bytes, pos : Int, len : Int ) {
  107. try {
  108. while( len > 0 ) {
  109. var nbytes = s.output.writeBytes(buf,pos,len);
  110. pos += nbytes;
  111. len -= nbytes;
  112. }
  113. } catch( e : Dynamic ) {
  114. closeConnection(s);
  115. }
  116. }
  117. /**
  118. This method is called when some data has been readed into a Client buffer.
  119. If the data can be handled, then you can return the number of bytes handled
  120. that needs to be removed from the buffer. It the data can't be handled (some
  121. part of the message is missing for example), returns 0.
  122. **/
  123. public function processClientData( d : ClientData, buf : haxe.io.Bytes, bufpos : Int, buflen : Int ) {
  124. throw "ServerLoop::processClientData is not implemented";
  125. return 0;
  126. }
  127. /**
  128. Called when an error occured. This enable you to log the error somewhere.
  129. By default the error is displayed using [trace].
  130. **/
  131. public function onError( e : Dynamic ) {
  132. trace(Std.string(e)+"\n"+haxe.Stack.toString(haxe.Stack.exceptionStack()));
  133. }
  134. function readData( cl : ServerClient<ClientData> ) {
  135. var buflen = cl.buffer.length;
  136. // eventually double the buffer size
  137. if( cl.bufbytes == buflen ) {
  138. var nsize = buflen * 2;
  139. if( nsize > MAX_BUFSIZE ) {
  140. if( buflen == MAX_BUFSIZE )
  141. throw "Max buffer size reached";
  142. nsize = MAX_BUFSIZE;
  143. }
  144. var buf2 = haxe.io.Bytes.alloc(nsize);
  145. buf2.blit(0,cl.buffer,0,buflen);
  146. buflen = nsize;
  147. cl.buffer = buf2;
  148. }
  149. // read the available data
  150. var nbytes = cl.sock.input.readBytes(cl.buffer,cl.bufbytes,buflen - cl.bufbytes);
  151. cl.bufbytes += nbytes;
  152. }
  153. function processData( cl : ServerClient<ClientData> ) {
  154. var pos = 0;
  155. while( cl.bufbytes > 0 ) {
  156. var nbytes = processClientData(cl.data,cl.buffer,pos,cl.bufbytes);
  157. if( nbytes == 0 )
  158. break;
  159. pos += nbytes;
  160. cl.bufbytes -= nbytes;
  161. }
  162. if( pos > 0 )
  163. cl.buffer.blit(0,cl.buffer,pos,cl.bufbytes);
  164. }
  165. /**
  166. Run the server. This function should never return.
  167. **/
  168. public function run( host : Host, port : Int ) {
  169. var serv = new Socket();
  170. serv.bind(host,port);
  171. serv.listen(listenCount);
  172. socks = [serv];
  173. while( true ) {
  174. for( s in Socket.select(socks,null,null,updateTime).read ) {
  175. var cl : ServerClient<ClientData> = untyped s.__client;
  176. if( cl == null ) {
  177. // no associated client : it's our server socket
  178. var sock = serv.accept();
  179. sock.setBlocking(true);
  180. cl = {
  181. sock : sock,
  182. data : null,
  183. buffer : haxe.io.Bytes.alloc(DEFAULT_BUFSIZE),
  184. bufbytes : 0,
  185. };
  186. // bind the client
  187. untyped sock.__client = cl;
  188. // creates the data
  189. try {
  190. cl.data = newData(sock);
  191. } catch( e : Dynamic ) {
  192. onError(e);
  193. try sock.close() catch( e : Dynamic ) { };
  194. continue;
  195. }
  196. // adds the client to the lists
  197. socks.push(sock);
  198. clients.add(cl.data);
  199. } else {
  200. // read & process the data
  201. try {
  202. readData(cl);
  203. processData(cl);
  204. } catch( e : Dynamic ) {
  205. if( !Std.is(e,haxe.io.Eof) )
  206. onError(e);
  207. closeConnection(cl.sock);
  208. }
  209. }
  210. }
  211. update();
  212. }
  213. serv.close();
  214. }
  215. }