2024-07-22

IPC via Unix domain sockets in Common Lisp

IPC via Unix domain sockets in Common Lisp

By André A. Gomes

Netcat

The goal is to demonstrate how to replicate the behavior of netcat.

From a shell process, start the server.

From another shell process, start the client, which takes data from standard input and outputs to the standard output of the server's process.

To stop the communication, kill the client.

To monitor network connections, run netstat -xa | grep /tmp/foo.socket (valid for all sections).

Common Lisp

IOlib

From a REPL, start the server.

(asdf:load-system :iolib)

(defun start-server (&optional (path (uiop:native-namestring "/tmp/foo.socket")))
  "Listen on PATH and print to `*standard-output*'."
  (unwind-protect
       (iolib:with-open-socket (s :address-family :local
                                  :connect :passive
                                  :local-filename path)
         (iolib:with-accept-connection (client s)
           (handler-case (loop (write-line (read-line client)))
             (end-of-file () (format t "Client closed connection!~%")))))
    (uiop:delete-file-if-exists path)))

(start-server)

From another REPL, start the client. Interrupt when done.

(asdf:load-system :iolib)

(defun client (&optional (path (uiop:native-namestring "/tmp/foo.socket")))
  "Connect to server socket at PATH and send data from `*standard-input*'."
  (iolib:with-open-socket (server :address-family :local
                                  :remote-filename path)
    (loop (write-line (read-line) server)
          (finish-output server))))

(client)

SBCL's sb-bsd-sockets module

From a REPL, start the server.

(defun start-server (&optional (path (uiop:native-namestring "/tmp/foo.socket")))
  "Listen on PATH and print to `*standard-output*'."
  (let ((socket (make-instance 'sb-bsd-sockets:local-socket :type :stream)))
    (sb-bsd-sockets:socket-bind socket path)
    (sb-bsd-sockets:socket-listen socket 100)
    (unwind-protect
         ;; Note that initializing the stream as below would fail since
         ;; `sb-bsd-sockets:socket-accept' returns a connected socket.
         ;; (sb-bsd-sockets:socket-make-stream socket :input t)
         (with-open-stream (client (sb-bsd-sockets:socket-make-stream
                                    (sb-bsd-sockets:socket-accept socket) :input t))
           (handler-case (loop (write-line (read-line client)))
             (end-of-file () (format t "Client closed connection!~%"))))
      (uiop:delete-file-if-exists path))))

(start-server)

From another REPL, start the client. Interrupt when done.

(defun client (&optional (path (uiop:native-namestring "/tmp/foo.socket")))
  "Connect to server socket at PATH and send data from `*standard-input*'."
  (let ((socket (make-instance 'sb-bsd-sockets:local-socket :type :stream)))
    (with-open-stream (server (sb-bsd-sockets:socket-make-stream socket :output t))
      (sb-bsd-sockets:socket-connect socket path)
      (loop (write-line (read-line) server)
            (finish-output server)))))

(client)

Did you enjoy this article? Register for our newsletter to receive the latest hacker news from the world of Lisp and browsers!