TCPSocket

From Vendetta Lua
Jump to navigationJump to search

Introductional post by a1k0n in the forum:

http://vendetta-online.com/x/msgboard/16/17247

As I mentioned before, we can't use luasockets because of event loop issues, so we exposed our own internal TCP sockets class without ever telling you even what it was called.

The class is TCPSocket.

Construction:

local sock = TCPSocket()

Methods:

- success, error = sock:Connect(host, port): establish a TCP connection to host:port. Returns 1,nil if successful, nil,"error message" otherwise. This is a *non-blocking* connect, which means it always returns immediately, whether or not the connection succeeded. The write callback (described below) will be called when the connect completes (whether it succeeds or fails).

- success, error = sock:Listen(port): listen on TCP port. Newly established connections will trigger a connection event, which we will get to shortly.

- newconn = sock:Accept(): return new TCPSocket object for the established connection.

- sock:SetConnectHandler(fn): set a callback which occurs when someone connects to a socket that is Listen()ing. You must Accept to get the new connection. Not used for outgoing connections.

- sock:SetReadHandler(fn): callback when data is available for reading. Disables callback if fn is nil.

- sock:SetWriteHandler(fn): callback when output buffer space is available for writing, or when the connection completes. Disables callback if fn is nil.

- nsent = sock:Send(string): send string to socket. Socket is in non-blocking mode (otherwise your whole client would lock up trying to send long strings) so will not always send entire string. nsent is the number of bytes sent.

- msg, errcode = sock:Recv(): get all available data. If there is a socket error, msg will be nil and errcode will contain the standard errno error code.

- errmsg = sock:GetSocketError(): if there is an error flagged on the socket, return the error message string. This is slightly wonky under OS X and Linux because it uses getsockopt(SO_ERROR) which isn't quite the same as errno, where errors usually go. Mostly useful for getting the status of a Connect() after the resulting write callback.

- hostname = sock:GetPeerName(): return string containing the IP and port of the remote end of the socket.

Now, this is somewhat confusing stuff. You can't just write regular straight-line send-this-recv-that socket code and expect it to work. Which is why I have a couple layers of wrappers we use, and you can use.

The first layer (tcpsock.lua) sets up an asynchronous buffered line protocol. You get a callback when someone sends a line to you, and you write lines (in response or whenever you want) and it takes care of input and output buffering.

The next layer (client.lua & server.lua) builds a coroutine-based RPC environment using JSON as the transport. It creates an object with a metatable that allows you to say RPCobject:anymethod("any", {arguments=true}, 23894) -- this will send a JSON-formatted request for 'anymethod' to the remote end, block the current coroutine until it gets a matching response from the other end, and then return it to the caller.

I built a database connection layer (dbclient.lua & dbserver.lua) using luasql at the server end using this. You can use it as an example; it won't work out of the box.

Code is available at http://a1k0n.net/vendetta/lua/tcpstuff/