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?
(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.
Here are some key points about Emacs network communication (according to the Elisp reference manual):
delete-process
function closes a connection (so it is, in fact, similar to a socket).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"