#lang scribble/doc @(require scribble/manual scribble/basic ;; planet croacks with the natives ;;(for-label "main.ss" scheme scheme/port net/url net/dns )) @title{mzsocket} @;@table-of-contents{} @section{Overview} mzsocket is a native extension library for PLT-scheme, that provides the BSD/POSIX sockets interface. It supports IPv4, IPv6, Unix domain, and raw sockets depending on availability on the host platform. @subsection{Installation} To use the library through PLaneT: @schemeinput[ (require (planet vyzo/socket)) ] To locally install the library, extract the library archive to your collects directory and run @commandline{setup-plt -l socket} To use the local library: @schemeinput[ (require socket) ] To run basic tests on the library: @schemeinput[(require (only socket/test run-tests))] @schemeinput[(run-tests)] @subsection{License} (C) Copyright 2007,2008 Dimitris Vyzovitis mzsocket 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 3 of the License, or (at your option) any later version. mzsocket 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 mzsocket. If not, see @link["http://www.gnu.org/licenses/"]{}. @section{API} @defmodule[socket] The @schemeid[socket] API closely follows the native POSIX/BSD API. In general, all operations that might block come in two variants @schemeid[op] and @schemeid[op*]. The blocking variant @schemeid[op] blocks the current scheme thread until the system call completes. The non-blocking variant @schemeid[op*] raises an exception with error code @scheme[EWOULDBLOCK] if the operation cannot be completed (see @secref{exn}). Non-blocking operations can be synchronized in a matter similar to @tt{select} using events and @scheme[sync] (see @secref{sync}) . @subsection{Sockets} @subsubsection{Primitives} @defproc[(socket (family fixnum? PF_INET) (type fixnum? SOCK_STREAM) (proto fixnum? 0)) socket?]{ Socket constructor. Creates a new socket, registered with the current custodian. Sockets are implicitly closed when they become unreachable. } @defproc[(socket? (o any)) boolean?]{ Socket predicate. } @defproc[(stream-socket? (o any)) boolean?]{ True for @scheme[SOCK_STREAM] sockets. } @defproc[(socket-close (o socket?)) void?]{ Explicitly close a socket. } @defproc[(socket-closed? (o socket?)) boolean?]{ True if the socket @scheme[o] has been closed. } @subsubsection{Address Binding} @defproc[(socket-bind (o socket?) (addr address)) void?]{ Explicitly bind a socket to a specific address. } @defproc[(socket-getsockname (o socket?)) address]{ The local address of a bound socket. } @defproc[(socket-getpeername (o socket?)) address]{ The remote address of a connected socket. } @subsubsection{Connections} @defproc[(socket-listen (o socket?) (backlog fixnum?)) void?]{ Listens for incoming connections in a stream socket. } @deftogether[( @defproc[(socket-accept (o socket?)) (values socket? address)] @defproc[(socket-accept* (o socket?)) (values socket? address)] )]{ Accept an incoming connection in a stream socket. } @deftogether[( @defproc[(socket-connect (o socket?) (peer address)) void?] @defproc[(socket-connect* (o socket?) (peer address)) boolean?] )]{ Connect a socket to a remote endpoint. The non-blocking variant @schemeid[socket-connect*] returns @scheme[#t] if the connection can be immediately completed; otherwise, an asynchronous connection is initiated and @scheme[#f] is returned. } @defproc[(socket-shutdown (o socket?) (how fixnum?)) void?]{ Directional socket shutdown. @scheme[how] must be one of @scheme[SHUT_RD], @scheme[SHUT_WR], @scheme[SHUT_RDWR]. } @subsubsection{Binary I/O} @deftogether[( @defproc[(socket-send (o socket?) (x bytes?) (b fixnum? 0) (e fixnum? (bytes-length x)) (flags fixnum? 0)) fixnum?] @defproc[(socket-send* (o socket?) (x bytes?) (b fixnum? 0) (e fixnum? (bytes-length x)) (flags fixnum? 0)) fixnum?] @defproc[(socket-sendto (o socket?) (peer address) (x bytes?) (b fixnum? 0) (e fixnum? (bytes-length x)) (flags fixnum? 0)) fixnum?] @defproc[(socket-sendto* (o socket?) (peer address) (x bytes?) (b fixnum? 0) (e fixnum? (bytes-length x)) (flags fixnum? 0)) fixnum?] )]{ Send the bytes contained in @scheme[x][@scheme[b] @scheme[e]). Return the number of bytes written. } @deftogether[( @defproc[(socket-recv (o socket?) (x bytes?) (b fixnum? 0) (e fixnum? (bytes-length x)) (flags fixnum? 0)) fixnum?] @defproc[(socket-recv* (o socket?) (x bytes?) (b fixnum? 0) (e fixnum? (bytes-length x)) (flags fixnum? 0)) fixnum?] @defproc[(socket-recvfrom (o socket?) (x bytes?) (b fixnum? 0) (e fixnum? (bytes-length x)) (flags fixnum? 0)) (values fixnum? address)] @defproc[(socket-recvfrom* (o socket?) (x bytes?) (b fixnum? 0) (e fixnum? (bytes-length x)) (flags fixnum? 0)) (values fixnum? address)] )]{ Receive data into the buffer delimited by @scheme[x][@scheme[b] @scheme[e]). Return the number of bytes read and, in the case of @schemeid[socket-recvfrom]/@schemeid[socket-recvfrom*] the peer address as a second value. For stream sockets, a value of @scheme[0] indicates a peer shutdown. } @deftogether[( @defproc[(socket-send-all (o socket?) (x bytes?) (b fixnum? 0) (e fixnum? (bytes-length x))) fixnum?] @defproc[(socket-recv-all (o socket?) (x bytes?) (b fixnum? 0) (e fixnum? (bytes-length x))) fixnum?] )]{ Send (receive) all the data in the buffer delimited by @scheme[x][@scheme[b] @scheme[e]), blocking if necessary. Return the number of bytes sent (received), which may be less than @scheme[(- e b)] if an error occurs or the peer has shutdown the socket. } @deftogether[( @defproc[(socket-send/port (o socket?) (inp input-port?) (bufsz fixnum? 4096)) fixnum?] @defproc[(socket-recv/port (o socket?) (outp output-port?) (bufsz fixnum? 4096)) fixnum?] )]{ Copy the contents of a port to a socket (and vice versa) with a buffer size @scheme[bufsz]. Return the (total) number of bytes copied. } @subsubsection{Port I/O} @deftogether[( @defproc[(socket-input-port (o socket?)) input-port?] @defproc[(socket-output-port (o socket?)) output-port?] )]{ Create input and output ports tied to a stream socket. } @defproc[(open-socket-stream (peer address)) (values input-port? output-port?)]{ Opens a client stream connected to @schemeid[peer]. Returns input and output ports for the stream. } @subsubsection{Control I/O} @margin-note{Supported only in UNIX-like systems} @deftogether[( @defproc[(socket-sendmsg (o socket?) (name (or bytes? #f)) (data (or bytes? #f)) (control (or bytes? #f)) (flags fixnum? 0)) fixnum?] @defproc[(socket-sendmsg* (o socket?) (name (or bytes? #f)) (data (or bytes? #f)) (control (or bytes? #f)) (flags fixnum? 0)) fixnum?] )]{ Send a raw message with control parameters. Return the number of bytes written. } @deftogether[( @defproc[(socket-recvmsg (o socket?) (name (or bytes? #f)) (data (or bytes? #f)) (control (or bytes? #f)) (flags fixnum? 0)) fixnum?] @defproc[(socket-recvmsg* (o socket?) (name (or bytes? #f)) (data (or bytes? #f)) (control (or bytes? #f)) (flags fixnum? 0)) (values fixnum? (or fixnum? #f) (or fixnum? #f) fixnum?)] )]{ Receive a raw message with control parameters. Return 4 values: @itemize{ @item{The length of the payload, placed in the @scheme[data] buffer} @item{The length of the peer name, placed in the @scheme[name] buffer when applicable} @item{The length of the control data, placed in the @scheme[control] buffer when applicable} @item{The received message flags} } } @subsubsection{Sockopts} @deftogether[( @defproc[(socket-getsockopt (o socket?) (level fixnum?) (option fixnum?)) any] @defproc[(socket-setsockopt (o socket?) (level fixnum?) (option fixnum?) (value any)) void?] )]{ Performs getsockopt/setsockopt, translating between raw option values and scheme objects. See the file @tt{sockopt.in} in the source distribution for the sockopts supported with value translation. } @deftogether[( @defproc[(socket-getsockopt-raw (o socket?) (level fixnum?) (option fixnum?) (value bytes?)) fixnum?] @defproc[(socket-setsockopt-raw (o socket?) (level fixnum?) (option fixnum?) (value bytes?)) void?] )]{ Raw getsockopt/setsockopt. } @subsubsection[#:tag "sync"]{Synchronization} @defproc[(socket-recv-evt (o socket?)) evt?]{ Create an event that is ready when data can be immediately received from the socket. The result of the synchronization is the event itself. } @defproc[(socket-send-evt (o socket?)) evt?]{ Create an event that is ready when data can be immediately sent through the socket. The result of the synchronization is the event itself. } @defproc[(socket-connect-evt (o socket?)) evt?]{ Create an event that is ready when an asynchronous connection initiated with @scheme[socket-connect*] completes. If the connection completes without error the result of the synchronization is the socket itself; if the connection fails the result is an exception. } @defproc[(socket-accept-evt (o socket?)) evt?]{ Creates an event that is ready when an connection can be accepted. The result of the synchronization is a new client socket if a connection is accepted successfully or an exception if the system fails to accept the new connection. } @subsection{Addresses} Internet addresses (IPv4 and IPv6) are encapsulated in instances of a native @schemeid[inet-address] type, while UNIX domain addresses are represented as a scheme @schemeid[path]. @defproc*[(((inet-address (family fixnum?) (host bytes?) (port fixnum?)) inet-address?) ((inet-address (family fixnum?) (host bytes?) (port fixnum?) (flowinfo fixnum?) (scopeid fixnum?)) inet-address?))]{ Raw @schemeid[inet-address] constructor. @scheme[family] must be @scheme[AF_INET] or @scheme[AF_INET6]. @scheme[host] must be a parseable by @tt{inet_pton}. @scheme[flowinfo] and @scheme[scopeid] are only meaningful for IPv6 addresses. } @deftogether[( @defproc[(inet4-address (host (or bytes? string?)) (port fixnum?)) inet-address?] @defproc[(inet6-address (host (or bytes? string?)) (port fixnum?) ...) inet-address?] )]{ Helper constructors for IPv4 and IPv6 addresses. } @deftogether[( @defproc[(inet-address? (o any)) boolean?] @defproc[(inet4-address? (o any)) boolean?] @defproc[(inet6-address? (o any)) boolean?] )]{ @schemeid[inet-address] predicates } @deftogether[( @defproc[(inet-address-family (o inet-address?)) fixnum?] @defproc[(inet-address-host (o inet-address?)) bytes?] @defproc[(inet-address-port (o inet-address?)) fixnum?] )]{ @schemeid[inet-address] field extractors. } @defproc[(inet-address=? (o inet-address?) ...+) boolean?]{ @schemeid[inet-address] equality test } @deftogether[( @defproc[(inet-address->vector (o inet-address?)) vector?] @defproc[(vector->inet-address (o vector?)) inet-address] )]{ Convert an @schemeid[inet-address] to/from a vector of the form @scheme[#(AF_INET addr port)] or @scheme[#(AF_INET6 addr port flowinfo scopeid)], suitable for marshalling. } @deftogether[( @defproc[(pack-address (o address)) bytes?] @defproc[(unpack-address (o bytes?)) address] )]{ Raw address packing and unpacking. The binary representation is a direct copy of the underlying @tt{sockaddr} struct, and thus platform-dependent. It is mainly useful for control I/O and sockopts. } @subsection[#:tag "exn"]{Exceptions} @defstruct[exn:fail:socket ((errno fixnum?))]{ Subtype of of @scheme[exn:fail:network]. Instances are raised by the mzsocket API on syscall errors. @scheme[errno] contains the value of the system @tt{errno} related to the error. } @defproc[(exn:fail:socket:blocking? (o any)) boolean?]{ True for instances of @schemeid[exn:fail:socket] with error code @scheme[EWOULDBLOCK]. } @subsection{Constants} @subsubsection{Features} @defthing[SOCKET_HAVE_RAW boolean?]{ True if raw sockets are available. } @defthing[SOCKET_HAVE_IPV6 boolean?]{ True if IPv6 is available. } @defthing[SOCKET_HAVE_UNIX boolean?]{ True if UNIX domain sockets are available. } @subsubsection{Fixed Addresses} @deftogether[( @defthing[INADDR_ANY bytes?] @defthing[INADDR_LOOPBACK bytes?] @defthing[INADDR_BROADCAST bytes?] @defthing[IN6ADDR_ANY bytes?] @defthing[IN6ADDR_LOOPBACK bytes?] )]{ Fixed host addresses, provided as immutable byte strings. } @subsubsection{Standard Constants} @deftogether[( @defthing[SHUT_RD fixnum?] @defthing[SHUT_WR fixnum?] @defthing[SHUT_RDWR fixnum?] @defthing[AF_UNSPEC fixnum?] @defthing[PF_UNSPEC fixnum?] @defthing[AF_INET fixnum?] @defthing[PF_INET fixnum?] @defthing[AF_INET6 fixnum?] @defthing[PF_INET6 fixnum?] @defthing[AF_UNIX fixnum?] @defthing[PF_UNIX fixnum?] @defthing[IPPROTO_IP fixnum?] @defthing[IPPROTO_IPV6 fixnum?] @defthing[IPPROTO_TCP fixnum?] @defthing[IPPROTO_UDP fixnum?] @defthing[IPPROTO_ICMP fixnum?] @defthing[IPPROTO_RAW fixnum?] @defthing[SOCK_STREAM fixnum?] @defthing[SOCK_DGRAM fixnum?] @defthing[SOCK_RAW fixnum?] @defthing[SOL_SOCKET fixnum?] @defthing[SO_ACCEPTCONN fixnum?] @defthing[SO_BROADCAST fixnum?] @defthing[SO_DEBUG fixnum?] @defthing[SO_DONTROUTE fixnum?] @defthing[SO_ERROR fixnum?] @defthing[SO_KEEPALIVE fixnum?] @defthing[SO_OOBLINE fixnum?] @defthing[SO_PASSCRED fixnum?] @defthing[SO_RCVBUF fixnum?] @defthing[SO_SNDBUF fixnum?] @defthing[SO_RCVLOWAT fixnum?] @defthing[SO_SNDLOWAT fixnum?] @defthing[SO_RCVTIMEO fixnum?] @defthing[SO_SNDTIMEO fixnum?] @defthing[SO_REUSEADDR fixnum?] @defthing[SO_REUSEPORT fixnum?] @defthing[SO_TYPE fixnum?] @defthing[SO_TIMESTAMP fixnum?] @defthing[SO_USELOOPBACK fixnum?] @defthing[IP_ADD_MEMBERSHIP fixnum?] @defthing[IP_DROP_MEMBERSHIP fixnum?] @defthing[IP_HDRINCL fixnum?] @defthing[IP_MTU fixnum?] @defthing[IP_MTU_DISCOVER fixnum?] @defthing[IP_MULTICAST_IF fixnum?] @defthing[IP_MULTICAST_LOOP fixnum?] @defthing[IP_MULTICAST_TTL fixnum?] @defthing[IP_OPTIONS fixnum?] @defthing[IP_PKTINFO fixnum?] @defthing[IP_RECVDSTADDR fixnum?] @defthing[IP_RECVIF fixnum?] @defthing[IP_RECVTOS fixnum?] @defthing[IP_RECVTTL fixnum?] @defthing[IP_TOS fixnum?] @defthing[IP_TTL fixnum?] @defthing[IPV6_ADD_MEMBERSHIP fixnum?] @defthing[IPV6_DROP_MEMBERSHIP fixnum?] @defthing[IPV6_MULTICAST_HOPS fixnum?] @defthing[IPV6_MULTICAST_IF fixnum?] @defthing[IPV6_MULTICAST_LOOP fixnum?] @defthing[IPV6_MTU fixnum?] @defthing[IPV6_MTU_DISCOVER fixnum?] @defthing[IPV6_CHECKSUM fixnum?] @defthing[IPV6_PKTINFO fixnum?] @defthing[IPV6_RECVERR fixnum?] @defthing[IPV6_UNICAST_HOPS fixnum?] @defthing[IPV6_RTHDR fixnum?] @defthing[IPV6_AUTHHDR fixnum?] @defthing[IPV6_DSTOPTS fixnum?] @defthing[IPV6_HOPOPTS fixnum?] @defthing[IPV6_FLOWINFO fixnum?] @defthing[IPV6_HOPLIMIT fixnum?] @defthing[TCP_KEEPALIVE fixnum?] @defthing[TCP_KEEPIDLE fixnum?] @defthing[TCP_KEEPINTVL fixnum?] @defthing[TCP_MAXRT fixnum?] @defthing[TCP_MAXSEG fixnum?] @defthing[TCP_NODELAY fixnum?] @defthing[TCP_SYNCNT fixnum?] @defthing[IP_PMTUDISC_WANT fixnum?] @defthing[IP_PMTUDISC_DONT fixnum?] @defthing[IP_PMTUDISC_DO fixnum?] @defthing[IPV6_PKTOPTIONS fixnum?] @defthing[IPTOS_LOWDELAY fixnum?] @defthing[IPTOS_THROUGHPUT fixnum?] @defthing[IPTOS_RELIABILITY fixnum?] @defthing[IPTOS_MINCOST fixnum?] @defthing[MSG_DONTROUTE fixnum?] @defthing[MSG_DONTWAIT fixnum?] @defthing[MSG_MORE fixnum?] @defthing[MSG_OOB fixnum?] )]{ Standard system constants, depending on platform availability. } @subsubsection{Error Codes} @deftogether[( @defthing[EACCES fixnum?] @defthing[EAFNOSUPPORT fixnum?] @defthing[EPFNOSUPPORT fixnum?] @defthing[EPROTONOSUPPORT fixnum?] @defthing[ENOBUFS fixnum?] @defthing[EFAULT fixnum?] @defthing[EADDRINUSE fixnum?] @defthing[EINVAL fixnum?] @defthing[ENOTSOCK fixnum?] @defthing[EOPNOTSUPP fixnum?] @defthing[EWOULDBLOCK fixnum?] @defthing[ECONNABORTED fixnum?] @defthing[EMFILE fixnum?] @defthing[ETIMEDOUT fixnum?] @defthing[ESOCKTNOSUPPORT fixnum?] @defthing[EALREADY fixnum?] @defthing[ECONNREFUSED fixnum?] @defthing[EISCONN fixnum?] @defthing[ENETUNREACH fixnum?] @defthing[ECONNRESET fixnum?] @defthing[EDESTADDRREQ fixnum?] @defthing[EMSGSIZE fixnum?] @defthing[ENOTCONN fixnum?] @defthing[EPROTOTYPE fixnum?] @defthing[ENOPROTOOPT fixnum?] @defthing[EINPROGRESS fixnum?] @defthing[EADDRNOTAVAIL fixnum?] @defthing[ENETDOWN fixnum?] @defthing[ENETRESET fixnum?] @defthing[ESHUTDOWN fixnum?] @defthing[EHOSTDOWN fixnum?] @defthing[EHOSTUNREACH fixnum?] @defthing[ENOMEM fixnum?] @defthing[ENFILE fixnum?] @defthing[EBADF fixnum?] @defthing[EPIPE fixnum?] )]{ Standard error codes } @section{Examples} @subsection{A URL fetcher} A function that fetches a url and prints the result to the current output port: @schemeblock[ (require net/url net/dns) (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))) ] To fetch googgle.com try: @schemeinput[(get-url "http://www.google.com")] The function uses standard library modules from the net collection and a trivial HTTP/1.0 request constructor: @schemeblock[ (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: @schemeblock[ (require scheme/port) (code:comment #, @t{for 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))))) ] @subsection{An echo server} A simple multi-threaded echo server implementation for stream sockets: @schemeblock[ (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))))) ] Client connections are proceesed by the function @schemeidfont{echo}: @schemeblock[ (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: @schemeinput[ (echo-server PF_INET (inet4-address INADDR_ANY 5000)) ] If you want to limit connections to localhost only, then bind to @schemeidfont{INADDR_LOOPBACK}: @schemeinput[ (echo-server PF_INET (inet4-address INADDR_LOOPBACK 5000)) ] You can now interact with the server via telnet: @commandline{telnet localhost 5000} Or you can simply connect a port-pair to it from an mzscheme shell: @schemeinput[ (let-values (((in out) (open-socket-stream (inet4-address #"127.0.0.1" 5000)))) (write '(hello world) out) (read in)) ] @schemeresult[=> (hello world)] The server can run on a unix domain socket as well: @schemeinput[ (echo-server PF_UNIX (string->path "/tmp/echosrv")) ] @subsection{UDP echos} A UDP echo server: @schemeblock[ (define (udp-echo-server port) (let ((sock (socket PF_INET SOCK_DGRAM)) (buf (make-bytes 1500))) (socket-setsockopt sock SOL_SOCKET SO_REUSEADDR #t) (code:comment #, @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: @schemeblock[ (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-recv-evt sock) (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: @schemeblock[ (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-recv-evt sock) (lambda (x) (let-values (((ilen peer) (socket-recvfrom sock buf))) peer)))))) ]