364 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
		
		
			
		
	
	
			364 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
| 
								 | 
							
								/*
							 | 
						||
| 
								 | 
							
								 * Copyright (C)2005-2019 Haxe Foundation
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * Permission is hereby granted, free of charge, to any person obtaining a
							 | 
						||
| 
								 | 
							
								 * copy of this software and associated documentation files (the "Software"),
							 | 
						||
| 
								 | 
							
								 * to deal in the Software without restriction, including without limitation
							 | 
						||
| 
								 | 
							
								 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
							 | 
						||
| 
								 | 
							
								 * and/or sell copies of the Software, and to permit persons to whom the
							 | 
						||
| 
								 | 
							
								 * Software is furnished to do so, subject to the following conditions:
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * The above copyright notice and this permission notice shall be included in
							 | 
						||
| 
								 | 
							
								 * all copies or substantial portions of the Software.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
							 | 
						||
| 
								 | 
							
								 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
							 | 
						||
| 
								 | 
							
								 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
							 | 
						||
| 
								 | 
							
								 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
							 | 
						||
| 
								 | 
							
								 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
							 | 
						||
| 
								 | 
							
								 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
							 | 
						||
| 
								 | 
							
								 * DEALINGS IN THE SOFTWARE.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								package cpp.net;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import cpp.vm.Thread;
							 | 
						||
| 
								 | 
							
								import cpp.net.Poll;
							 | 
						||
| 
								 | 
							
								import cpp.vm.Lock;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								private typedef ThreadInfos = {
							 | 
						||
| 
								 | 
							
									var id:Int;
							 | 
						||
| 
								 | 
							
									var t:Thread;
							 | 
						||
| 
								 | 
							
									var p:Poll;
							 | 
						||
| 
								 | 
							
									var socks:Array<sys.net.Socket>;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								private typedef ClientInfos<Client> = {
							 | 
						||
| 
								 | 
							
									var client:Client;
							 | 
						||
| 
								 | 
							
									var sock:sys.net.Socket;
							 | 
						||
| 
								 | 
							
									var thread:ThreadInfos;
							 | 
						||
| 
								 | 
							
									var buf:haxe.io.Bytes;
							 | 
						||
| 
								 | 
							
									var bufpos:Int;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
									The ThreadServer can be used to easily create a multithreaded server where each thread polls multiple connections.
							 | 
						||
| 
								 | 
							
									To use it, at a minimum you must override or rebind clientConnected, readClientMessage, and clientMessage and you must define your Client and Message.
							 | 
						||
| 
								 | 
							
								**/
							 | 
						||
