Title

Basic socket interface

Author

Takashi Kato

Status

This SRFI is currently in ``draft'' status. To see an explanation of each status that a SRFI can hold, see here. To provide input on this SRFI, please mail to <srfi minus 106 at srfi dot schemers dot org>. See instructions here to subscribe to the list. You can access previous messages via the archive of the mailing list.

Abstract

This document specifies basic socket interfaces.

Rationale

Many Scheme implementations have its own socket APIs however there are no portable way to write socket program. Therefore programmers need to provide implementation dependent layer for their programs.

This document specifies high and middle range of socket interfaces which are commonly used to write socket programming. That should make it easier to write portable programs that need to send or receive data from their socket.

Specification

All procedures defined in this SRFI may raise an error when the procedure failed because of the connection problem or other socket related problem. This document does not specify which condition should be raised. The end of this section, I put a table described which condition will be raised by some of the Scheme implementations.
Names defined in this document:
Constructors and predicate
      make-client-socket make-server-socket socket?
    
Socket operations
      socket-accept socket-send socket-recv
      socket-shutdown socket-close
    
Port conversion
      socket-input-port
      socket-output-port
    
Control feature
      call-with-socket
    
Flag operations
      address-family address-info 
      socket-domain ip-protocol
      message-type shutdown-method

      socket-merge-flags
      socket-purge-flags
    
Constant values
      *af-unspec* *af-inet* *af-inet6*
      *sock-stream* *sock-dgram*
      *ai-canonname* *ai-numerichost*
      *ai-v4mapped* *ai-all* *ai-addrconfig*
      *ipproto-ip* *ipproto-tcp* *ipproto-udp*
      *msg-peek* *msg-oob* *msg-waitall*
      *shut-rd* *shut-wr* *shut-rdwr*
    

The procedures

The procedure description uses following notation;
socketa socket object
bva bytevector
objectany value

Constructors and predicate

make-client-socket node service [ai-family [ai-socktype [ai-flags [ai-protocol]]]] -> socket
Returns a client socket connected to an Internet address.
The Internet address is identified by node and service. node and service must be string.
Example value of node: "localhost" "127.0.0.1"
Example value of service: "http" "80"
The optional parameter may specify the created socket's behaviour.
If the optional argument(s) is omitted, then following flags should be used as default.
ai-family
*af-inet*
ai-socktype
*sock-stream*
ai-flags
(socket-merge-flags *ai-v4mapped* *ai-addrconfig*)
ai-protocol
*ipproto-ip*
The created socket may not be closed automatically so it is users' responsibility to close it explicitly.
make-server-socket service [ai-family [ai-socktype [ai-protocol]]] -> socket
Returns a server socket waiting for connection.
The description of node argument is the same as make-client-socket.
The optional parameter may specify the created socket's behaviour.
If the optional argument(s) is omitted, then following flags should be used as default.
ai-family
*af-inet*
ai-socktype
*sock-stream*
ai-protocol
*ipproto-ip*
The created socket may not be closed automatically so it is users' responsibility to close it explicitly.
socket? object -> boolean
Returns #t if given object is socket object. Otherwise #f.

Socket operations

socket-accept socket -> socket
Wait for an incoming connection request, and returns a fresh connected client socket.
socket-send socket bv [flags] -> size
Sends a binary data block to a socket and returns the sent data size.
flags may specify the procedure's behaviour.
If the flags is omitted, the default value must be the result of following form;
(message-type none)
socket-recv socket size [flags] -> bv
Receives a binary data block from a socket. If zero length bytevector is returned, it means the peer connection is closed.
flags may specify the procedure's behaviour.
If the flags is omitted, the default value must be the result of following form;
(message-type none)
socket-shutdown socket how -> (unspecified)
Shutdowns a socket.
how must be one of the following constants;
  • *shut-rd*
  • *shut-wr*
  • *shut-rdwr*
socket-close socket -> (unspecified)
Closes a socket.
The procedure should not shutdown the given socket. To shutdown a socket, socket-shutdown should be called explicitly.

Port conversion

socket-input-port socket -> binary-input-port
socket-output-port socket -> binary-output-port
Returns a fresh binary input and output port associated with a socket, respectively.
The port should not close underlying socket when it's closing.

Control feature

call-with-socket socket proc -> object
Calls a given procedure with a given socket as an argument.
If given proc returns then it returns the result of proc and socket will be automatically closed. If proc doesn't return then given socket won't be closed automatically. It's analogy of call-with-port.

Flag operations

