Я пытаюсь создать простой сетевой RPC-сервер/клиент в emacs lisp.
На данный момент у меня есть следующий (несколько минималистичный) код:
(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]))
Кажется, все эти вызовы работают нормально.
Моя проблема в следующем: теперь я хочу отправить сообщение от клиента на сервер. В документации только сказано, что для отправки данных в процесс следует использовать process-send-string
. Поэтому, естественно, я попытался (process-send-string surfer "asdf")
, в результате чего:
Debugger entered--Lisp error: (error "Process surfer not running: listen")
process-send-string(#<process surfer> "asdf")
eval((process-send-string surfer "asdf") nil)
Я делаю это неправильно? Как отправить сообщение от подключенного клиента на сервер? При подключении клиента к серверу вызывается handle-connection
, и в буфере клиента появляется строка «привет», поэтому, похоже, работает наоборот. Я думаю, мне нужно подключение, чтобы это работало, но как мне сказать emacs, что он должен использовать подключенный клиент для отправки сообщения серверному процессу?
(process-send-string client "asdf")
;; => got asdf from surfer <127.0.0.1:1337>
Насколько я понимаю, «клиент» действует как сокет, который позволяет вам взаимодействовать с сервером.
handle-filter
, кстати, не используется.
Вот некоторые ключевые моменты о сетевой связи Emacs (согласно справочному руководству Elisp):
delete-process
закрывает соединение (по сути, это похоже на сокет).process-send-string
), но не для ее получения. Вместо этого входная строка отправляется в фильтр; фильтр по умолчанию отправляет его в указанный буфер или отбрасывает, если такой буфер не был указан (или :buffer nil
был передан в make-network-process
).Для простоты в прилагаемом коде предполагается только один клиент и один сервер.
(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"))
Возможный результат:
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"
Emacs может работать как клиент и сервер одновременно. Сервер работает в подпроцессе. Подробности выложу чуть позже.
Большое спасибо. Пример кода действительно помог. Я написал сценарий C++ для подключения к серверу elisp, работающему в emacs, и это тоже сработало :)
Я рад, что смог помочь ;)
Я забыл добавить строку
(set-process-filter client 'handle-filter)
. Ваше предложение работает, но у меня возник вопрос, как отправить данные с клиента на сервер. Поскольку одним из моих вопросов также был: «Я делаю что-то не так», я склонен принять ответ, потому что теперь я представляю, что Emacs всегда работает только как серверный процесс в таком сценарии. Затем клиент подключится к экземпляру Emacs. Думаю ли я сейчас в правильном направлении?