Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Connect Node.js as client to a Common Lisp server

I've got small but CPU heavy app in alpha stage in node.js, it's a small game. I'm running into performance issues and I need to speed it up by at least a factor of 20 to get to beta. And since parallel execution would get me very far, I decided that good start would be to share the game map between processes or threads that would perform parallel operations on it. That's pretty impossible to do in node, so I decided to write the meaty parts in CL (SBCL + Linux) and connect to it through unix domain socket.

The plan is:

[players] <-> (node.js front server) <-> (SBCL performing game ticks)

The point is, I need to pass fast messages between node.js and SBCL in a matter similar to socket.io.


Here is what didn't work (you can skip this part)

On Node side, I can't use plain socket.io because it doesn't support Unix Domain Sockets, but net module does, So I can at least do socket.write('raw data') - better than nothing for now.

On CL side, I tried to run woo web server (it supports local sockets) and I could connect to it from node and pass raw data around, but there are all the unnecessary HTTP parts involved and woo is always running as server; it's waiting for GET / HTTP/1.1 ..... I didn't find a way to actually initiate a message from woo first. Also, it's totally undocumented and uncommented and involves lot of FF calls to C libs, which I'm not at all familiar with.

So I went through several more CL web servers that didn't compile, didn't support unix sockets, were abandoned or undocumented, eventually moved to plain sb-bsd-sockets and finally to iolib, but I still can't figure it out.


iolib looked promising, but I can't connect to it from node.

I've got this:

(with-open-socket (socket :address-family :local
                          :type :datagram
                          ;; :connect  :passive
                          :local-filename "/tmp/socket")

  (format t "Socket created")
  ;; (listen-on socket)
  ;; (bind-address socket (make-address "/tmp/socket"))
  (loop
     (let ((msg (receive-from socket :size 20)))
       (format t msg))))    

and I'm getting

#<Syscall "recvfrom" signalled error EWOULDBLOCK(11) "Resource temporarily unavailable" FD=6>
   [Condition of type IOLIB/SYSCALLS:EWOULDBLOCK]

Restarts:
 0: [IGNORE-SYSCALL-ERROR] Ignore this socket condition
 1: [RETRY-SYSCALL] Try to receive data again
 2: [RETRY] Retry SLIME interactive evaluation request.
 3: [*ABORT] Return to SLIME's top level.
 4: [ABORT] abort thread (#<THREAD "worker" RUNNING {10055169A3}>)

I don't know if I should call something like accept-connection or listen-to on that socket first. All I tried resulted in errors. Also, if I [RETRY-SYSCALL] in repl, the error goes away for about 10 seconds but comes back. In this time, node still can't connect.

This seems to get more complicated than I thought. I've already lost ~6 hours of work on iolib alone and I didn't even start on parsing the messages, learning how to create events from them, converting between JSON and s-exps etc..


My questions are:

  • how do i set this connection up in iolib so that node's net can connect?
  • Assuming I can choose, what type of connection would be best suited for passing events/messages? (datagram / stream)
  • Are there some working tools that I didn't try?
  • Also, are there some other libs than iolib that are perhaps more high-level / better documented?
  • Are there any better/easier/faster approaches to this performance / concurrency problem?
  • Any other ideas?

I'm close to just ditching the idea of CL and use something like in-memory mongo with several node processes instead (..it doesn't really sound fast) but I love lisp, it would be great to have things like lparallel on the backend. I just haven't moved an inch since yesterday morning, I just can't figure out the libs. Perhaps I should learn clojure instead.

PS: I wouldn't normally ask for "write me teh code", but if some good soul is around, I would really appreciate it, even in pseudocode.

PPS: Any radically different approaches are also welcome. Please, speak up your mind :)

Thanks for reading!

like image 535
enrey Avatar asked Jun 15 '16 12:06

enrey


1 Answers

If I understand correctly your problem, you need to establish a server in Common Lisp. Let me reuse a previous answer of mine which uses USOCKET:

(use-package :usocket)

(defun some-server (hostname port)    
  ;; create a server which establishes an event-loop
  (socket-server hostname ; a string
                 port     ; a number

                 ;; tcp-handler (see API for details)
                 ;; This function is called each time a client connects,
                 ;; and provides a bidirectional stream to communicate with it.
                 ;; The function executes in a context where some useful special
                 ;; variables are bound.
                 ;; The connection is closed automatically on exit.

                 (lambda (stream)
                   ;; format to stream to client
                   (format stream
                           "~D~%"
                           ;; add all elements of the host,
                           ;; a vector of 4 integers
                           (reduce #'+ *remote-host*)))))
like image 66
coredump Avatar answered Nov 14 '22 23:11

coredump