Following, address-family, address-info, socket-domain, ip-protocol, message-type and shutdown-method must be implemented as macros.
address-family name -> address-family
Returns proper address family from given name.
Implementation must support at least following names and must have the described behaviour.
inet
Returns *af-inet*
inet6
Returns *af-inet6*
unspec
Returns *af-unspec*
Implementation may support more names such as unix or local or other names.
address-info names ... -> address-info
Returns merged address info flags from given names.
Implementation must support at least following names and must have the described behaviour.
canoname
Returns *ai-canonname*
numerichost
Returns *ai-numerichost*
v4mapped
Returns *ai-v4mapped*
all
Returns *ai-all*
addrconfig
Returns *ai-addrconfig*
Implementation may support more names.
socket-domain name -> socket-domain
Returns socket domain flags from given name.
Implementation must support at least following names and must have the described behaviour.
stream
Returns *sock-stream*
datagram
Returns *sock-dgram*
Implementation may support more names.
ip-protocol name -> ip-protocol
Returns ip-protocol flag from given name.
Implementation must support at least following names and must have the described behaviour.
ip
Returns *ipproto-ip*
tcp
Returns *ipproto-tcp*
udp
Returns *ipproto-udp*
Implementation may support more names.
message-type names ... -> message-type
Returns message type flag from given name.
The flag can be used both socket-recv and socket-send.
Implementation must support at least following names and must have the described behaviour.
none
Returns no flag.
peek
Returns *msg-peek*
oob
Returns *msg-oob*
wait-all
Returns *msg-waitall*
Implementation may support more names.
shutdown-method names ... -> shutdown-method
Returns shutdown method flags from given names.
Implementation must support at least following names and must have the described behaviour.
read
Returns *shut-rd*
write
Returns *shut-wr*
If shutdown-method is given both read and write, then it must return *shut-rdwr*
socket-merge-flags flags ... -> new-flags
Merges given flags and returns a new flag.
socket-purge-flags base-flag flags ... -> new-flags
Removes flags from base-flag if exists and returns a new flag.

Constants

Implementation must support following constant variables.
All constant variable must be consistent with POSIX's[1] definition.

Address family

*af-inet*
Internet domain sockets for use with IPv4 addresses.
This must behave the same as POSIX's AF_INET.
*af-inet6*
Internet domain sockets for use with IPv6 addresses.
This must behave the same as POSIX's AF_INET6.
*af-unspec*
Unspecified.
This must behave the same as POSIX's AF_UNSPEC.

Socket domain

*sock-stream*
Byte-stream socket.
This must behave the same as POSIX's SOCK_STREAM.
*sock-dgram*
Datagram socket.
This must behave the same as POSIX's SOCK_DGRAM.

Address info

*ai-canonname*
This must behave the same as POSIX's AI_CANONNAME.
*ai-numerichost*
This must behave the same as POSIX's AI_NUMERICHOST.
*ai-v4mapped*
This must behave the same as POSIX's AI_V4MAPPED.
*ai-all*
This must behave the same as POSIX's AI_ALL.
*ai-addrconfig*
This must behave the same as POSIX's AI_ADDRCONFIG.

IP protocol

*ipproto-ip*
Internet protocol.
This must behave the same as POSIX's IPPROTO_IP.
*ipproto-tcp*
Transmission control protocol.
This must behave the same as POSIX's IPPROTO_TCP.
*ipproto-udp*
User datagram protocol.
This must behave the same as POSIX's IPPROTO_UDP.

Message type

*msg-peek*
For socket-recv.
Peeks at an incoming message. The data is treated as unread and the next socket-recv shall still return this data.
This must behave the same as POSIX's MSG_PEEK.
*msg-oob*
For both socket-recv and socket-send.
Requests/sends out-of-band data.
This must behave the same as POSIX's MSG_OOB.
*msg-waitall*
For socket-recv.
On sockets created with *sock-stream* flag, this requests the procedure block until the full amount of data ban be returned.
This must behave the same as POSIX's MSG_WAITALL.

Shutdown method

*shut-rd*
Disables further receive operation.
This must behave the same as POSIX's SHUT_RD.
*shut-wr*
Disables further send operations.
This must behave the same as POSIX's SHUT_WR.
*shut-rdwr*
Disables further send and receive operations.
This must behave the same as POSIX's SHUT_RDWR.

Conditions

These are some of conditions raised when networking error occurs.
Implementation nameCondition type
Gauche<system-error>
Larcenyno error, returns #f
Mosh&i/o-read
Racketexn:fail:network
Sagittarius&i/o
Ypsilon&i/o

Example

;; simple echo server
(import (rnrs) (srfi :106 socket))

(define echo-server-socket (make-server-socket "5000"))

