Skip to content

Clausal Prolog — TCP Module (tcp)

Overview

The tcp module provides predicates for TCP client/server socket operations, wrapping Python's socket module. Socket handles are opaque Python objects — use them with send, receive, and close.

-import_from(py.tcp, [connect, send, receive, close])

echo_client(HOST, PORT, MESSAGE, RESPONSE) <- (
    connect(HOST, PORT, SOCKET),
    send(SOCKET, MESSAGE),
    receive(SOCKET, RESPONSE),
    close(SOCKET)
)

Or via module import:

-import_module(py.tcp)

main <- (
    py.tcp.connect("localhost", 8080, S),
    py.tcp.send(S, "hello"),
    py.tcp.receive(S, REPLY),
    ++print(REPLY),
    py.tcp.close(S)
)

Import

-import_from(py.tcp, [
    connect, listen, accept,
    send, receive, close, set_timeout
])

Client predicates

Predicate Mode Description
connect(Host, Port, Socket) +Host, +Port, -Socket connect to TCP server, bind opaque socket handle
connect("example.com", 80, SOCKET)

A network failure raises (ruled 2026-10-02; it used to fail): a host that does not resolve is existence_error(source_sink, Host) (Scryer's socket_client_open/3 term), a refused connection system_error(connection_refused), an unreachable host system_error(host_unreachable), a timeout resource_error(timeout), a permission refusal permission_error(open, source_sink, Host). listen/3 on a port in use is system_error(address_in_use); send/2 on a broken connection system_error(broken_pipe) or system_error(connection_reset).


Server predicates

Predicate Mode Description
listen(Host, Port, ServerSocket) +Host, +Port, -ServerSocket Create listening socket with SO_REUSEADDR. Port 0 → ephemeral.
accept(ServerSocket, ClientSocket) +Server, -Client accept incoming connection. Blocks until a client connects.
listen("0.0.0.0", 8080, SERVER),
accept(SERVER, CLIENT),
receive(CLIENT, DATA),
send(CLIENT, DATA),    # echo back
close(CLIENT),
close(SERVER)

Data transfer

Predicate Mode Description
send(Socket, Data) +Socket, +Data send string (UTF-8) or bytes via sendall()
receive(Socket, Data) +Socket, -Data receive up to 4096 bytes, decode UTF-8
receive(Socket, BufferSize, Data) +Socket, +BufSize, -Data receive with custom buffer size

receive returns a string if the data is valid UTF-8, or raw bytes if decoding fails. Fails if the connection is closed (no data received).

send(SOCKET, "Hello, server!")
receive(SOCKET, RESPONSE)

# Custom buffer size
receive(SOCKET, 65536, LARGE_DATA)

Socket management

Predicate Mode Description
close(Socket) +Socket close socket. Always succeeds (even on already-closed sockets).
set_timeout(Socket, Seconds) +Socket, +Seconds Set socket timeout (float). A later operation that times out raises resource_error(timeout).
set_timeout(SOCKET, 5.0),     # 5-second timeout
receive(SOCKET, DATA)         # fails if no data within 5 seconds

Examples

Simple TCP client

-import_from(py.tcp, [connect, send, receive, close])

tcp_request(HOST, PORT, REQUEST, RESPONSE) <- (
    connect(HOST, PORT, S),
    send(S, REQUEST),
    receive(S, RESPONSE),
    close(S)
)

Echo server (single client)

-import_from(py.tcp, [listen, accept, send, receive, close])

echo_server(PORT) <- (
    listen("0.0.0.0", PORT, SERVER),
    accept(SERVER, CLIENT),
    receive(CLIENT, DATA),
    send(CLIENT, DATA),
    close(CLIENT),
    close(SERVER)
)

Gotchas

  • Sockets are impure — socket operations have side effects and do not backtrack cleanly. If a send succeeds but a later goal fails, the data has already been sent.
  • accept blocks — it waits for a connection. Use set_timeout on the server socket to limit the wait time.
  • No automatic cleanup — always close sockets explicitly. Python's garbage collector will eventually close them, but relying on GC is bad practice for network resources.
  • Port 0 in listen lets the OS pick an ephemeral port — useful for tests.

See also: HTTP — higher-level HTTP requests (no sockets needed) · Process — shell commands and subprocess execution · Python Interop — ++() for advanced socket operations.