| 
								 | 
							
								class ThreadServer<Client, Message> {
							 | 
						||
| 
								 | 
							
									var threads:Array<ThreadInfos>;
							 | 
						||
| 
								 | 
							
									var sock:sys.net.Socket;
							 | 
						||
| 
								 | 
							
									var worker:Thread;
							 | 
						||
| 
								 | 
							
									var timer:Thread;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										Number of total connections the server will accept.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public var listen:Int;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										Number of server threads.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public var nthreads:Int;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										Polling timeout.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public var connectLag:Float;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										Stream to send error messages.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public var errorOutput:haxe.io.Output;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										Space allocated to buffers when they are created.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public var initialBufferSize:Int;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										Maximum size of buffered data read from a socket. An exception is thrown if the buffer exceeds this value.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public var maxBufferSize:Int;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										Minimum message size.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public var messageHeaderSize:Int;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										Time between calls to update.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public var updateTime:Float;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										The most sockets a thread will handle.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public var maxSockPerThread:Int;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										Creates a ThreadServer.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public function new() {
							 | 
						||
| 
								 | 
							
										threads = new Array();
							 | 
						||
| 
								 | 
							
										nthreads = if (Sys.systemName() == "Windows") 150 else 10;
							 | 
						||
| 
								 | 
							
										messageHeaderSize = 1;
							 | 
						||
| 
								 | 
							
										listen = 10;
							 | 
						||
| 
								 | 
							
										connectLag = 0.5;
							 | 
						||
| 
								 | 
							
										errorOutput = Sys.stderr();
							 | 
						||
| 
								 | 
							
										initialBufferSize = (1 << 10);
							 | 
						||
| 
								 | 
							
										maxBufferSize = (1 << 16);
							 | 
						||
| 
								 | 
							
										maxSockPerThread = 64;
							 | 
						||
| 
								 | 
							
										updateTime = 1;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function runThread(t) {
							 | 
						||
| 
								 | 
							
										while (true) {
							 | 
						||
| 
								 | 
							
											try {
							 | 
						||
| 
								 | 
							
												loopThread(t);
							 | 
						||
| 
								 | 
							
											} catch (e:Dynamic) {
							 | 
						||
| 
								 | 
							
												logError(e);
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function readClientData(c:ClientInfos<Client>) {
							 | 
						||
| 
								 | 
							
										var available = c.buf.length - c.bufpos;
							 | 
						||
| 
								 | 
							
										if (available == 0) {
							 | 
						||
| 
								 | 
							
											var newsize = c.buf.length * 2;
							 | 
						||
| 
								 | 
							
											if (newsize > maxBufferSize) {
							 | 
						||
| 
								 | 
							
												newsize = maxBufferSize;
							 | 
						||
| 
								 | 
							
												if (c.buf.length == maxBufferSize)
							 | 
						||
| 
								 | 
							
													throw "Max buffer size reached";
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											var newbuf = haxe.io.Bytes.alloc(newsize);
							 | 
						||
| 
								 | 
							
											newbuf.blit(0, c.buf, 0, c.bufpos);
							 | 
						||
| 
								 | 
							
											c.buf = newbuf;
							 | 
						||
| 
								 | 
							
											available = newsize - c.bufpos;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										var bytes = c.sock.input.readBytes(c.buf, c.bufpos, available);
							 | 
						||
| 
								 | 
							
										var pos = 0;
							 | 
						||
| 
								 | 
							
										var len = c.bufpos + bytes;
							 | 
						||
| 
								 | 
							
										while (len >= messageHeaderSize) {
							 | 
						||
| 
								 | 
							
											var m = readClientMessage(c.client, c.buf, pos, len);
							 | 
						||
| 
								 | 
							
											if (m == null)
							 | 
						||
| 
								 | 
							
												break;
							 | 
						||
| 
								 | 
							
											pos += m.bytes;
							 | 
						||
| 
								 | 
							
											len -= m.bytes;
							 | 
						||
| 
								 | 
							
											work(clientMessage.bind(c.client, m.msg));
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										if (pos > 0)
							 | 
						||
| 
								 | 
							
											c.buf.blit(0, c.buf, pos, len);
							 | 
						||
| 
								 | 
							
										c.bufpos = len;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function loopThread(t:ThreadInfos) {
							 | 
						||
| 
								 | 
							
										if (t.socks.length > 0)
							 | 
						||
| 
								 | 
							
											for (s in t.p.poll(t.socks, connectLag)) {
							 | 
						||
| 
								 | 
							
												var infos:ClientInfos<Client> = s.custom;
							 | 
						||
| 
								 | 
							
												try {
							 | 
						||
| 
								 | 
							
													readClientData(infos);
							 | 
						||
| 
								 | 
							
												} catch (e:Dynamic) {
							 | 
						||
| 
								 | 
							
													t.socks.remove(s);
							 | 
						||
| 
								 | 
							
													if (!Std.isOfType(e, haxe.io.Eof) && !Std.isOfType(e, haxe.io.Error))
							 | 
						||
| 
								 | 
							
														logError(e);
							 | 
						||
| 
								 | 
							
													work(doClientDisconnected.bind(s, infos.client));
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										while (true) {
							 | 
						||
| 
								 | 
							
											var m:{s:sys.net.Socket, cnx:Bool} = Thread.readMessage(t.socks.length == 0);
							 | 
						||
| 
								 | 
							
											if (m == null)
							 | 
						||
| 
								 | 
							
												break;
							 | 
						||
| 
								 | 
							
											if (m.cnx)
							 | 
						||
| 
								 | 
							
												t.socks.push(m.s);
							 | 
						||
| 
								 | 
							
											else if (t.socks.remove(m.s)) {
							 | 
						||
| 
								 | 
							
												var infos:ClientInfos<Client> = m.s.custom;
							 | 
						||
| 
								 | 
							
												work(doClientDisconnected.bind(m.s, infos.client));
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function doClientDisconnected(s:sys.net.Socket, c) {
							 | 
						||
| 
								 | 
							
										try
							 | 
						||
| 
								 | 
							
											s.close()
							 | 
						||
| 
								 | 
							
										catch (e:Dynamic) {};
							 | 
						||
| 
								 | 
							
										clientDisconnected(c);
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function runWorker() {
							 | 
						||
| 
								 | 
							
										while (true) {
							 | 
						||
| 
								 | 
							
											var f = Thread.readMessage(true);
							 | 
						||
| 
								 | 
							
											try {
							 | 
						||
| 
								 | 
							
												f();
							 | 
						||
| 
								 | 
							
											} catch (e:Dynamic) {
							 | 
						||
| 
								 | 
							
												logError(e);
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											try {
							 | 
						||
| 
								 | 
							
												afterEvent();
							 | 
						||
| 
								 | 
							
											} catch (e:Dynamic) {
							 | 
						||
| 
								 | 
							
												logError(e);
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										Internally used to delegate something to the worker thread.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public function work(f:Void->Void) {
							 | 
						||
| 
								 | 
							
										worker.sendMessage(f);
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function logError(e:Dynamic) {
							 | 
						||
| 
								 | 
							
										var stack = haxe.CallStack.exceptionStack();
							 | 
						||
| 
								 | 
							
										if (Thread.current() == worker)
							 | 
						||
| 
								 | 
							
											onError(e, stack);
							 | 
						||
| 
								 | 
							
										else
							 | 
						||
| 
								 | 
							
											work(onError.bind(e, stack));
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function addClient(sock:sys.net.Socket) {
							 | 
						||
| 
								 | 
							
										var start = Std.random(nthreads);
							 | 
						||
| 
								 | 
							
										for (i in 0...nthreads) {
							 | 
						||
| 
								 | 
							
											var t = threads[(start + i) % nthreads];
							 | 
						||
| 
								 | 
							
											if (t.socks.length < maxSockPerThread) {
							 | 
						||
| 
								 | 
							
												var infos:ClientInfos<Client> = {
							 | 
						||
| 
								 | 
							
													thread: t,
							 | 
						||
| 
								 | 
							
													client: clientConnected(sock),
							 | 
						||
| 
								 | 
							
													sock: sock,
							 | 
						||
| 
								 | 
							
													buf: haxe.io.Bytes.alloc(initialBufferSize),
							 | 
						||
| 
								 | 
							
													bufpos: 0,
							 | 
						||
| 
								 | 
							
												};
							 | 
						||
| 
								 | 
							
												sock.custom = infos;
							 | 
						||
| 
								 | 
							
												infos.thread.t.sendMessage({s: sock, cnx: true});
							 | 
						||
| 
								 | 
							
												return;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										refuseClient(sock);
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function refuseClient(sock:sys.net.Socket) {
							 | 
						||
| 
								 | 
							
										// we have reached maximum number of active clients
							 | 
						||
| 
								 | 
							
										sock.close();
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function runTimer() {
							 | 
						||
| 
								 | 
							
										var l = new Lock();
							 | 
						||
| 
								 | 
							
										while (true) {
							 | 
						||
| 
								 | 
							
											l.wait(updateTime);
							 | 
						||
| 
								 | 
							
											work(update);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function init() {
							 | 
						||
| 
								 | 
							
										worker = Thread.create(runWorker);
							 | 
						||
| 
								 | 
							
										timer = Thread.create(runTimer);
							 | 
						||
| 
								 | 
							
										for (i in 0...nthreads) {
							 | 
						||
| 
								 | 
							
											var t = {
							 | 
						||
| 
								 | 
							
												id: i,
							 | 
						||
| 
								 | 
							
												t: null,
							 | 
						||
| 
								 | 
							
												socks: new Array(),
							 | 
						||
| 
								 | 
							
												p: new Poll(maxSockPerThread),
							 | 
						||
| 
								 | 
							
											};
							 | 
						||
| 
								 | 
							
											threads.push(t);
							 | 
						||
| 
								 | 
							
											t.t = Thread.create(runThread.bind(t));
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										Called when the server gets a new connection.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public function addSocket(s:sys.net.Socket) {
							 | 
						||
| 
								 | 
							
										s.setBlocking(false);
							 | 
						||
| 
								 | 
							
										work(addClient.bind(s));
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										Start the server at the specified host and port.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public function run(host, port) {
							 | 
						||
| 
								 | 
							
										sock = new sys.net.Socket();
							 | 
						||
| 
								 | 
							
										sock.bind(new sys.net.Host(host), port);
							 | 
						||
| 
								 | 
							
										sock.listen(listen);
							 | 
						||
| 
								 | 
							
										init();
							 | 
						||
| 
								 | 
							
										while (true) {
							 | 
						||
| 
								 | 
							
											try {
							 | 
						||
| 
								 | 
							
												addSocket(sock.accept());
							 | 
						||
| 
								 | 
							
											} catch (e:Dynamic) {
							 | 
						||
| 
								 | 
							
												logError(e);
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										Send data to a client.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public function sendData(s:sys.net.Socket, data:String) {
							 | 
						||
| 
								 | 
							
										try {
							 | 
						||
| 
								 | 
							
											s.write(data);
							 | 
						||
| 
								 | 
							
										} catch (e:Dynamic) {
							 | 
						||
| 
								 | 
							
											stopClient(s);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										Shutdown a client's connection and remove them from the server.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public function stopClient(s:sys.net.Socket) {
							 | 
						||
| 
								 | 
							
										var infos:ClientInfos<Client> = s.custom;
							 | 
						||
| 
								 | 
							
										try
							 | 
						||
| 
								 | 
							
											s.shutdown(true, true)
							 | 
						||
| 
								 | 
							
										catch (e:Dynamic) {};
							 | 
						||
| 
								 | 
							
										infos.thread.t.sendMessage({s: s, cnx: false});
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// --- CUSTOMIZABLE API ---
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										Called when an error has ocurred.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public dynamic function onError(e:Dynamic, stack) {
							 | 
						||
| 
								 | 
							
										var estr = try Std.string(e) catch (e2:Dynamic) "???" + try "[" + Std.string(e2) + "]" catch (e:Dynamic) "";
							 | 
						||
| 
								 | 
							
										errorOutput.writeString(estr + "\n" + haxe.CallStack.toString(stack));
							 | 
						||
| 
								 | 
							
										errorOutput.flush();
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										Called when a client connects. Returns a client object.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public dynamic function clientConnected(s:sys.net.Socket):Client {
							 | 
						||
| 
								 | 
							
										return null;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										Called when a client disconnects or an error forces the connection to close.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public dynamic function clientDisconnected(c:Client) {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										Called when data has been read from a socket. This method should try to extract a message from the buffer.
							 | 
						||
| 
								 | 
							
										The available data resides in buf, starts at pos, and is len bytes wide. Return the new message and the number of bytes read from the buffer.
							 | 
						||
| 
								 | 
							
										If no message could be read, return null.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public dynamic function readClientMessage(c:Client, buf:haxe.io.Bytes, pos:Int, len:Int):{msg:Message, bytes:Int} {
							 | 
						||
| 
								 | 
							
										return {
							 | 
						||
| 
								 | 
							
											msg: null,
							 | 
						||
| 
								 | 
							
											bytes: len,
							 | 
						||
| 
								 | 
							
										};
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										Called when a message has been recieved. Message handling code should go here.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public dynamic function clientMessage(c:Client, msg:Message) {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										This method is called periodically. It can be used to do server maintenance.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public dynamic function update() {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										Called after a client connects, disconnects, a message is received, or an update is performed.
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									public dynamic function afterEvent() {}
							 | 
						||
| 
								 | 
							
								}
							 |