0

I'm trying to create a simple network RPC server/client thing in emacs lisp.
So far I have the following (somewhat minimalist) code:

(defun handle-connection (proc msg)
  (message (format "connection received payload: %s" msg))
  (process-send-string proc "hello, nice to meet you"))

(defun handle-filter (proc msg)
  (message (format "got %s from %s" msg proc)))

(defun handle-filter-surfer (proc msg)
  (message (format "got %s from %s" msg proc)))

(defun server-sentinel (proc msg)
  (cond ((string-match "^open" msg) (handle-connection proc msg))
    (t (message "something went wrong"))))

(setq surfer (make-network-process :name "surfer" :server 5 :host 'local
                   :buffer "surfer" :filter 'handle-filter-surfer
                   :service 1337 :sentinel 'server-sentinel))

(setq client (make-network-process :name "client" :buffer "client"
                   :remote [127 0 0 1 1337]))

All these calls seem to work fine.
My problem is the following: I now want to send a message from the client to the server. The documentation only says that to send data to a process, process-send-string should be used. So naturally I tried to (process-send-string surfer "asdf") which resulted in:

Debugger entered--Lisp error: (error "Process surfer not running: listen")
  process-send-string(#<process surfer> "asdf")
  eval((process-send-string surfer "asdf") nil)

Am I doing it wrong? How DO I send a message from the connected client to the server? When connecting client to server, handle-connection is called and the "hello" string pops up in the clients buffer, so the other way around seems to work. I guess I need to be connected for it to work, but how do I tell emacs that it should use the connected client to send the message to the server process?

1 Answer 1

1
(process-send-string client "asdf")
;; => got asdf from surfer <127.0.0.1:1337>

My understanding is that the 'client' acts as a socket, which allows you to interact with the server.

handle-filter is not used, btw.


EDIT:

Here are some key points about Emacs network communication (according to the Elisp reference manual):

  • A client is not a process but rather "a connection". You cannot kill it or send it signals. The delete-process function closes a connection (so it is, in fact, similar to a socket).
  • A server is a process, but you cannot communicate with it directly. When it receives a connection request, it creates a new network connection to represent the connection just made.
  • A server itself does not use filters and sentinels; these are inherited by the new connection made when a client connects to the server.
  • Unlike normal sockets, a connection object can be used to send a string (via process-send-string) but not to receive it. Instead, an input string is sent to a filter; the default filter sends it to a specified buffer or discards it if no such buffer was specified (or :buffer nil was passed to make-network-process).

For simplicity, the attached code assumes only one client and one server.

(defconst net-port        35678)
(defconst max-connections 1)

(defvar my-surfer         nil) ;; the listening server process
(defvar my-client->server nil) ;; a connection
(defvar my-server->client nil) ;; another connection

;;;; Server-side functions

(defun handle-connection (proc _)
  (message (format " -> handle-connection"))
  (setq my-server->client proc)) ;; set this so 'test-main' can continue

;; Messages from the client are passed here
(defun surfer-filter (proc msg)
  (message "[surfer-filter] %S received %S" proc msg))

(defun surfer-sentinel (proc msg)
  ;; (message "[surfer sentinel] proc %S  msg %s" proc msg)
  (cond ((string-prefix-p "open" msg)
         (handle-connection proc msg))
        ((string-prefix-p "deleted" msg)
         (message "-> server connection deleted"))
        (t (message "something went wrong"))))

;;;; Client-side

;; Messages from the server are sent here
(defun client-filter (_ msg)
  (message "[client-filter] received %S" msg))

;;;;

(defmacro my-delete-process(var-name)
  `(when ,var-name
     (delete-process ,var-name)
     (setq ,var-name nil)))

(defun test-main()
  (interactive)

  (with-current-buffer "*Messages*"
    (let ((inhibit-read-only t))
      (erase-buffer)))

  ;; Clean-up after the previous run
  (my-delete-process my-client->server)
  (my-delete-process my-surfer)
  (my-delete-process my-server->client)

  ;; Messages from the client are passed to 'surfer-filter'.
  ;; Since we have a filter, we can manage without a buffer.
  (setq my-surfer (make-network-process
                   :name "surfer" :server max-connections
                   :host 'local
                   ;; :buffer "surfer"
                   :filter 'surfer-filter
                   :service net-port :sentinel 'surfer-sentinel))

  ;; Messages from the server are passed to 'client-filter'
  (setq my-client->server
        (make-network-process
         :name "client" ;; :buffer "client"
         :filter 'client-filter
         :remote (vector 127 0 0 1 net-port)))

  (message "waiting for server connection...")
  (let ((i 0))
    ;; Wait for 2 seconds max
    (while (and (< i 20)
                (not my-server->client))
      (cl-incf i)
      (sleep-for 0.1)))
  (cl-assert my-server->client)

  (process-send-string my-client->server
                       "a message from the client")
  (process-send-string my-server->client
                       "a message from the server"))

Possible output:

waiting for server connection...
 -> handle-connection
[client-filter] received "a message from the server"
[surfer-filter] #<process surfer <127.0.0.1:45054>> received "a message from the client"
4
  • I forgot to include the line (set-process-filter client 'handle-filter). Your suggestion does work, but my question was how to send data from the client to the server. As one of my questions also was "am I doing something wrong", I tend to accept the answer, because I now image Emacs only ever running as the server process in such a scenario. The client would then connect to the Emacs instance. Am I now thinking into the right direction?
    – L0ren2
    Commented Jul 14 at 15:11
  • Emacs can function as a client and a server simultaneously. A server runs in a subprocess. I'll post more details a bit later.
    – BadEnglish
    Commented Jul 16 at 10:16
  • Thank you so much. The example code really helped. I wrote a C++ script to connect to the elisp server running in emacs and that worked too :)
    – L0ren2
    Commented Jul 16 at 17:01
  • I'm glad I could help ;)
    – BadEnglish
    Commented Jul 16 at 17:57

Not the answer you're looking for? Browse other questions tagged or ask your own question.