Nicolas Cannasse 19 tahun lalu
induk
melakukan
1dab305b9a
1 mengubah file dengan 233 tambahan dan 0 penghapusan
  1. 233 0
      std/neko/net/ServerLoop.hx

+ 233 - 0
std/neko/net/ServerLoop.hx

@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) 2005, The haXe Project Contributors
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   - Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+package neko.net;
+
+private typedef ServerClient<ClientData> = {
+	var sock : Socket;
+	var buffer : String;
+	var bufbytes : Int;
+	var data : ClientData;
+}
+
+/**
+	This class enables you to quickly create a custom server that can
+	serve several clients in parallel. This server is using a single
+	thread and process so the server itself processing is not parallel.
+	Non-blocking sockets are used to ensure that a slow client does not
+	block the others.
+**/
+class ServerLoop<ClientData> {
+
+	/**
+		Each client has an associated buffer. This is the initial buffer size which
+		is set to 128 bytes by default.
+	**/
+	public static var DEFAULT_BUFSIZE = 128;
+
+	/**
+		Each client has an associated buffer. This is the maximum buffer size which
+		is set to 64K by default. When that size is reached and some data can't be processed,
+		the client is disconnected.
+	**/
+	public static var MAX_BUFSIZE = (1 << 16);
+
+	/**
+		This is the value of number client requests that the server socket
+		listen for. By default this number is 10 but can be increased for
+		servers supporting a large number of simultaneous requests.
+	**/
+	public var listenCount : Int;
+
+	/**
+		See [update].
+	**/
+	public var updateTime : Float;
+
+	var newData : Socket -> ClientData;
+	var socks : Array<Socket>;
+	public var clients : List<ClientData>;
+
+	/**
+		Creates a server instance. The [newData] methods must return
+		the data associated with the Client.
+	**/
+	public function new( ?newData ) {
+		this.newData = if( newData == null ) function(_) { return null; } else newData;
+		clients = new List();
+		socks = new Array();
+		listenCount = 10;
+		updateTime = 1;
+	}
+
+	/**
+		Closes the client connection and removes it from the client List.
+	**/
+	public function closeConnection( s : Socket ) : Bool {
+		var cl : ServerClient<ClientData> = untyped s.__client;
+		if( cl == null || !clients.remove(cl.data) )
+			return false;
+		socks.remove(s);
+		try s.close() catch( e : Dynamic ) { };
+		clientDisconnected(cl.data);
+		return true;
+	}
+
+	/**
+		The [update] method is called after each socket event has been
+		processed or when [updateTime] has been reached. It can be used
+		to perform time-regular tasks such as pings. By default [updateTime]
+		is set to one second.
+	**/
+	public function update() {
+	}
+
+	/**
+		This method is called after a client has been disconnected.
+	**/
+	public function clientDisconnected( d : ClientData ) {
+	}
+
+	/**
+		This method can be used instead of writing directly to the socket.
+		It ensures that all the data is correctly sent. If an error occurs
+		while sending the data, no exception will occur but the client will
+		be gracefully disconnected.
+	**/
+	public function clientWrite( s : Socket, buf : String, pos : Int, len : Int ) {
+		try {
+			while( len > 0 ) {
+				var nbytes = s.output.writeBytes(buf,pos,len);
+				pos += nbytes;
+				len -= nbytes;
+			}
+		} catch( e : Dynamic ) {
+			closeConnection(s);
+		}
+	}
+
+	/**
+		This method is called when some data has been readed into a Client buffer.
+		If the data can be handled, then you can return the number of bytes handled
+		that needs to be removed from the buffer. It the data can't be handled (some
+		part of the message is missing for example), returns 0.
+	**/
+	public function processClientData( d : ClientData, buf : String, bufpos : Int, buflen : Int ) {
+		throw "ServerLoop::processClientData is not implemented";
+		return 0;
+	}
+
+	/**
+		Called when an error occured. This enable you to log the error somewhere.
+		By default the error is displayed using [trace].
+	**/
+	public function onError( e : Dynamic ) {
+		trace(Std.string(e)+"\n"+haxe.Stack.toString(haxe.Stack.exceptionStack()));
+	}
+
+	function readData( cl : ServerClient<ClientData> ) {
+		var buflen = cl.buffer.length;
+		// eventually double the buffer size
+		if( cl.bufbytes == buflen ) {
+			var nsize = buflen * 2;
+			if( nsize > MAX_BUFSIZE ) {
+				if( buflen == MAX_BUFSIZE )
+					throw "Max buffer size reached";
+				nsize = MAX_BUFSIZE;
+			}
+			var buf2 = neko.Lib.makeString(nsize);
+			neko.Lib.copyBytes(buf2,0,cl.buffer,0,buflen);
+			buflen = nsize;
+			cl.buffer = buf2;
+		}
+		// read the available data
+		var nbytes = cl.sock.input.readBytes(cl.buffer,cl.bufbytes,buflen - cl.bufbytes);
+		cl.bufbytes += nbytes;
+	}
+
+	function processData( cl : ServerClient<ClientData> ) {
+		var pos = 0;
+		while( cl.bufbytes > 0 ) {
+			var nbytes = processClientData(cl.data,cl.buffer,pos,cl.bufbytes);
+			if( nbytes == 0 )
+				break;
+			pos += nbytes;
+			cl.bufbytes -= nbytes;
+		}
+		if( pos > 0 )
+			neko.Lib.copyBytes(cl.buffer,0,cl.buffer,pos,cl.bufbytes);
+	}
+
+	/**
+		Run the server. This function should never return.
+	**/
+	public function run( host : Host, port : Int ) {
+		var serv = new Socket();
+		serv.bind(host,port);
+		serv.listen(listenCount);
+		socks = [serv];
+		while( true ) {
+			for( s in Socket.select(socks,null,null,updateTime).read ) {
+				var cl : ServerClient<ClientData> = untyped s.__client;
+				if( cl == null ) {
+					// no associated client : it's our server socket
+					var sock = serv.accept();
+					sock.setBlocking(true);
+					cl = {
+						sock : sock,
+						data : null,
+						buffer : neko.Lib.makeString(DEFAULT_BUFSIZE),
+						bufbytes : 0,
+					};
+					// bind the client
+					untyped sock.__client = cl;
+					// creates the data
+					try {
+						cl.data = newData(sock);
+					} catch( e : Dynamic ) {
+						onError(e);
+						try sock.close() catch( e : Dynamic ) { };
+						continue;
+					}
+					// adds the client to the lists
+					socks.push(sock);
+					clients.add(cl.data);
+				} else {
+					// read & process the data
+					try {
+						readData(cl);
+						processData(cl);
+					} catch( e : Dynamic ) {
+						if( !Std.is(e,neko.io.Eof) )
+							onError(e);
+						closeConnection(cl.sock);
+					}
+				}
+			}
+			update();
+		}
+		serv.close();
+	}
+
+}