(define (server-run)
  (define (get-line-from-binary-port bin)
    (utf8->string
     (call-with-bytevector-output-port
      (lambda (out)
        (let loop ((b (get-u8 bin)))
          (case b
            ((#xA) #t) ;; newline
            ((#xD) (loop (get-u8 bin))) ;; carriage return
            (else (put-u8 out b) (loop (get-u8 bin))))))))
  (call-with-socket (socket-accept echo-server-socket)
    (lambda (sock)
      (let ((in (socket-input-port sock))
            (out (socket-output-port sock)))
        (let lp2 ((r (get-line-from-binary-port in)))
          (put-bytevector out (string-<utf8 (string-append r "\r\n")))
          (lp2 (get-line-from-binary-port in)))))))
(server-run)

;; simple echo client
(import (rnrs) (srfi :106 socket))
(define client-socket (make-client-socket "localhost" "5000"
                                          (address-family inet)
                                          (socket-domain stream)
                                          (address-info v4mapped addrconfig)
                                          (ip-protocol ip)))
(socket-send client-socket (string->utf8 "hello\r\n"))
(socket-recv client-socket (string-length "hello\r\n"))

Implementation

The following implementation is written in R6RS.

Interface layer

(library (srfi :106 socket)
    (export make-client-socket make-server-socket
            socket? 
            (rename (socket-port socket-input-port)
                    (socket-port socket-output-port))
            call-with-socket
            socket-merge-flags socket-purge-flags
            socket-accept socket-send socket-recv socket-shutdown socket-close
            *af-unspec* *af-inet* *af-inet6*
            *sock-stream* *sock-dgram*
            *ai-canonname* *ai-numerichost*
            *ai-v4mapped* *ai-all* *ai-addrconfig*
            *ipproto-ip* *ipproto-tcp* *ipproto-udp*
            *shut-rd* *shut-wr* *shut-rdwr*
            address-family socket-domain address-info
            ip-protocol message-type shutdown-method)
    (import (rnrs) (except (socket impl) socket-port))

  (define %address-family `((inet    ,*af-inet*)
                            (inet6   ,*af-inet6*)
                            (unspec  ,*af-unspec*)))
  (define %address-info `((canoname     ,*ai-canonname*)
                          (numerichost  ,*ai-numerichost*)
                          (v4mapped     ,*ai-v4mapped*)
                          (all          ,*ai-all*)
                          (addrconfig   ,*ai-addrconfig*)))

  (define %ip-protocol `((ip  ,*ipproto-ip*)
                         (tcp ,*ipproto-tcp*)
                         (udp ,*ipproto-udp*)))

  (define %socket-domain `((stream   ,*sock-stream*)
                           (datagram ,*sock-dgram*)))

  (define %message-types `((none 0)
                           (peek ,*msg-peek*)
                           (oob  ,*msg-oob*)
                           (wait-all ,*msg-waitall*)))

  (define (lookup who sets name)
    (cond ((assq name sets) => cadr)
          (else (assertion-violation who "no name defined" name)))

  (define-syntax address-family
    (syntax-rules ()
      ((_ name)
       (lookup 'address-family %address-family 'name))))

  (define-syntax address-info
    (syntax-rules ()
      ((_ names ...)
       (apply socket-merge-flags 
              (map (lambda (name) (lookup 'address-info %address-info name))
                   '(names ...))))))

  (define-syntax socket-domain
    (syntax-rules ()
      ((_ name)
       (lookup 'socket-domain %socket-domain 'name))))

  (define-syntax ip-protocol
    (syntax-rules ()
      ((_ name)
       (lookup 'ip-protocol %ip-protocol 'name))))

  (define-syntax message-type
    (syntax-rules ()
      ((_ names ...)
       (apply socket-merge-flags 
              (map (lambda (name) (lookup 'message-type %message-types name))
                   '(names ...))))))

  (define (%proper-method methods)
    (define allowed-methods '(read write))
    (define (check-methods methods)
      (let loop ((methods methods) (seen '()))
        (cond ((null? methods))
              ((memq (car methods) allowed-methods)
               => (lambda (m)
                    (if (memq (car m) seen)
                        (assertion-violation 'shutdown-method
                                             "duplicate method" m)
                        (loop (cdr methods) (cons (car m) seen)))))
              (else (assertion-violation 'shutdown-method
                                         "unknown method" (car methods)))))
    (check-methods methods)
    (if (null? (cdr methods))
        (case (car methods)
          ((read) *shut-rd*)
          ((write) *shut-wr*))
        *shut-rdwr*))

  (define-syntax shutdown-method
    (syntax-rules ()
      ((_ methods ...)
       (%proper-method '(methods ...)))))

  (define (socket-port socket)
    (define (read! bv start count)
      (let ((r (socket-recv socket count)))
        (bytevector-copy! r 0 bv start (bytevector-length r))
        (bytevector-length r)))
    (define (write! bv start count)
      (let ((buf (make-bytevector count)))
        (bytevector-copy! bv start buf 0 count)
        (socket-send socket buf)))
    (make-custom-binary-input/output-port 
         "socket-port" read! write! #f #f #f))

  )

Implementation dependent layer

For Ypsilon

(library (srfi :106 socket impl)
    (export make-client-socket make-server-socket
            socket? socket-port call-with-socket
            (rename (bitwise-ior socket-merge-flags)
                    (bitwise-xor socket-purge-flags))
            socket-accept socket-send socket-recv socket-shutdown socket-close
            (rename (AF_UNSPEC *af-unspec*)
                    (AF_INET   *af-inet*)
                    (AF_INET6  *af-inet6*))
            (rename (SOCK_STREAM *sock-stream*)
                    (SOCK_DGRAM  *sock-dgram*))
            (rename (AI_CANONNAME   *ai-canonname*)
                    (AI_NUMERICHOST *ai-numerichost*)
                    (AI_V4MAPPED    *ai-v4mapped*)
                    (AI_ALL         *ai-all*)
                    (AI_ADDRCONFIG  *ai-addrconfig*))
            (rename (IPPROTO_IP  *ipproto-ip*)
                    (IPPROTO_TCP *ipproto-tcp*)
                    (IPPROTO_UDP *ipproto-udp*))
            (rename (MSG_PEEK     *msg-peek*)
                    (MSG_OOB      *msg-oob*)
                    (MSG_WAITALL  *msg-waitall*))
            (rename (SHUT_RD   *shut-rd*)
                    (SHUT_WR   *shut-wr*)
                    (SHUT_RDWR *shut-rdwr*)))
    (import (rnrs) (rename (ypsilon socket)
                           (socket-send %socket-send)        
                           (socket-recv %socket-recv)))

  (define IPPROTO_IP 0)
  (define IPPROTO_TCP 6)
  (define IPPROTO_UDP 17)

  (define (socket-send socket bv . flags)
    (let ((flags (if (null? flags) 0 (car flags))))
      (%socket-send socket bv flags)))

  (define (socket-recv socket size . flags)
    (let ((flags (if (null? flags) 0 (car flags))))
      (%socket-recv socket size flags)))
)

For Mosh

(library (srfi :106 socket impl)
    (export make-client-socket make-server-socket
            socket? socket-port call-with-socket
            (rename (bitwise-ior socket-merge-flags)
                    (bitwise-xor socket-purge-flags))
            socket-accept socket-send socket-recv socket-shutdown socket-close
            (rename (AF_UNSPEC *af-unspec*)
                    (AF_INET   *af-inet*)
                    (AF_INET6  *af-inet6*))
            (rename (SOCK_STREAM *sock-stream*)
                    (SOCK_DGRAM  *sock-dgram*))
            (rename (AI_CANONNAME   *ai-canonname*)
                    (AI_NUMERICHOST *ai-numerichost*)
                    (AI_V4MAPPED    *ai-v4mapped*)
                    (AI_ALL         *ai-all*)
                    (AI_ADDRCONFIG  *ai-addrconfig*))
            (rename (IPPROTO_IP  *ipproto-ip*)
                    (IPPROTO_TCP *ipproto-tcp*)
                    (IPPROTO_UDP *ipproto-udp*))
            (rename (MSG_PEEK     *msg-peek*)
                    (MSG_OOB      *msg-oob*)
                    (MSG_WAITALL  *msg-waitall*))
            (rename (SHUT_RD   *shut-rd*)
                    (SHUT_WR   *shut-wr*)
                    (SHUT_RDWR *shut-rdwr*)))
    (import (rnrs) (mosh socket))
  (define IPPROTO_IP 0)
  (define MSG_OOB 1)
  (define MSG_PEEK 2)
  (define MSG_WAITALL 8)
  )

For Sagittarius

(library (srfi :106 socket impl)
    (export make-client-socket make-server-socket
            socket? socket-port call-with-socket
            (rename (bitwise-ior socket-merge-flags)
                    (bitwise-xor socket-purge-flags))
            socket-accept socket-send socket-recv socket-shutdown socket-close
            (rename (AF_UNSPEC *af-unspec*)
                    (AF_INET   *af-inet*)
                    (AF_INET6  *af-inet6*))
            (rename (SOCK_STREAM *sock-stream*)
                    (SOCK_DGRAM  *sock-dgram*))
            (rename (AI_CANONNAME   *ai-canonname*)
                    (AI_NUMERICHOST *ai-numerichost*)
                    (AI_V4MAPPED    *ai-v4mapped*)
                    (AI_ALL         *ai-all*)
                    (AI_ADDRCONFIG  *ai-addrconfig*))
            (rename (IPPROTO_IP  *ipproto-ip*)
                    (IPPROTO_TCP *ipproto-tcp*)
                    (IPPROTO_UDP *ipproto-udp*))
            (rename (MSG_PEEK     *msg-peek*)
                    (MSG_OOB      *msg-oob*)
                    (MSG_WAITALL  *msg-waitall*))
            (rename (SHUT_RD   *shut-rd*)
                    (SHUT_WR   *shut-wr*)
                    (SHUT_RDWR *shut-rdwr*)))
    (import (rnrs) (rename (sagittarius socket)
                           (socket-send %socket-send)
                           (socket-recv %socket-recv)))

  (define IPPROTO_IP  0)
  (define IPPROTO_TCP 6)
  (define IPPROTO_UDP 17)

  (define (socket-send socket bv :optional (flags 0))
    (%socket-send socket bv flags))

  (define (socket-recv socket size :optional (flags 0))
    (%socket-recv socket size flags))
  )

For others

(library (srfi :106 socket impl)
    (export make-client-socket make-server-socket
            socket? socket-port call-with-socket
            socket-accept socket-send socket-recv socket-shutdown socket-close
            (rename (AF_UNSPEC *af-unspec*)
                    (AF_INET   *af-inet*)
                    (AF_INET6  *af-inet6*))
            (rename (SOCK_STREAM *sock-stream*)
                    (SOCK_DGRAM  *sock-dgram*))
            (rename (AI_CANONNAME   *ai-canonname*)
                    (AI_NUMERICHOST *ai-numerichost*)
                    (AI_V4MAPPED    *ai-v4mapped*)
                    (AI_ALL         *ai-all*)
                    (AI_ADDRCONFIG  *ai-addrconfig*))
            (rename (IPPROTO_IP  *ipproto-ip*)
                    (IPPROTO_TCP *ipproto-tcp*)
                    (IPPROTO_UDP *ipproto-udp*))
            (rename (MSG_PEEK     *msg-peek*)
                    (MSG_OOB      *msg-oob*)
                    (MSG_WAITALL  *msg-waitall*))
            (rename (SHUT_RD   *shut-rd*)
                    (SHUT_WR   *shut-wr*)
                    (SHUT_RDWR *shut-rdwr*)))
    (import (rnrs))

  (define-syntax define-unsupported
    (syntax-rules ()
      ((_ (name))
       (define (name . _)
         (raise 
          (condition (make-implementation-restriction-violation)
                     (make-who-condition 'name)
                     (make-message-condition
                      "This SRFI is not supported on this implementation")))))
      ((_ name)
       (define name #f))))

  (define-unsupported (make-client-socket))
  (define-unsupported (make-server-socket))
  (define-unsupported (socket?           ))
  (define-unsupported (socket-port       ))
  (define-unsupported (call-with-socket  ))
  (define-unsupported (socket-accept     ))
  (define-unsupported (socket-send       ))
  (define-unsupported (socket-recv       ))
  (define-unsupported (socket-shutdown   ))
  (define-unsupported (socket-close      ))

  (define-unsupported AF_UNSPEC     )
  (define-unsupported AF_INET       )
  (define-unsupported AF_INET6      )
  (define-unsupported SOCK_STREAM   )
  (define-unsupported SOCK_DGRAM    )
  (define-unsupported AI_CANONNAME  )
  (define-unsupported AI_NUMERICHOST)
  (define-unsupported AI_V4MAPPED   )
  (define-unsupported AI_ALL        )
  (define-unsupported AI_ADDRCONFIG )

  (define-unsupported IPPROTO_IP)
  (define-unsupported IPPROTO_TCP)
  (define-unsupported IPPROTO_UDP)

  (define-unsupported MSG_OOB)
  (define-unsupported MSG_PEEK)
  (define-unsupported MSG_WAITALL)

  (define-unsupported SHUT_RD)
  (define-unsupported SHUT_WR)
  (define-unsupported SHUT_RDWR)
)

References

[1] The Open Group Base Specifications Issue 7
    http://pubs.opengroup.org/onlinepubs/9699919799/nframe.html

Copyright

Copyright (C) Takashi Kato (2012). All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Editor: Mike Sperber
Author: Takashi Kato
Last modified: Tue Oct 9 08:21:24 MST 2012