sigildocs

Socket

TCP, UDP, and Unix domain socket operations for network programming.
(import (sigil socket))

TCP Client

;; Connect to a TCP server (returns socket or #f)
(define sock (tcp-connect "example.com" 80))

;; Send an HTTP request
(socket-write-line sock "GET / HTTP/1.0")
(socket-write-line sock "")

;; Read the full response
(display (socket-read-all sock))
(socket-close sock)

call-with-tcp-connection ensures the socket is closed when the procedure returns:

(call-with-tcp-connection "example.com" 80
  (lambda (sock)
    (socket-write-line sock "GET / HTTP/1.0")
    (socket-write-line sock "")
    (socket-read-all sock)))

TCP Server

;; Listen on a port (0 = OS-assigned)
(define server (tcp-listen 8080))

;; Accept loop
(let loop ()
  (let ((client (tcp-accept server)))
    (socket-write-line client "Hello!")
    (socket-close client)
    (loop)))

tcp-listen accepts an optional backlog argument (default 5). Pass port 0 for an OS-assigned port, then inspect it with socket-local-address.

call-with-tcp-server wraps the listen/close lifecycle:

(call-with-tcp-server 0
  (lambda (server)
    (let ((client (tcp-accept server)))
      (socket-write-line client "Welcome")
      (socket-close client))))

Unix Domain Sockets

Connect to a Unix domain socket path. Not available on Windows.

(define sock (unix-connect "/var/run/app.sock"))
(socket-write-line sock "STATUS")
(display (socket-read-line sock))
(socket-close sock)

Reading and Writing

socket-read

Read up to max-bytes (default 4096) from a socket. Returns a string, empty string if no data is available in non-blocking mode, or eof-object when the connection is closed.

(socket-read sock)        ; read up to 4096 bytes
(socket-read sock 1024)   ; read up to 1024 bytes

socket-read-line

Read a single line (up to the next newline character).

(socket-read-line sock)   ; => "HTTP/1.0 200 OK"

socket-write

Write a string or bytevector. Returns the number of bytes written, or #f on error.

(socket-write sock "Hello")       ; => 5
(socket-write sock #u8(1 2 3))    ; => 3

socket-write-line

Write a string followed by CRLF (\r\n).

(socket-write-line sock "GET / HTTP/1.0")

socket-read-all

Read all data until EOF, returning the accumulated string.

(define response (socket-read-all sock))

socket-send-all

Send all data, retrying on partial writes. Returns #t on success.

(socket-send-all sock large-string)  ; => #t

make-line-reader

Create a buffered line reader that handles fragmented input and CRLF line endings. Returns a procedure that yields one complete line per call.

(define reader (make-line-reader sock))
(reader)  ; => "first line"
(reader)  ; => "second line"
(reader)  ; => eof-object (connection closed)

UDP

;; Create and bind a UDP socket
(define sock (udp-socket))
(udp-bind sock 5000)

;; Send a datagram (sock data host port)
(udp-send sock "ping" "127.0.0.1" 5000)

;; Receive a datagram — returns (data host port)
(let ((result (udp-receive sock)))
  (display (car result)))  ; => "ping"

(socket-close sock)

udp-bind accepts an optional host argument: (udp-bind sock port host).

udp-receive accepts an optional max-bytes argument (default 65535).

Non-blocking I/O

;; Enable non-blocking mode
(socket-set-non-blocking! sock #t)
(socket-non-blocking? sock)  ; => #t

;; Check if data is available without blocking
(socket-ready? sock)  ; => #t or #f

socket-select

Wait for activity on multiple sockets. Returns (readable writable) — two lists of ready sockets.

(let* ((result (socket-select (list sock1 sock2) '() 1000))
       (readable (car result))
       (writable (cadr result)))
  (for-each handle-data readable))

The third argument is a timeout in milliseconds.

Socket Info

;; Type predicate
(socket? sock)        ; => #t
(socket-closed? sock) ; => #f

;; Local address — returns (host port)
(socket-local-address sock)   ; => ("0.0.0.0" 8080)

;; Remote address — returns (host port)
(socket-remote-address sock)  ; => ("127.0.0.1" 54321)

;; DNS resolution — returns list of IP strings
(resolve-hostname "example.com")  ; => ("93.184.216.34" ...)

;; Local machine hostname
(gethostname)  ; => "my-computer"

Options

Disable Nagle's algorithm for lower latency on interactive protocols:

(socket-set-tcp-nodelay! sock #t)

Common Patterns

TCP Echo Server

(import (sigil socket))

(define (echo-server port)
  (call-with-tcp-server port
    (lambda (server)
      (let loop ()
        (let* ((client (tcp-accept server))
               (reader (make-line-reader client)))
          (let read-loop ()
            (let ((line (reader)))
              (cond
                ((eof-object? line) (socket-close client))
                (else (socket-write-line client line)
                      (read-loop)))))
          (loop))))))

Line-based Protocol Client

(import (sigil socket))

(call-with-tcp-connection "localhost" 6379
  (lambda (sock)
    (socket-write-line sock "PING")
    (let ((reader (make-line-reader sock)))
      (display (reader)))))  ; => "+PONG"

UDP Messaging

(import (sigil socket))

;; Sender
(let ((sock (udp-socket)))
  (udp-send sock "hello" "127.0.0.1" 9000)
  (socket-close sock))

;; Receiver
(let ((sock (udp-socket)))
  (udp-bind sock 9000)
  (let ((msg (udp-receive sock)))
    (display (car msg))      ; data
    (display (cadr msg))     ; sender host
    (display (caddr msg)))   ; sender port
  (socket-close sock))