Outdated egg!

This is an egg for CHICKEN 4, the unsupported old release. You're almost certainly looking for the CHICKEN 5 version of this egg, if it exists.

If it does not exist, there may be equivalent functionality provided by another egg; have a look at the egg index. Otherwise, please consider porting this egg to the current version of CHICKEN.

socket

socket provides an interface to the BSD socket API. For a somewhat higher-level interface, see the tcp6 and udp6 extensions.

Overview

This extension provides a comprehensive interface to BSD sockets, including socket creation, client and server setup, data transfer, i/o ports, forward and reverse address resolution, socket options, and socket-related integer constants. It supports both IPv4 and IPv6, as well as UNIX sockets.

All socket operations block only the calling thread; other threads can continue to run, even on Windows platforms.

  1. Outdated egg!
  2. socket
    1. Overview
    2. Socket interface
      1. Socket creation
      2. Socket addresses
      3. Address resolution
      4. Setup and teardown
      5. Status
      6. Data transfer
        1. Receiving data
        2. Sending data
      7. I/O ports
      8. Parameters
    3. Socket option interface
      1. Socket option accessors
      2. Low-level socket option interface
      3. Socket option constants
        1. Socket-level constants
        2. TCP-level constants
        3. IP-level constants
        4. Socket and protocol levels
      4. SO_LINGER lowlevel example
    4. Examples
      1. Client-server examples
      2. Disable Nagle's Algorithm on TCP listener socket
      3. Set socket options on HTTP server
    5. Bugs and limitations
    6. About this egg
      1. Source
      2. Author
      3. Version history
      4. Portability
      5. License

Socket interface

Socket creation

