mzsocket

Contents

Overview

mzsocket is a native extension library for mzscheme, that provides the BSD/POSIX sockets interface.

It supports IPv4, IPv6, Unix domain, and raw sockets depending on availability on the host platform.

Installation

Extract the library archive to your local collects directory and run

setup-plt -l socket

To use the library:

(require (lib "socket.ss" "socket"))

To run basic tests on the library:

(require (only (lib "test.ss" "socket") run-tests))
(run-tests)

License

mzsocket: BSD/POSIX sockets library for mzscheme
Copyright (C) 2007 Dimitris Vyzovitis <vyzo@media.mit.edu>

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
USA

API

Sockets

procedure: (socket #:optional domain type proto)
-> socket

Creates a socket, registerd with the current custodian.

The default argument values are PF_INET, SOCK_STREAM, and 0. Sockets are implicitly closed at garbage collection.

procedure: (socket? o)
-> bool

Predicate.

procedure: (stream-socket? o)
-> bool

True for SOCK_STREAM sockets

procedure: (socket-close socket)

Closes a socket

procedure: (socket-open? socket)
-> bool

True if the socket is still open

procedure: (socket-shutdown socket how)

Directional socket hutdown.

how is one of SHUT_RD, SHUT_WR, or SHUT_RDWR.

procedure: (socket-bind socket address)

Binds a socket to the specified address.

procedure: (socket-getsockname sock)
-> address

The local address of a bound socket.

Connections

procedure: (socket-listen socket backlog)

Listens for incoming connections in a stream oriented socket.

procedure: (socket-accept socket)
-> values: socket address
procedure: (socket-accept* socket)
-> values: socket address

Accepts an incoming connection in a stream-oriented socket.

socket-accept blocks until a connection is available.

socket-accept* is the non-blocking variant. Raises an exception with error code errno:EWOULDBLOCK if the operation cannot be completed immediately.

procedure: (socket-connect socket address)
procedure: (socket-connect* socket address)
-> bool

Connects a socket to a remote endpoint.

socket-connect* is the non-blocking variant. Returns #t if the connection can be immediately completed; otherwise, an asynchronous connection is initiated and returns #f. Completion can be synchronized with a socket-evt, with the result of the operation available through the SO_ERROR sockopt.

procedure: (socket-getpeername sock)
-> address

Returns the address of the peer in a connected socket.

Byte I/O

procedure: (socket-send socket bytes #:optional start end #:key flags)
-> int
procedure: (socket-send* socket bytes #:optional start end #:key flags)
-> int
procedure: (socket-sendto socket dest bytes #:optional start end #:key flags)
-> int
procedure: (socket-sendto* socket dest bytes #:optional start end #:key flags)
-> int

Send data from a byte string, starting at start and ending at end. The result is the number of bytes written.

socket-send* and socket-sendto* are non-blocking variants. They raise an exception with error code errno:EWOULDBLOCK if no data can be sent without blocking.

procedure: (socket-recv* socket bytes #:optional start end #:key flags)
-> int
procedure: (socket-recv socket bytes #:optional start end #:key flags)
-> int
procedure: (socket-recvfrom* socket bytes #:optional start end #:key flags)
-> values: int address
procedure: (socket-recvfrom socket bytes #:optional start end #:key flags)
-> values: int address

Receive data into a byte string, starting at start and ending at end.

socket-recv returns the number of bytes read, with 0 indicating a closed connection for stream oriented sockets.

socket-recvfrom returns two values, the number of bytes read and the address of the peer.

socket-recv* and socket-recvfrom* are non-blocking variants.

procedure: (socket-send-all socket bytes #:optional start end)
-> int

Sends all the data in a byte-string, starting at start and ending at end.

The operation blocks until all the data have been sent. The result is the number of bytes written.

procedure: (socket-recv-all socket bytes #:optional start end)

Receives all the data that can fit into a byte string, starting at start and ending at end..

The operation blocks until the byte string is full or the connection is closed for stream-oriented sockets. The result is the number of bytes read.

Port I/O

procedure: (socket-send/port socket input-port #:optional bufsz)
-> int

Sends all the data available through an input-port.

procedure: (socket-recv/port socket output-port #:optional bufsz)
-> int

Receives data and places them to an output port.

procedure: (socket->input-port socket)
-> input-port
procedure: (socket->output-port socket)
-> output-port

Creates a port that reads/writes from a stream socket.

procedure: (socket->ports sock)
-> values: input-port output-port

Creates a port pair for a stream socket.

procedure: (open-socket-stream address)
-> values: input-port output-port

Connects a SOCK_STREAM socket to an address and returns a port pair.

Control

procedure: (socket-sendmsg* socket #:key name data control flags)
-> int
procedure: (socket-sendmsg socket #:key name data control flags)
-> int
procedure: (socket-recvmsg* socket #:key name data control flags)
-> values: int int int int
procedure: (socket-recvmsg socket #:key name data control flags)
-> values: int int int int

Send/receive a message.

name, data, and control must be byte strings or #f. name corresponds to the msg_name field of a msghdr structure; data is mapped to msg_iov; control corresponds to msg_control.

socket-sendmsg/socket-sendmsg* return the number of bytes written.

socket-recvmsg/socket-recvmsg* return four values: number of bytes read, number of bytes placed to name, number of bytes placed to control, and the received message flags.

Currently only supported on UNIX-like systems.

Sockopts

procedure: (socket-setsockopt socket level option value)
procedure: (socket-getsockopt socket level option)
-> object

Performs setsockopt/getsockopt, translating from/to scheme objects.

procedure: (socket-setsockopt/bytes socket level option bytes)
procedure: (socket-getsockopt/bytes socket level option bytes)
-> int

Raw getsockopt/setsockopt using byte strings.

Addresses

Internet addresses (IPv4 and IPv6) are encapsulated in instances of inet-address. Unix domain addresses are represented as mzscheme paths.

Note: inet-address is a native extension type, so for equal? to work correctly you need mzscheme 369.8 or later.

Internet addresses

procedure: (inet4-address host port)
-> inet-address

Creates an IPv4 address. The host is a string or byte-string in dotted quad notation.

procedure: (inet6-address host port [flowinfo scope-id])
-> inet-address

Creates an IPv6 address.

procedure: (inet-address family bytes port)

Generic inet address constructor. The address family can be AF_INET or AF_INET6.

procedure: (inet-address-host inet-address)
-> bytes
procedure: (inet-address-port inet-address)
-> int

Basic field extractors

procedure: (inet-address? o)
procedure: (inet4-address? o)
procedure: (inet6-address? o)
procedure: (inet-address=? inet-address inet-address ...)

inet-address predicates

Binary representation

procedure: (inet-address->vector inet-address)
-> vector
procedure: (vector->inet-address vector)
-> inet-address

Exports/imports an inet-address to/from a platform independent representation.

procedure: (pack-address address)
-> bytes
procedure: (unpack-address bytes)
-> address

Packs/unpacks an inet-address or path to a binary struct representation suitable for raw low level operations.

Exceptions

System call failures generate exceptions that are instances of exn:socket.

The struct type is a subtype of exn:fail:network and encapsulates the error code from the failed system call.

Error codes that can be generated by socket interface syscall are available as errno:ERRORNAME. On windows, winsock errors are mapped to the equivalent POSIX names with cygwin semantics.

procedure: (exn:socket? o)
-> bool

Predicate for exn:socket instances.

procedure: (exn:socket:blocking? o)
-> bool

True if the object is an exn:socket with error code errno:EWOULDBLOCK

procedure: (exn:socket-errno e)
-> errno

Extracts the error code from a socket exception

Socket events

The library provices a native event type that can be syncrhonized with other mzscheme events. The event has semantics equivalent to select.

procedure: (socket-evt socket conditions)

Creates a syncronizable event, that is ready when the specified conditions would unblock a select call.

socket-evt:read
socket-evt:write
socket-evt:except

The basic event conditions. They can be mixed together with bitwise-ior.

socket-evt:connect

Equivalent to socket-evt:write | socket-evt:except

socket-evt:accept

Equivalent to socket-evt:read

Constants

Features

SOCKET_HAVE_RAW

True if raw sockets are available.

SOCKET_HAVE_IPV6

True if IPv6 is available.

SOCKET_HAVE_UNIX

True if UNIX domain sockets are available.

Fixed addresses

INADDR_ANY
INADDR_LOOPBACK
INADDR_BROADCAST
IN6ADDR_ANY
IN6ADDR_LOOPBACK

Fixed host addresses, provided as immutable byte strings.

Standard constants

The following standard constants are provided, depending on platform availability.

SHUT_RD
SHUT_WR
SHUT_RDWR
AF_UNSPEC
PF_UNSPEC
AF_INET
PF_INET
AF_INET6
PF_INET6
AF_UNIX
PF_UNIX
IPPROTO_IP
IPPROTO_IPV6
IPPROTO_TCP
IPPROTO_UDP
IPPROTO_ICMP
IPPROTO_RAW
SOCK_STREAM
SOCK_DGRAM
SOCK_RAW
SOL_SOCKET
SO_ACCEPTCONN
SO_BROADCAST
SO_DEBUG
SO_DONTROUTE
SO_ERROR
SO_KEEPALIVE
SO_OOBLINE
SO_PASSCRED
SO_RCVBUF
SO_SNDBUF
SO_RCVLOWAT
SO_SNDLOWAT
SO_RCVTIMEO
SO_SNDTIMEO
SO_REUSEADDR
SO_REUSEPORT
SO_TYPE
SO_TIMESTAMP
SO_USELOOPBACK
IP_ADD_MEMBERSHIP
IP_DROP_MEMBERSHIP
IP_HDRINCL
IP_MTU
IP_MTU_DISCOVER
IP_MULTICAST_IF
IP_MULTICAST_LOOP
IP_MULTICAST_TTL
IP_OPTIONS
IP_PKTINFO
IP_RECVDSTADDR
IP_RECVIF
IP_RECVTOS
IP_RECVTTL
IP_TOS
IP_TTL
IPV6_ADD_MEMBERSHIP
IPV6_DROP_MEMBERSHIP
IPV6_MULTICAST_HOPS
IPV6_MULTICAST_IF
IPV6_MULTICAST_LOOP
IPV6_MTU
IPV6_MTU_DISCOVER
IPV6_CHECKSUM
IPV6_PKTINFO
IPV6_RECVERR
IPV6_UNICAST_HOPS
IPV6_RTHDR
IPV6_AUTHHDR
IPV6_DSTOPTS
IPV6_HOPOPTS
IPV6_FLOWINFO
IPV6_HOPLIMIT
TCP_KEEPALIVE
TCP_KEEPIDLE
TCP_KEEPINTVL
TCP_MAXRT
TCP_MAXSEG
TCP_NODELAY
TCP_SYNCNT
IP_PMTUDISC_WANT
IP_PMTUDISC_DONT
IP_PMTUDISC_DO
IPV6_PKTOPTIONS
IPTOS_LOWDELAY
IPTOS_THROUGHPUT
IPTOS_RELIABILITY
IPTOS_MINCOST
MSG_DONTROUTE
MSG_DONTWAIT
MSG_MORE
MSG_OOB

Error codes

errno:EACCES
errno:EAFNOSUPPORT
errno:EPFNOSUPPORT
errno:EPROTONOSUPPORT
errno:ENOBUFS
errno:EFAULT
errno:EADDRINUSE
errno:EINVAL
errno:ENOTSOCK
errno:EOPNOTSUPP
errno:EWOULDBLOCK
errno:ECONNABORTED
errno:EMFILE
errno:ETIMEDOUT
errno:ESOCKTNOSUPPORT
errno:EALREADY
errno:ECONNREFUSED
errno:EISCONN
errno:ENETUNREACH
errno:ECONNRESET
errno:EDESTADDRREQ
errno:EMSGSIZE
errno:ENOTCONN
errno:EPROTOTYPE
errno:ENOPROTOOPT
errno:EINPROGRESS
errno:EADDRNOTAVAIL
errno:ENETDOWN
errno:ENETRESET
errno:ESHUTDOWN
errno:EHOSTDOWN
errno:EHOSTUNREACH
errno:ENOMEM
errno:ENFILE
errno:EBADF
errno:EPIPE

Examples

A URL fetcher

A function that fetches a url and prints the result to the current output port:

(define (get-url what)
  (let* ((url (string->url what))
         (host (dns-get-address (dns-find-nameserver) (url-host url)))
         (port (or (url-port url) 80))
         (sock (socket)))
    (socket-connect sock (inet4-address host port))
    (socket-send-all sock (url->request url))
    (socket-shutdown sock SHUT_WR)
    (socket-recv/port sock (current-output-port))
    (socket-close sock)))

The function uses standard library modules from the net collection and a simple HTTP/1.0 request constructor:

(require (lib "url.ss" "net")
         (lib "dns.ss" "net"))

(define (url->request url)
  (string->bytes/utf-8 (format "GET ~a HTTP/1.0\r\n\r\n" (url->string url))))

The same function implemented using socket ports:

(require (only (lib "port.ss") copy-port))
(define (get-url/stream what)
  (let* ((url (string->url what))
         (host (dns-get-address (dns-find-nameserver) (url-host url)))
         (port (or (url-port url) 80)))
    (let-values (((inp outp) (open-socket-stream (inet4-address host port))))
      (write-bytes (url->request url) outp)
      (close-output-port outp)
      (copy-port inp (current-output-port)))))

An echo server

Ageneric echo server implementation:

(define (echo-server domain addr)
  (let ((sock (socket domain SOCK_STREAM)))
    (socket-setsockopt sock SOL_SOCKET SO_REUSEADDR #t)
    (socket-bind sock addr)
    (socket-listen sock 5)
    (let lp ()
      (let-values (((clisock cliaddr) (socket-accept sock)))
        (thread (lambda () (echo clisock cliaddr)))
        (lp)))))

The function accepts a socket domain and an address as arguments. Client connections are processed by the function echo:

(define (echo sock addr)
  (let ((buf (make-bytes 4096)))
    (let lp ()
      (let ((ilen (socket-recv sock buf)))
        (unless (= ilen 0)
          (socket-send-all sock buf 0 ilen)
          (lp)))))
  (socket-close sock))

To run the server on port 5000:

(echo-server PF_INET (inet4-address INADDR_ANY 5000))

If you want to limit connections to localhost only, then bind to INADDR_LOOPBACK:

(echo-server PF_INET (inet4-address INADDR_LOOPBACK 5000))

You can interact with the server with telnet:

telnet localhost 5000

Or you can simply connect a port-pair to it:

(let-values (((in out) (open-socket-stream (inet4-address #"127.0.0.1" 5000))))
  (write '(hello world) out)
  (read in))
=> (hello world)

The server can run on a unix domain socket as well:

(echo-server PF_UNIX (string->path "/tmp/echosrv"))

UDP echos

A UDP echo server:

(define (udp-echo-server port)
  (let ((sock (socket PF_INET SOCK_DGRAM))
        (buf (make-bytes 1500)))
    (socket-setsockopt sock SOL_SOCKET SO_REUSEADDR #t)
    ;; receive broadcasts too
    (socket-setsockopt sock SOL_SOCKET SO_BROADCAST #t)
    (socket-bind sock (inet4-address INADDR_ANY port))
    (let lp ()
      (let-values (((ilen peer) (socket-recvfrom sock buf)))
        (socket-sendto sock peer buf 0 ilen)
        (lp)))))

A function that sends a message to a udp-echo-server and waits for the reply with a timeout:

(define (udp-echo-sendto dest timeout msg)
  (let* ((sock (socket PF_INET SOCK_DGRAM))
         (buf (make-bytes (bytes-length msg))))
    (socket-sendto sock dest msg)
    (sync/timeout timeout
      (handle-evt (socket-evt sock socket-evt:read)
        (lambda x 
          (let-values (((ilen peer) (socket-recvfrom sock buf)))
            (values peer buf)))))))

A function that uses broadcast to find a udp echo server in the LAN:

(define (udp-echo-find port timeout)
  (let* ((sock (socket PF_INET SOCK_DGRAM))
         (buf (make-bytes 8)))
    (socket-setsockopt sock SOL_SOCKET SO_BROADCAST #t)
    (socket-sendto sock (inet4-address INADDR_BROADCAST port) #"hello")
    (sync/timeout timeout
      (handle-evt (socket-evt sock socket-evt:read)
        (lambda x 
          (let-values (((ilen peer) (socket-recvfrom sock buf))) peer))))))