Search code examples
networkingtcpprocessemacs

elisp process-send-string giving unexpected error "process not running: listen"


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?


Solution

  • (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"