KeV
KeV

Reputation: 2891

Communicating result of a method invocation over TCP

I was wondering how one could implement a kind of "Remote Method Invocation" (in a very simplistic form). I'm writing a program in an object oriented way. I have objects living on the raspberry pi and objects living on my computer. So sometimes I want to send a message from an object on my computer to an object on the raspberry pi. So far nothing too difficult. But when I want that message to have a result I'm stuck.

For example, how could one implement that an object on my computer is sending the message "add" with the arguments 3 and 4 to the raspberry pi and expecting a result (7) ?

I made a protocol to send messages and arguments to an object over TCP. But if a result is expected the object on my Raspberry Pi will have to answer by sending a new message over TCP. But how can I catch that answer?

I could use/make a blocking operation which sends the message to the Raspberry Pi and waits until a result is returned. But in a simulation program I guess that isn't what you want.

So I was thinking how I could implement this without doing a blocking operation. I came up with this :

I could extend the object that is taking care of the communication (over TCP) with a "message table". In that table I can store for each message the ID, the message itself, the destination object (on the raspberry pi) and the result.

So now if for example I want to send the message "getNumber" (which randomly returns a number), I will call the communication object with the message and the destination. It will fill the table with a unique ID, the message and the destination. At a certain moment in time the destination object will have done it's calculations and return the result. To do so it will send it's answer over TCP to the communication object which will fill the result in the table. From that moment on one can request the result of a message simply by passing the ID. All the communication object will have to do is read the result from his table.

Without communication over TCP/IP it would look like :

(destination-object 'get-number) ; Results in a number, with TCP this could not have a result because the result itself also has to be send over TCP.

With communication like described above :

(define id ((communication-object 'send-message) "get-number" "destination-object"))
(define result ((communication-object 'get-result) id))

Because I never made a program which communicates over TCP/IP I wanted to know if this is a good way of taking care of messages and their results or if there is a better/easier way for doing so.

Knowing I am writing the program in Racket, maybe using call with current continuation is an easier way to implement this (if possible) ? By retaining the "future" (what still has to be done) until the result is known.

Upvotes: 2

Views: 150

Answers (2)

Throw Away Account
Throw Away Account

Reputation: 2671

One possible approach would be to make one connection per request to the Raspberry Pi, and one thread per connection on both ends. So on the PC your send message would be defined something like this:

(define (send-message message-name . args)
     (let-values ((in-port out-port) (tcp-connect rasp-pi-addr port))
           ; Assuming you're sending Lisp values across the network
           (write (cons message-name args) out-port)
           (let ((result (read in-port)))
               (close-input-port in-port)
               (close-output-port out-port)
               result)))

...and then when you want to send a request to the Raspberry Pi, you would do it in a thread:

(thread (λ () (async-channel-put result-channel
                 ((communication-object 'send-message) "get-number" "destination-object"))))

Then, the rest of your program would be free to continue running while this operation happens. You could also do whatever you wanted to do with that result in the thread instead of having the main thread wait on the result:

(thread (λ ()
          (update-opponent-position
              ((communication-object 'send-message) "opponent" "make-move"))))

Depending on what your program does, this might allow you to minimize the extent of the needed redesign.

The thing to watch out for is that Racket's threads are not real CPU threads, so you don't benefit from your processor's multiple cores by using them. But they're great for parallellizing I/O-bound tasks.

Upvotes: 1

Mick
Mick

Reputation: 25491

If you want to leverage other work in this area, you could use a REST API.

This essentially uses the mechanisms of the HTTP protocol, which runs on top of TCP and is the main protocol that is used by browsers to connect with web sites, to provide a web like API's to services on the host. Returning a result is a standard part of this architecture.

It is not as lightweight as a custom protocol on top of TCP, but on the other hand you will be able to leverage all the built in error and edge case handling, and a large user community.

There are quite a few guides available for REST on Raspberry:

Upvotes: 1

Related Questions