[record] socket
[procedure] (socket family type #!optional (protocol 0))
[procedure] (socket? so)
[procedure] (socket-fileno so)
[procedure] (socket-family so)
[procedure] (socket-type so)
[procedure] (socket-protocol so)

Socket objects. You construct a socket using the socket procedure, passing an address family af/* constant for family (IPv4, IPv6) and a socket type sock/* constant for socket type (TCP, UDP). protocol should almost always be zero, unless you are creating raw sockets; it is implicit in the socket type. Sockets take up a file descriptor in the system until closed, which may or may not happen automatically on error. All sockets are created in non-blocking mode.

Accessors:

socket-fileno
The socket's file descriptor.
socket-family
The socket family, an integer constant.
socket-type
The socket type, an integer constant.
socket-protocol
The socket protocol, an integer constant.

Note that sockets are also implicitly created by socket-connect/ai and socket-accept.

Example:

(socket af/inet sock/stream)
  ; => #<socket fd:19 af/inet sock/stream>
(socket af/inet6 sock/dgram)
  ; => #<socket fd:20 af/inet6 sock/dgram>
[constant] af/inet
[constant] af/inet6
[constant] af/unix
[constant] af/unspec
[procedure] (integer->address-family int)
[procedure] (address-family->integer sym)

Address family constants for socket creation. It is possible to convert between integer constants and symbols using the provided procedures, but this is just for debugging convenience; the API requires integer constants.

[constant] sock/stream
[constant] sock/dgram
[constant] sock/raw
[procedure] (integer->socket-type int)
[procedure] (socket-type->integer sym)

Socket type constants for socket creation.

[constant] ipproto/tcp
[constant] ipproto/udp
[procedure] (integer->protocol-type int)
[procedure] (protocol-type->integer sym)

Protocol constants for socket creation.

Socket addresses

[record] sockaddr
[procedure] (sockaddr? sa)
[procedure] (sockaddr-family sa)
[procedure] (sockaddr-address sa)
[procedure] (sockaddr-port sa)
[procedure] (sockaddr-path sa)
[procedure] (sockaddr->string sa)

The socket address object. sockaddr is used throughout the BSD sockets API to represent the address of a local or remote socket endpoint.

The most convenient constructor for Internet socket addresses is inet-address. The fundamental Internet socket address constructor is address-information, which is more powerful but also more complex to use. For UNIX sockets, use unix-address.

Socket address object accessors are:

sockaddr-family
returns the socket address family as an integer constant, e.g. af/inet.
sockaddr-address
returns the address of a socket as a string (for Internet sockets, this is the IP address).
sockaddr-port
returns the socket port for Internet sockets; it is an error to call it on another type of socket.
sockaddr-path
returns the pathname for UNIX sockets, or an error for other socket types.
sockaddr->string
returns a compact representation of the socket address as a string. For Internet sockets, it returns "address" when port is 0; otherwise, it returns "address:port" for IPv4 addresses and "[address]:port" for IPv6 addresses.
[procedure] (inet-address addr port)

Returns a sockaddr object constructed from IP address addr (a string) and port port (a number or numeric string). If the address or port input is invalid, an error is raised.

If addr is #f, the unspecified address is used ("::" or "0.0.0.0"). If port is #f, the unspecified port is used (integer 0). It is an error for both addr and port to be unspecified.

Note that when IPv6 is preferred on your system, the unspecified address #f is typically "::" and the resulting object will be of family af/inet6. This may not be what you want, so it is a good idea to specify which unspecified address (yes, really) you mean -- "::" or "0.0.0.0" -- in lieu of #f.

[procedure] (unix-address path)

Returns a sockaddr object constructed from the pathname path, suitable for use with a socket in address family af/unix. Throws an error if UNIX sockets are not supported on your platform.

Address resolution

[procedure] (address-information node service #!key family (type sock/stream) protocol flags)
[constant] ai/numerichost
[constant] ai/passive
[constant] ai/canonname

Looks up node name and service name and translates them to numeric values. Returns a list of addrinfo objects, each of which contains a socket address (sockaddr object) suitable for use in socket calls such as socket-bind and socket-connect.

node is either a node name (string) or IP address (string). service may be a string representing a service name or port number, or an integer. If node is #f, it is treated as the loopback address; however, if ai/passive is set it is treated as the unspecified address. If service is #f, it is treated as unspecified (0).

Keyword arguments accept numeric constants and restrict the returned addresses accordingly:

family
Address family, either af/inet or af/inet6, defaulting to #f. If #f, both IPv6 and IPv4 addresses may be returned, depending on your system's configuration and IP stack.
type
Socket type; usually sock/stream or sock/dgram, defaulting to sock/stream. Can be #f, but results may vary between systems, so it is safer to specify one. See examples.
protocol
Protocol type, usually #f. Can also be ipproto/tcp or ipproto/udp; however, some systems (such as Windows) do not construct a proper socket address when type is unspecified, so it is safer to just provide a value for type and leave this as #f.

The behavior of address-information can be influenced by the value of flags, which should be the bitwise-ior (or simply +) of any of the following constants:

ai/numerichost
The host is an IP address string; do not attempt to resolve it.
ai/passive
The socket address is intended to be used in a call to bind(). The only difference is that an address of #f is translated into the unspecified address "::" or "0.0.0.0", rather than the loopback address.
ai/canonname
Include the canonical (usually FQDN) hostname in the addrinfo object. If not provided, that field will be #f.

Examples:

(address-information "localhost" "http")
  ; => (#<addrinfo "[::1]:80" af/inet6 sock/stream ipproto/tcp>
        #<addrinfo "[fe80::1%lo0]:80" af/inet6 sock/stream ipproto/tcp>
        #<addrinfo "127.0.0.1:80" af/inet sock/stream ipproto/tcp>)
(address-information "127.0.0.1" 53 type: sock/dgram)
  ; => (#<addrinfo "127.0.0.1:53" af/inet sock/stream ipproto/udp>)
(address-information "he.net" 80)
  ; => (#<addrinfo "[2001:470:0:76::2]:80" af/inet6 sock/stream ipproto/tcp>
        #<addrinfo "216.218.186.2:80" af/inet sock/stream ipproto/tcp>)
(address-information "he.net" 80 type: #f)
  ; Possible response on UNIX -- return both TCP and UDP addresses.
  ; Might also just return TCP.
  ; => (#<addrinfo "[2001:470:0:76::2]:80" af/inet6 sock/dgram ipproto/udp>
        #<addrinfo "[2001:470:0:76::2]:80" af/inet6 sock/stream ipproto/tcp>
        #<addrinfo "216.218.186.2:80" af/inet sock/dgram ipproto/udp>
        #<addrinfo "216.218.186.2:80" af/inet sock/stream ipproto/tcp>)
  ; Possible response on Windows -- socket addresses are not valid for use
  ; => (#<addrinfo "[2001:470:0:76::2]:80" af/inet6 0 0>
        #<addrinfo "216.218.186.2:80" af/inet 0 0>)
(address-information #f "http")
  ; => (#<addrinfo "[::1]:80" af/inet6 sock/stream ipproto/tcp>
        #<addrinfo "127.0.0.1:80" af/inet sock/stream ipproto/tcp>)
(address-information #f "http" flags: ai/passive)
  ; => (#<addrinfo "[::]:80" af/inet6 sock/stream ipproto/tcp>
        #<addrinfo "0.0.0.0:80" af/inet sock/stream ipproto/tcp>)
  ; As an example of inconsistent per-platform behavior, note that
  ; recent Ubuntu among others returns the above in reverse order.
(address-information "allie" 0 flags: ai/canonname)
  ; => (#<addrinfo "192.168.1.7" af/inet sock/stream ipproto/tcp
                   canonical: "allie.xorinia.dim">)
(address-information #f #f)
  ; => ()
[record] addrinfo
[procedure] (addrinfo? ai)
[procedure] (addrinfo-family ai)
[procedure] (addrinfo-socktype ai)
[procedure] (addrinfo-protocol ai)
[procedure] (addrinfo-address ai)
[procedure] (addrinfo-canonname ai)
[procedure] (addrinfo-flags ai)

Address information record returned by address-information.

[procedure] (name-information saddr #!optional (flags 0))
[constant] ni/numerichost
[constant] ni/numericserv
[constant] ni/dgram
[constant] ni/namereqd
[constant] ni/nofqdn

Given a socket address object saddr, performs a reverse-lookup to obtain the node and service names, returning them as the pair ("node" . "service"). If hostname lookup fails, the numeric representation of the address is returned as a string. If service number lookup fails, it is returned as an integer.

The socket address object is usually constructed with inet-address or obtained from a socket call, e.g. socket-peer-name. As a convenience in socket 0.2.1 and later, if saddr is a string, it is converted to a socket address object with (inet-address saddr #f).

The behavior of name-information can be influenced by FLAGS. FLAGS may be the bitwise-ior (or simply +) of the following constants:

ni/numerichost
Do not resolve the node address to a name; instead, return the canonical string representation of the address, as in inet_ntop(). (sockaddr-address saddr) returns the same representation.
ni/numericserv
Do not attempt to resolve the service number to a name.
ni/namereqd
If hostname lookup fails, raise an error.
ni/nofqdn
Return only the local part of the hostname for local hosts.
ni/dgram
Look up the service as a datagram service. A few service names may differ between TCP and UDP.

Examples:

(name-information (inet-address "127.0.0.1" 80))
  ; => ("localhost" . "http")
(name-information (inet-address "::1" 80))
  ; => ("localhost" . "http")
(name-information (inet-address "::1" #f))
  ; => ("localhost" . 0)
(name-information "::1")
  ; => ("localhost" . 0)
(name-information (inet-address "::1" 80) ni/numerichost)
  ; => ("::1" . "http")
(name-information (inet-address "::1" 80) (+ ni/numerichost ni/numericserv))
  ; => ("::1" . 80)
(name-information (inet-address "127.0.0.2" 80))
  ; => ("127.0.0.2" . "http")
(name-information (inet-address "127.0.0.2" 80) ni/namereqd)
  ; => error: nodename nor servname provided, or not known
(name-information (inet-address "2001:470:0:64::2" 80) ni/numericserv)
  ; => ("ipv6.he.net" . 80)
(name-information (socket-peer-name s))  ;; s being an accept()ed socket
  ; => ("taco.universe12.dim" . 31828)

Setup and teardown

[procedure] (socket-connect so saddr)
[procedure] (socket-connect/ai ais)

socket-connect connects to the remote socket address saddr over the socket so. Upon completion, so will be connected; on connection failure an error is thrown. The return value is unspecified. This is a non-blocking operation; other SRFI-18 threads can continue to run.

If connection fails due to refusal, network down, unreachable host or system enforced timeout, it raises a "transient" error of type (exn i/o net transient), signalling the connection can be retried later if desired. A connection may also raise an (exn i/o net timeout) error after (socket-connect-timeout) milliseconds. If a fatal error occurs, it raises an error of type (exn i/o net), like all other socket procedures.

socket-connect/ai connects to the addresses in the addrinfo list ais sequentially until the connection succeeds or there are no more addresses. If a fatal error occurs while connecting, it aborts immediately; but transient or timeout errors cause it to try the next address. If all attempts fail, the error raised is that of the last attempt. On success, the return value is a fresh connected socket of the appropriate family and type.

Examples:

 ;; Connect to localhost:22 over IPv4.
 (define so (socket af/inet sock/stream))
 (socket-connect so (inet-address "127.0.0.1" 22))
 ;; Connect to localhost:22 over IPv6.
 (define so (socket af/inet6 sock/stream))
 (socket-connect so (inet-address "::1" 22))
 ;; Connect to localhost:22 over IPv4 and return the connected socket.
 (socket-connect/ai
   (address-information "localhost" 22 family: af/inet))
 ; => #<socket fd:8 af/inet sock/stream>
 ;; Try to connect to localhost:ssh via any address family.  
 ;; In this case, address-information may return the IPv6 loopback
 ;; address "::1" and perhaps "fe80::1", along with the usual 
 ;; IPv4 "127.0.0.1".  socket-connect/ai will try them all in order
 ;; and return a new connected socket.  For illustrative purposes
 ;; we use socket-peer-name to show where we connected.

 (socket-peer-name
  (socket-connect/ai (address-information "localhost" "ssh")))
 ; => #<sockaddr "[::1]:22">         ;; If ssh listening on ::1
 ; => #<sockaddr "[fe80::1%lo0]:22"> ;; If listening on link-local loopback
 ; => #<sockaddr "127.0.0.1:22">     ;; If listening on 127.0.0.1 only
 ; => error: connection refused      ;; If ssh isn't running
[procedure] (socket-bind so saddr)

Binds socket so to socket address saddr. The return value is unspecified.

; Bind to the IPv4 unspecified address on port 8000.
(define so (socket af/inet sock/stream))
(socket-bind so (inet-address "0.0.0.0" 8000))
; Bind to the IPv6 unspecified address on port 8000.  This may also
; allow IPv4 connections, depending on your system settings. 
(define so (socket af/inet6 sock/stream))
(socket-bind so (inet-address "::" 8000))
; Bind to the IPv6 unspecified address on port 8000, limiting
; connections to IPv6 only.
(define so (socket af/inet6 sock/stream))
(set! (ipv6-v6-only? so) #t)
(socket-bind so (inet-address "::" 8000))
[procedure] (socket-listen so backlog)

Listen for incoming connections on socket so, with a connection queue of integer length backlog. This call is only valid for connection-oriented (stream) sockets.

[procedure] (socket-accept so)
[procedure] (socket-accept-ready? so)

socket-accept accepts a connection on listening socket so, returning a new connected socket object. The address of the peer can be obtained by calling socket-peer-name on the socket.

This is a non-blocking operation; other SRFI-18 threads can continue to run in the meantime, although this one will block. If the accept does not complete within socket-accept-timeout milliseconds, a timeout error is raised.

socket-accept-ready? tests whether there is a connection waiting to be accepted, so you can avoid blocking the current thread. However, if a peer resets his connection between your testing and accepting, the accept may block nonetheless.

[procedure] (socket-shutdown so how)
[constant] shut/rd
[constant] shut/wr
[constant] shut/rdwr

Shutdown the full-duplex connection on socket so in the manner of how:

shut/rd
disallow further receiving
shut/wr
disallow further sending
shut/rdwr
disallow further receiving and sending

The system normally shuts down the connection itself when closing, so calling this manually is not often necessary. One usage example is using shut/wr to inform a server you have finished transmitting, while allowing it to continue sending to you.

[procedure] (socket-close so)

Close socket so. If a connection was established, the socket is shut down gracefully.

socket-close throws an error if the close() fails.

[procedure] (socket-close* so)

Same as socket-close, except that socket-close* does not throw an error if the close() fails.

This could be useful in certain lowlevel code, such as after a network error, but you should not use this unless you know what you're doing. This might go away or change semantics in the future.

Status

[procedure] (socket-name so)

Return a sockaddr object representing the address of the local endpoint of socket so. If the socket has not been bound, it returns #f.

The procedure name is derived from getsockname(), hence the use of "name" to describe a socket address.

[procedure] (socket-peer-name so)

Return a sockaddr object representing the address of the remote endpoint of socket so. If no connection exists, returns #f. This can be used after socket-connect or on a socket returned by socket-accept. Note that this also works with UDP "connections" made with socket-connect.

The procedure name is derived from getpeername(), hence the use of "name" to describe a socket address.

Data transfer

Receiving data
[procedure] (socket-receive! so buf #!optional (start 0) (end #f) (flags 0))
[procedure] (socket-receive-ready? so)

Receives data from socket so and writes it into buf, which may be a string or a blob. start and end are optional offsets into buf; the call attempts to read end - start = len bytes. If end is #f, it is interpreted as the end of the blob or string.

This call will block until data is available, but other threads can proceed. If the receive does not complete within socket-receive-timeout milliseconds, a timeout error is raised. To avoid blocking the current thread, you can check if data is ready via socket-receive-ready?.

Returns the number of bytes actually received (and updates buf as a side effect).

For datagram sockets, if len is smaller than the amount of data in the next datagram, the rest of the data is irrevocably lost.

[procedure] (socket-receive so len #!optional (flags 0))

Receives up to len bytes from data from socket so and returns it in a string (sized to fit the returned data). Otherwise, it behaves like socket-receive!.

[procedure] (socket-receive-from! so buf #!optional (start 0) (end #f) (flags 0))

Like socket-receive!, but receives data on a connectionless datagram socket, returning 2 values: the number of bytes read, and a sockaddr object representing the source.

[procedure] (socket-receive-from so len #!optional (flags 0))

Receives up to len bytes from data from socket so and returns two values: a string (sized to fit the returned data), and a sockaddr object representing the source. Otherwise, it behaves like socket-receive-from!.

Sending data
[procedure] (socket-send so buf #!optional (start 0) (end #f) (flags 0))

Sends data to socket so from the buffer buf, which may be a string or a blob. start and end are optional offsets into buf; the call attempts to write end - start = len bytes. If end is #f, it is interpreted as the end of the blob or string.

This call will block until at least some data is sent, but other threads can proceed. If the send does not complete within (socket-send-timeout) milliseconds, a timeout error is raised.

Returns the number of bytes actually sent.

[procedure] (socket-send-to so buf saddr #!optional (start 0) (end #f) (flags 0))

Like socket-send, but sends data over a connectionless datagram socket to sockaddr saddr, returning the number of bytes actually sent.

[procedure] (socket-send-all so buf #!optional (start 0) (end #f) (flags 0))

Sends all data between start and end in buf over connected socket so by calling socket-send multiple times until all data is sent.

Data is sent in chunks of size (socket-send-size); the last chunk sent may be smaller than this. A #f value for socket-send-size will attempt to send all remaining data with each call to send(). Note that this chunking works for connected datagram sockets as well as stream sockets; you can use it to send a large buffer divided into, say, 512-byte datagrams.

I/O ports

[procedure] (socket-i/o-ports so)

Constructs an input port I and an output port O associated with the connected socket so, returning (values I O). This procedure works on both stream and datagram sockets.

To enable output buffering on stream socket ports, see the parameter socket-send-buffer-size. Setting it to a value of 1024 bytes is reasonable.

Below is a fairly involved explanation of input and output buffering and chunking, as well as recommendations for use with datagrams.

socket-i/o-ports is normally used with stream sockets. Input data is always buffered in a buffer of size (socket-receive-buffer-size). Whenever the buffer is empty, socket-receive! is called once to read up to that many bytes. Output data is sent with socket-send-all, so it may be divided into chunks of size (socket-send-size). If output is unbuffered, socket-send-all is called as soon as data is written to the port.

If output is buffered by setting (socket-send-buffer-size) to N, then N characters are buffered before sending the data. Note that only multiples of the buffer size are sent (any overage is kept in the buffer). For example, if the buffer can hold 512 bytes and contains 500 bytes, writing 526 more bytes brings the total unsent size to 1026 bytes. 1024 bytes (2 blocks) are written out in a single call to socket-send-all and the last 2 bytes are retained in the buffer.

When the output buffer size and chunk size are both set, it is recommended to make the chunk size a multiple of the buffer size; for example, buffer size = 1024, chunk size = 8192. If not aligned, extraneous small packets may be sent. Buffer size is almost always less than or equal to chunk size. If greater, it should be a multiple of the chunk size. Using powers of 2 for both satisfies all cases.

Note that socket-i/o-ports can also be used to create ports on connected datagram sockets. Input is always buffered and a single chunk of up to size (socket-receive-buffer-size) is read into the buffer whenever the buffer is empty. (If the datagram is smaller than the buffer, repeated reads are not performed; rather, the buffer is used until exhausted again. Any datagram exceeding the buffer size will be truncated.) Output is divided into chunks of size (socket-send-size), as in socket-send-all -- this is useful for placing a maximum cap on datagram size transmitted. Finally, output buffering may be enabled, which behaves the same as with TCP ports; characters are buffered and sent in blocks of (socket-send-buffer-size) bytes. Again, to avoid excessive transmission, the chunk size should be a multiple of the buffer size or vice versa.

For example, to accept up to 4K datagrams, buffer 128 characters at a time and send 128-, 256-, 384- or 512-byte datagrams at a time:

(parameterize ((socket-receive-buffer-size 4096)
               (socket-send-buffer-size 128)
               (socket-send-size 512))
  (define so (socket-connect/ai
              (address-information host port type: sock/dgram)))
  (define-values (i o) (socket-i/o-ports so))
  ;; a useful example would be nice
  ...)
[procedure] (socket-i/o-port->socket port)

Returns the socket object assocated with input or output port port. From there you can obtain a file descriptor with socket-fileno.

Alternatively, port->fileno from posix is supported to obtain a file descriptor. (Also see Bugs and limitations.)

[procedure] (socket-abandon-port port)

Marks the socket input or output port port as abandoned. Normally, when an socket input port is closed the read side of the connection is shut down; similarly closing the output port shuts down the write side. Marking a port as abandoned skips this shutdown. This is useful to ensure a connection stays open after the port is closed.

The socket is still closed after both ports are closed, regardless of their abandoned status.

Parameters

[parameter] (socket-connect-timeout ms) [default: #f]
[parameter] (socket-accept-timeout ms) [default: #f]
[parameter] (socket-receive-timeout ms) [default: 1 minute]
[parameter] (socket-send-timeout ms) [default: 1 minute]

Timeouts in milliseconds for connect, receive, send and accept operations. If these timeout are exceeded, the error (exn i/o net timeout) is raised. If #f, the operation never times out (unless the system forces it to).

[parameter] (socket-send-buffer-size n) [default: #f]
[parameter] (socket-send-size n) [default: 16384]
[parameter] (socket-receive-buffer-size n) [default: 4096]

These parameters are used mostly to adjust the behavior of socket ports, and take effect when the ports are created.

(socket-send-buffer-size) is the buffer size used by socket output ports. If #f, no buffering is done. A power of 2, such as 1K or 4K, is an appropriate value.

(socket-send-size) is used by socket output ports, and is the size used in a single call to socket-send by socket-send-all. It can be #f, meaning infinite, so that any remaining data is sent at each call. When set, it should usually be a multiple of (socket-send-buffer-size), assuming buffering is also enabled. A power of 2 is an appropriate value, such as 8K or 16K.

(socket-receive-buffer-size) is the size used for the input buffer in socket input ports. A power of 2, such as 4K, is appropriate. Input buffering can not be disabled.

Socket option interface

BSD socket option values are of substantially differing types: boolean flags (TCP_NODELAY), integers (SO_SNDBUF), structures (SO_LINGER), and so on. Still, we want a consistent interface and an element of type-safety as well. So for each option, we provide a unique getter / setter procedure which does the type-checking and marshals (or unmarshals) the data as needed.

Each getter / setter takes a socket argument. The "socket" is either a file descriptor number, as returned by a socket() call, or a socket object as provided in this extension. SRFI-17 generalized set! is used on the getters to set socket options.

(tcp-no-delay? s)            ; => #t or #f
(set! (tcp-no-delay s) #t)

An error is thrown if the socket call fails, if the value passed is of incorrect type, or if you try to set a read-only option.

An error of (exn i/o net unsupported) is thrown if you try to use a socket option that is not defined at all on the platform, or that is defined but not supported by the operating system. Note that some platforms, particularly Windows, may return a false positive for "unsupported option" when it really indicates a usage error (wrong socket type or option value).

Socket option accessors

Below is a list of option procedures and their value type. The procedure names are verbose variants of their associated constant names. For example, SO_REUSEADDR becomes so-reuse-address.

Only booleans and integers and their read-only variants are currently supported. The intention is to additionally support timevals, linger, ip_mreq structs and ipoptions, at least. There is an example of linger support in the low-level interface below.

[procedure] (so-reuse-address? s) [so/reuseaddr]
[procedure] (so-debug? s) [so/debug]
[procedure] (so-keep-alive? s) [so/keepalive]
[procedure] (so-dont-route? s) [so/dontroute]
[procedure] (so-broadcast? s) [so/broadcast]
[procedure] (so-oob-inline? s) [so/oobinline]
[procedure] (so-accept-connections? s) [so/acceptconn] (r/o)
[procedure] (so-send-buffer s) [so/sndbuf]
[procedure] (so-receive-buffer s) [so/rcvbuf]
[procedure] (so-send-low-water s) [so/sndlowat]
[procedure] (so-receive-low-water s) [so/rcvlowat]
[procedure] (so-error s) [so/error] (r/o)
[procedure] (so-type s) [so/type] (r/o)

Getters / setters for boolean and integer socket-level options, where s is a socket object or an integer file descriptor. To set an option, use SRFI-17 generalized set:

(set! (so-reuse-address? s) #t)

"(r/o)" indicates the option is read-only; an error will be raised if you attempt to set it.

As a special note on so-reuse-address?, on Windows platforms it will first attempt to use option so/exclusiveaddruse because this option matches UNIX semantics. If this fails it will fall back to so/reuseaddr, which allows any processes to rebind a previously bound address and port.

[procedure] (tcp-no-delay? s) [tcp/nodelay]
[procedure] (tcp-no-push? s) [tcp/nopush]
[procedure] (tcp-no-options? s) [tcp/noopt]
[procedure] (tcp-keep-alive s) [tcp/keepalive]
[procedure] (tcp-max-segment-size s) [tcp/maxseg]

Getters / setters for TCP-level socket options (ipproto/tcp). s is a socket object or an integer file descriptor.

[procedure] (ip-header-included? s) [ip/hdrincl]
[procedure] (ip-type-of-service s) [ip/tos]
[procedure] (ip-time-to-live s) [ip/ttl]

Getters / setters for IP-level socket options (ipproto/ip). s is a socket object or an integer file descriptor.

[procedure] (ipv6-v6-only? s) [ipv6/v6only]

Getters / setters for IPv6-level socket options (ipproto/ipv6). s is a socket object or an integer file descriptor.

Low-level socket option interface

A low-level socket option interface is also provided. This is intended to let you use the constants defined above (or your own) when there is no high-level interface implemented. This interface can get or set arbitrary option contents; you're not limited to predefined types such as integer or boolean. No checking is done that the passed option value is appropriate, as that's the job of the high-level interface.

[procedure] (set-socket-option s level name val)

Set the value of option name at socket level level on socket s to val. val may be a fixnum or a boolean. It may also be a blob or a string; if so, the raw contents are passed to the option, which is useful when a structure is required. The return value is unspecified.

If an unsupported option or level is requested, a condition of type (exn i/o net unsupported) is raised.

Note: due to the vagaries of structure member alignment (and 32 vs. 64-bit sizes), it's not generally safe to pack raw data yourself into a blob or a SRFI-4 vector. Instead, you should treat the blob contents as a C struct. See the longer example down the page for more.

(set-socket-option S ipproto/tcp tcp/nodelay 1)
(set-socket-option S ipproto/tcp tcp/nodelay (make-string 4 #\x0))
(set-socket-option S sol/socket so/rcvlowat (u32vector->blob/shared (u32vector #x01020304)))
[procedure] (get-socket-option s level name #!optional (len #f))

Get the value of option name at socket level level on socket s. If len is #f, the default, we interpret the option value as an integer and return that. Otherwise, temporary storage of length len is allocated to receive the binary option data; after the call it is resized to fit the data, and returned. If len is too small to hold the returned data, the result is undefined.

If an unsupported option or level is requested, a condition of type (exn i/o net unsupported) is raised.

This procedure does not convert integers to boolean values---if you expect a boolean flag, assume zero means #f and non-zero means #t. Don't be surprised if boolean flags return different non-zero integer values from those you put in; that's an implementation detail. You can only rely on true being some non-zero value.

(get-socket-option S ipproto/tcp tcp/nodelay)     ; => 8, perhaps, meaning #t
(get-socket-option S ipproto/tcp tcp/nodelay 64)  ; => #${08000000}
(get-socket-option S ipproto/tcp tcp/nodelay 4)   ; => #${08000000}

Socket option constants

Integer constants are provided for socket levels, socket types, and socket options. They are renamed slightly, and consistently, to achieve a more Schemely appearance: for example, C's SO_REUSEADDR becomes so/reuseaddr.

Some platforms do not define all the constants we provide. If a constant is undefined, its value is #f and attempting to get or set it will raise an error. Note that a platform may define a constant but not support it (for example, ipv6/v6only on Windows prior to Vista); this will not be known until you try to get or set it.

Socket-level constants
[constant] so/reuseaddr
[constant] so/debug
[constant] so/acceptconn
[constant] so/keepalive
[constant] so/dontroute
[constant] so/broadcast
[constant] so/linger
[constant] so/oobinline
[constant] so/sndbuf
[constant] so/rcvbuf
[constant] so/sndlowat
[constant] so/rcvlowat
[constant] so/sndtimeo
[constant] so/rcvtimeo
[constant] so/error
[constant] so/type
[constant] so/useloopback
[constant] so/reuseport
[constant] so/timestamp
[constant] so/exclusiveaddruse

Socket-level socket options for use with set-socket-option and get-socket-option at level sol/socket.

TCP-level constants
[constant] tcp/nodelay
[constant] tcp/nopush
[constant] tcp/noopt
[constant] tcp/keepalive
[constant] tcp/maxseg

TCP-level socket options for use with set-socket-option and get-socket-option at level ipproto/tcp.

IP-level constants
[constant] ip/options
[constant] ip/hdrincl
[constant] ip/tos
[constant] ip/ttl
[constant] ip/mtu
[constant] ip/mtu-discover
[constant] ip/pktinfo
[constant] ip/recverr
[constant] ip/recvtos
[constant] ip/recvttl
[constant] ip/router-alert
[constant] ip/recvopts
[constant] ip/recvretopts
[constant] ip/retopts
[constant] ip/recvdstaddr
[constant] ip/multicast-if
[constant] ip/multicast-ttl
[constant] ip/multicast-loop
[constant] ip/add-membership
[constant] ip/drop-membership

IP-level socket options for use with set-socket-option and get-socket-option at level ipproto/ip.

Socket and protocol levels
[constant] sol/socket
[constant] ipproto/ip
[constant] ipproto/ipv6
[constant] ipproto/tcp
[constant] ipproto/icmp
[constant] ipproto/udp

Socket level constants, for use with set-socket-option and get-socket-option.

SO_LINGER lowlevel example

This is a pretty hairy example of getting and setting the so/linger option, which does not currently have a high-level equivalent. so/linger usually takes and returns a struct linger value which consists of an on/off flag and a linger timeout in seconds. (But not always!)

The approach below encases the struct linger in an appropriately-sized blob and creates an encoder and decoder for this structure. Any useful option taking a structure value should ideally have a high-level interface created for it instead.

(define _linger_size (foreign-value "sizeof(struct linger)" int))
(define (encode-linger-option state time)
  (let ((blob (make-blob _linger_size)))
    ((foreign-lambda* void ((scheme-pointer ptr) (bool onoff) (int linger))
                      "struct linger *p = ptr;"
                      "p->l_onoff = onoff; p->l_linger = linger;")
     blob state time)
    blob))
(define (decode-linger-option blob)
  ; sanity checking on parameter recommended here
  (list ((foreign-lambda* bool ((scheme-pointer p)) 
         "return(((struct linger *)p)->l_onoff);") blob)
        ((foreign-lambda* int ((scheme-pointer p)) 
         "return(((struct linger *)p)->l_linger);") blob)))
(set-socket-option S sol/socket so/linger (encode-linger-option #t 100))
(decode-linger-option 
 (get-socket-option S sol/socket so/linger _linger_size))
   ; => (#t 100)

Examples

Client-server examples

For a simple example of client-server communication over a unix socket, see here.

A TCP/IP example is still to be written.

Disable Nagle's Algorithm on TCP listener socket

The tcp unit does not support setting arbitrary socket options on sockets it creates. However, you can obtain a listener's socket file descriptor after the fact.

(define L (tcp-listen 8080))
(define S (tcp-listener-fileno L))
(set! (tcp-no-delay? S) #t)

Set socket options on HTTP server

This is similar to the above. HTTP servers may see some performance gain when Nagle's algorithm is disabled. This is generally the default on Linux, but not Solaris or OS X.

(This needs to be updated for spiffy / intarweb.)

(parameterize ((http:listen-procedure
               (lambda (port backlog host)
                (let ((L (tcp-listen port backlog host)))
                  (set! (tcp-no-delay? (tcp-listener-fileno L) #t))
                  L))))
  ((http:make-server ...)))

Bugs and limitations

About this egg

Source

https://github.com/ursetto/socket-egg

Author

Jim Ursetto

Some code was derived from the core tcp unit by Felix Winkelmann and the rest of the Chicken team.

Version history

0.3.3
Quote variables for Windows build (wasamasa)
0.3.2
Remove debug print statements (kdltr)
0.3.1
Add Chicken 5 support. Add simple unix socket test to tests/; not used by -test.
0.2.7
remove some debug prints
0.2.6
Handle ##sys#scan-buffer-line signature change in CHICKEN 4.8.2. Bug reported by Jonathan Chan and David Krentzlin.
0.2.5
socket-accept: Handle select failure. Patch by Jonathan Chan.
0.2.4
SO_EXCLUSIVEADDR fix for Cygwin
0.2.3
Set connectionless sockets to nonblocking
0.2.2
Fix segfault in socket-receive, socket-send and friends when socket argument was not a socket (reported by hypnocat)
0.2.1
Treat string addr arg to name-information as (inet-address addr #f)
0.2
Add UNIX socket support; eliminate much dead code and runtime support checks.
0.1
Initial release for Chicken 4.

Portability

License

Copyright (c) 2011-2019, Jim Ursetto.  All rights reserved.
Copyright (c) 2008-2011, The Chicken Team
Copyright (c) 2000-2007, Felix L. Winkelmann 

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

  Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer. Redistributions in
  binary form must reproduce the above copyright notice, this list of
  conditions and the following disclaimer in the documentation and/or
  other materials provided with the distribution. Neither the name of the
  author nor the names of its contributors may be used to endorse or
  promote products derived from this software without specific prior
  written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.