Forums » Community Projects

Using the TCP socket support

Jul 23, 2007 a1k0n link
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/

UPDATE UPDATE: seems that the OS X and ia32 linux versions are missing this stuff; amd64 linux and windows have it. Not sure what happened there.
Jul 24, 2007 Demonen link
Yaaay!
Any idea when the ia32 Linux and Mac will added to the list of TCP-enabled clients?

** Zathras does the TCP dance **
Jul 24, 2007 a1k0n link
UPDATE UPDATE UPDATE: ok, 1.7.35.2 released with corrected lunix and OS X executables.
Jul 24, 2007 Demonen link
YAAY! *does the Vendetta Fanboi dance*
Jul 24, 2007 a1k0n link
pcall() and xpcall() are apparently not exported either. Grrrrrrr. I'm exporting those for the next version because they are important. If your TCPSocket callback function has an error, it will bomb the whole client. pcall and xpcall are used to catch those errors.

For now you can do this:

declare('pcall')
declare('xpcall')
function pcall(f,...) return true,f(unpack(arg)) end
function xpcall(f,e) return f() end

also, you'll need to add declare('TCP') etc to the top of my example code. Sorry about that. Also, I corrected some errors above. The write callback is called after an outgoing connection completes; the connection callback is only for incoming connections.
Jul 24, 2007 firsm link
Thanks a1kon for your this great stuff, tcpsock.lua makes it real easy to write some nice stuff. For everybody who's interested, here's some example code I wrote based upon a1k0n's tcpsock.lua:

EchoServer - Runs an echo server in VO (returns everything you give to it):
http://vokb.svn.sourceforge.net/viewvc/vokb/vokb/client/trunk/echoserver.lua?view=markup
tcpsock.lua needs to be in the same directory, but it DOESN'T need to be modified.

All you have to do is:
/consoletoggle
/lua dofile('echoserver.lua')
/lua EchoServer.Start()

and then use your telnet client to connect to localhost on port 9999 and send something to it, then you'll get it back - extremely useful :-)

EDIT: Well, here's a second example, but this one is actually really useful:
http://vokb.svn.sourceforge.net/viewvc/vokb/vokb/client/trunk/http.lua?view=markup
The samples are now included in the file. GET works nicely, POST does too, except that you sometimes don't get anything back.

Aug 04, 2007 samuel.penn link
Thanks firsm. I started writing my own http layer this morning until I realised that you'd already done it. It's working fine for sending large amounts of POST data to Tomcat.
Aug 28, 2007 a1k0n link
Apparently the listening socket (server) code doesn't work under Windows and maybe other platforms due to a dumb stubbed-out C++ server I have that isn't properly overridden sometimes. Next version will make listening sockets work.
Sep 26, 2007 Scuba Steve 9.0 link
a1k0n, it appears that the listening socket code segfaults under Windows whenever a client connects :/

Or at least under Windows.

Nevermind that I'm mixing segfaults and XP.
Sep 27, 2007 raybondo link
Can you post/email me some code that exhibits this problem?

It looks like you called GetPeerName() on an TCP object that wasn't connected.

I'll fix it so it doesn't crash, but it will return nil instead.
Sep 27, 2007 csgno1 link
Regarding http://vokb.svn.sourceforge.net/viewvc/vokb/vokb/client/trunk/http.lua?view=markup

firsm, when I run your example code I get an error telling me that TCP is nil. I looked in the volb code and tried a bunch of things and I've come to the conclusion that my lua knowledge is not yet advanced enough to help me produce the proper line of code to allow it to work for me. Can you nudge me in the right direction please?
Sep 28, 2007 MSKanaka link
Make sure to convert the code for Lua 5.1; when firsm was working with that VO still used 5.0. (This is part of the reason the ingame VOKB client isn't working right now.)
Sep 28, 2007 raybondo link
Someone should fix that.
Sep 28, 2007 MSKanaka link
Sep 28, 2007 csgno1 link
Thanks but I still get the same error message. Does it rely on another plugin that I missed? I have tcpsock.lua in the same directory. (has tcpsock.lua been updated?)

205: attempt to index global 'TCP' (a nil value)
Sep 29, 2007 MSKanaka link
Try putting the rest of the TCPsock files that a1k0n links to in the folder if they're not there already; failing that, I honestly have no idea--I haven't worked with TCPsock yet, and only converted firsm's file to use Lua 5.1--Scuba might have a better idea.
Sep 29, 2007 csgno1 link
I tried firsm's echoserver example which works fine, so I guess sockets work on my box in general.
Sep 30, 2007 nh link
http.lua isn't loading tcpsock.lua. You will have to do that, in addition to declaring the global "TCP".
Oct 01, 2007 csgno1 link
Thank you nh, that was exactly the solution.