Chapter 5. I/O
Prev |  | Next

# Chapter 5. I/O

SISC's I/O routines are implemented in a flexible manner, allowing extensions to create new I/O sources that will behave as standard Scheme port objects. The ports can then be operated on with all R5RS port operations, as well as some SISC specific port functions.

## Ports

### URLs

In SISC all procedures that create ports for accessing files, e.g. `open-input-file`, `open-output-file` accept URLs in addition to ordinary file names. Here are some examples of valid URLs:

    http://foo.com/bar/bar1.scm
    file:/tmp/foo.scm
    file:c:\bar\baz.scm
    file:foo.scm
    jar:http://foo.com/bar.jar!/bar/bar1.scm

The last is a URL referring to a file stored in a JAR on a remote web server. For further details on the format of URLs please consult this specification. The format of JAR URLs is defined in the JDK API documentation. What types of URLs are supported by a particular installation of Java depends on the configured protocol handlers. See the JDK API documentation for details. [3]

Relative file names or URLs are resolved in relation to the following parameter:

> parameter: `(current-url [url]) => url`

> > Retrieves or sets the URL which forms the basis for resolving relative filenames and URLs. It is initialized on start up with the path to the current directory. All parameters and the returned value are strings.

The algorithm for resolving relative URLs is defined in this specification. For compatibility with other Schemes, SISC also supports the `current-directory` procedure, which is a simple wrapper around `current-url`.

A convenience procedure exists for executing a procedure while the `current-url` is temporarily set to a different value:

> procedure: `(with-current-url url thunk) => value`

> > Sets the current URL to the URL obtained by normalizing `url` in relation to the current URL, then executes `thunk`, and then sets the current URL back to the previous value.

URLs can be normalized using

> procedure: `(normalize-url url1 [url2]) => url`

> > When called with one string argument, `normalize-url` returns the normalized version of the given URL. Normalization involves, amongst other things, the replacement of relative path references such as `.` and `..`.

> > When called with two string arguments, the procedure returns the normalized version of the second URL when interpreted as a being relative to the first URL.

### Buffered I/O

Buffered ports are provided in SISC to layer over any existing port, and read or write in larger, more efficient chunks. Buffered ports are created with the following constructors, which accept the underlying port and an optional size, indicating the number of bytes or characters to buffer before making an underlying read or write.

> procedure: `(open-buffered-binary-input-port binary-input-port) => binary-input-port`

> > Creates a binary-input-port which is buffered, and reads its bytes from the provided port.

> procedure: `(open-buffered-binary-output-port binary-output-port) => binary-output-port`

> > Creates a binary-output-port which is buffered, and writes its bytes to the provided port.

> procedure: `(open-buffered-character-input-port character-input-port) => character-input-port`

> > Creates a character-input-port which is buffered, and reads its characters from the provided port.

> procedure: `(open-buffered-character-output-port character-output-port) => character-output-port`

> > Creates a character-output-port which is buffered, and writes its characters to the provided port.

With buffered output ports, individual writes may not actually reach the eventual output source, so the programmer must explicitly flush the port when it the output data must reach its destination.

> procedure: `(flush-output-port [output-port]) => undefined`

> > Causes the specified `output-port`'s buffered data to be written immediately. This operation is allowed on any output port, but may have no affect on some. `output-port` defaults to `current-output-port`.

### Character Ports

The R5RS I/O primitives implemented by SISC create character ports. Character ports read characters from input sources and treat the data as characters in a given character set. Correspondingly, character ports output bytes from characters according to a given character set's encoding rules.

By default character ports use the value of the string parameter `character-set` as the character encoding name. A list of many possible encoding names can be found in the Java Platform Documentation

One may temporarily change the default character set using the `with-character-set` function.

> procedure: `(with-character-set encoding thunk) => value`

> > Changes the value of the `character-set` parameter, and the default character set to the named encoding while executing the body of `thunk`.

#### Port Creation

> procedure: `(open-input-file url [encoding]) => input-port`

> > Creates an input port from the specified `url`. If the optional `encoding` parameter, a string, is supplied input will be decoded from the specified encoding rather than the default.

> procedure: `(open-output-file url [encoding] [auto-flush]) => output-port`

> > Creates an output port to the specified `url`. If the optional `encoding` parameter, a string, is supplied output will be encoded in the specified encoding rather than the default. If the optional `auto-flush` argument is provided and is non-false, the port will automatically flush after each write call. If the specified file exists, it will be overwritten silently when the port is opened.

> procedure: `(open-character-input-port binary-input-port [character-set]) => input-port`

> > Creates an input port which reads from the provided binary input port. One may specify the desired character set as a string, otherwise the character set is retrieved from the `character-set` dynamic parameter.

> procedure: `(open-character-output-port binary-output-port [character-set] [auto-flush]) => output-port`

> > Creates an output port which writes to the provided binary output port. One may specify the desired character set as a string, otherwise the character set is retrieved from the `character-set` dynamic parameter. If the optional `auto-flush` argument is provided and is non-false, the port will automatically flush after each write call.

#### Bulk Character I/O

In addition to the R5RS I/O primitives, SISC provides two functions for reading and writing blocks of characters from a character port.

> procedure: `(read-string buffer offset count [character-input-port]) => integer`

> > Reads up to `count` characters from the implicit or specified character port into the string `buffer` starting from the position specified by the integer `offset`. The number of characters successfully read (which may be fewer than `count`) is returned, or the end-of-file value if the end-of-file was reached before any characters were encountered.

> procedure: `(write-string buffer offset count [character-output-port]) => undefined`

> > Writes exactly `count` characters from the given string `buffer` starting from the integer position `offset` to the implicit or specified character output port.

#### Port Creation Wrappers

The next set of procedures assists in creating a port, followed by calling a given procedure with that port. When the procedure returns, the port is closed. Invoking escaping continuations from inside the procedure does not close the port, and invoking a continuation captured inside the procedure does not open the port.

> procedure: `(call-with-input-file url [encoding] procedure) => value`

> > Calls `procedure` with a new input port attached to `url`. The result of the thunk is returned.

> > If the optional `encoding` parameter is provided, the character port created will use the specified encoding rather than the default.

> procedure: `(call-with-output-file url [encoding] procedure) => value`

> > Calls `procedure` with a new character output port attached to `url`. The result of the thunk is returned.

> > If the optional `encoding` parameter is provided, the character port created will use the specified encoding rather than the default.

#### Replacing Standard Ports

The following procedures wrap a thunk, redirecting the input and output of the thunk while it is evaluating to an input or output port other than the `current-input-port` and `current-output-port`. Invoking escaping continuations from inside the procedure restores the original port, and invoking a continuation captured inside the procedure restores the redirection.

> procedure: `(with-input-from-port input-port thunk) => value`

> > Evaluates `thunk` with `input-port` as the `current-input-port` for the duration of the evaluation.

> procedure: `(with-output-to-port output-port thunk) => value`

> > Evaluates `thunk` with `output-port` as the `current-output-port` for the duration of the evaluation.

> procedure: `(with-input-from-file url [encoding] thunk) => value`

> > Evaluates `thunk` with an input port attached to a file opened for reading from `url` as the `current-input-port` for the duration of the evaluation. The port is closed when `thunk` returns normally.

> > If the optional `encoding` parameter is provided, the character port created will use the specified encoding rather than the default.

> procedure: `(with-output-to-file url [encoding] thunk) => value`

> > Evaluates `thunk` with an input port attached to a file opened for writing to `url` as the `current-output-port` for the duration of the evaluation. The port is closed when `thunk` returns normally.

> > If the optional `encoding` parameter is provided, the character port created will use the specified encoding rather than the default.

#### Port Predicates

> procedure: `(input-port? value) => #t/#f`

> > Returns #t if `value` is an input port, #f otherwise.

> procedure: `(output-port? value) => #t/#f`

> > Returns #t if `value` is an output port, #f otherwise.

### String Ports

Requires: (import string-io)

String ports are input or output ports that read or write to a string rather than a file or other stream. String ports can be used to parse or emit formatted strings using the standard Scheme port operations. A String Input port will read from a given string until the end of string is reached, at which point `#!eof` is returned.

String ports deal with characters as the atomic unit, and as such preserve full unicode width characters at all times.

> procedure: `(open-input-string string) => string-input-port`

> > Creates a string input port whose characters are read from the provided string. Characters will be returned from any read operation on the port until the end of the string is reached. Read calls after reaching the end of the string will return #!eof.

> procedure: `(open-output-string) => string-output-port`

> > Creates a string output port, which behaves as an ordinary output port, except that writes are used to create a string as output. The results of all the write operations are retrieved using `get-output-string`.

> procedure: `(get-output-string string-output-port) => string`

> > Returns the string that was created by zero or more writes to a string output port. If no writes were performed on the string output port, an empty string ("") is returned. After this call, the provided string output port is reset to its initial, empty state.

> procedure: `(call-with-input-string string procedure) => value`

> > Calls `procedure` with a new string input port created from `string`. The result of the thunk is returned.

> procedure: `(call-with-output-string procedure) => string`

> > Calls `procedure` with a new string output port. The contents of the string-output-port are returned when the procedure returns.

> procedure: `(with-input-from-string string thunk) => value`

> > Evaluates `thunk` with a string-input-port created from `string` as the `current-input-port` for the duration of the evaluation.

> procedure: `(with-output-to-string thunk) => string`

> > Evaluates `thunk` with a string-output-port created as the `current-output-port` for the duration of the evaluation. When the thunk returns, the contents of the string-output-port are returned.

> procedure: `(string-input-port? value) => #t/#f`

> > Returns #t if `value` is a string input port, #f otherwise.

> procedure: `(string-output-port? value) => #t/#f`

> > Returns #t if `value` is a string output port, #f otherwise.

### Note

This interface complies with SRFI-6 (Basic String Ports).

### Binary Ports and Block IO

Requires: (import binary-io)

In addition to the R5RS I/O functions, SISC provides a symmetric set of functions for reading and writing binary data to and from ports with no character set translation. These ports are operated on using the binary I/O functions described below. Using character-oriented operations (such as the traditional R5RS functions `read`, `read-char`, `display`, etc.) is an error. Binary ports also provide block input/output functions, that allow a Scheme program to read blocks of more than one byte of data at a time from binary ports. SISC stores data that is read or is to be written in block fashion in a binary buffer (see the section called “Binary Buffers”).

> procedure: `(open-binary-input-file url) => binary-input-port`

> > Creates an input port in the same manner as R5RS `open-input-file`, producing an input port that does no character-set decoding on the bytes read as input.

> procedure: `(open-binary-output-file url [auto-flush]) => binary-output-port`

> > Creates an output port in the same manner as `open-output-file`, producing an output port that does no character-set encoding.

> procedure: `(call-with-binary-input-file url procedure) => value`

> > Calls `procedure` with a new binary input port attached to `url`. The result of the thunk is returned.

> procedure: `(call-with-binary-output-file url procedure) => value`

> > Calls `procedure` with a new binary output port attached to `url`. The result of the thunk is returned.

> procedure: `(with-binary-input-from-file url thunk) => value`

> > Evaluates `thunk` with a binary input port attached to a file opened for reading from `url` as the `current-input-port` for the duration of the evaluation. The port is closed when `thunk` returns normally.

> procedure: `(with-binary-output-to-file url thunk) => value`

> > Evaluates `thunk` with a binary output port attached to a file opened for writing to `url` as the `current-output-port` for the duration of the evaluation. The port is closed when `thunk` returns normally.

> There are several operations specifically available for use on binary ports.

> procedure: `(peek-byte [binary-input-port]) => integer`

> > Similar to `peek-char`, reads ahead one byte in the stream, returning the next byte available but not advancing the stream. The byte is returned as an integer. The current input port is used unless specified.

> procedure: `(read-byte [binary-input-port]) => integer`

> > Reads a single byte from the stream, advancing the stream and returning the byte as an integer. The current input port is used unless specified.

> procedure: `(read-block buffer offset count [binary-input-port]) => integer`

> > Reads up to `count` bytes of data from the current input port or the `binary-input-port` parameter if provided, into the binary buffer `buffer` starting at position `offset`. Note that less than `count` bytes may be read. The number of bytes actually read is returned. If the end-of-file is encountered before any bytes could be read, `#!eof` will be returned.

> procedure: `(write-byte integer [binary-output-port]) => undefined`

> > Writes a single byte specified as an integer to the given `binary-output-port` if provided, current output port otherwise.

> procedure: `(write-block buffer offset count [binary-output-port]) => undefined`

> > Writes `count` bytes of data from the provided `buffer` at starting point `offset` to the given `binary-output-port` or to the current output port if unspecified. Exactly `count` bytes will be written.

> procedure: `(binary-input-port? value) => #t/#f`

> > Returns #t if `value` is a binary input port, #f otherwise.

> procedure: `(binary-output-port? value) => #t/#f`

> > Returns #t if `value` is a binary output port, #f otherwise.

#### Buffer I/O

Requires: (import buffer-io)

A module is also provided for input and output to and from binary buffers using buffer ports, similar to character I/O to and from strings using string ports.

> procedure: `(open-input-buffer buffer) => buffer-input-port`

> > Creates a buffer input port whose bytes are read from the provided buffer. Bytes will be returned from any read operation on the port until the end of the buffer is reached. Read calls after reaching the end of the buffer will return #!eof.

> procedure: `(open-output-buffer) => buffer-output-port`

> > Creates a buffer output port, which behaves as an ordinary output port, except that writes are used to create a buffer as output. The results of all the write operations are retrieved using `get-output-buffer`.

> procedure: `(get-output-buffer buffer-output-port) => buffer`

> > Returns the buffer that was created by zero or more writes to a buffer output port. If no writes were performed on the buffer output port, an empty buffer is returned. After this call, the provided buffer output port is reset to its initial, empty state.

> procedure: `(call-with-input-buffer buffer procedure) => value`

> > Calls `procedure` with a new buffer input port created from `buffer`. The result of the thunk is returned.

> procedure: `(call-with-output-buffer procedure) => buffer`

> > Calls `procedure` with a new buffer output port. The contents of the buffer-output-port are returned when the procedure returns.

> procedure: `(with-input-from-buffer buffer thunk) => value`

> > Evaluates `thunk` with a buffer-input-port created from `buffer` as the `current-input-port` for the duration of the evaluation.

> procedure: `(with-output-to-buffer thunk) => buffer`

> > Evaluates `thunk` with a buffer-output-port created as the `current-output-port` for the duration of the evaluation. When the thunk returns, the contents of the buffer-output-port are returned.

### Java Ports

Requires: (import java-io)

This module provides procedures to convert between Scheme and Java I/O types. In general, binary Scheme ports map to plain Java streams, while character Scheme ports map to Java readers and writers.

The following example is somewhat contrived, but illustrates a common usage pattern:

    ;; A convoluted Hello World example.
    (import s2j)
    (import java-io)

    (define-java-classes
      (<java.lang.system> |java.lang.System|))

    (define-generic-java-field-accessors
      (:jout out))

    (let ((stdout (open-character-output-port
                   (->binary-output-port
                    (:jout (java-null <java.lang.system>)) #t) #t)))
      (display "Hello, world!" stdout))

> procedure: `(->binary-input-port jinput-stream) => binary-input-port`

> > Returns a binary input port associated to the `java.io.InputStream` object passed as the `jinput-stream` parameter.

> procedure: `(->binary-output-port joutput-stream [aflush?]) => binary-output-port`

> > Returns a binary input port associated to the `java.io.OutputStream` object passed as the `joutput-stream` parameter. If the optional boolean `aflush?` parameter is not provided, the port will not autoflush by default.

> procedure: `(->character-input-port jreader) => character-input-port`

> > Returns a character input port associated to the `java.io.Reader` object passed as the `jreader` parameter.

> procedure: `(->character-output-port jwriter [aflush?]) => character-output-port`

> > Returns a binary input port associated to the `java.io.Writer` object passed as the `joutput-stream` parameter. If the optional boolean `aflush?` parameter is not provided, the port will not autoflush by default.

> procedure: `(->jinput-stream input-port) => jinput-stream`

> > Returns a `java.io.InputStream` object associated to the given Scheme `input-port`. The function produces an error if a character port is passed.

> procedure: `(->joutput-stream output-port) => joutput-stream`

> > Returns a `java.io.OutputStream` object associated to the given Scheme `output-port`. The function produces an error if a character port is passed.

> procedure: `(->jreader character-input-port) => jreader`

> > Returns a `java.io.Reader` object associated to the given Scheme `character-input-port`.

> procedure: `(->jwriter character-output-port) => jwriter`

> > Returns a `java.io.Writer` object associated to the given Scheme `character-output-port`.

Scheme ports can also be used from Java. See the section called “Scheme I/O” for information.

### Serialization

Requires: (import serial-io)

With `read` and `write`, Scheme values are read and written in a standardized, textual external representation. However, this external representation only fully describes a limited subset of Scheme types. For instance it is impossible to read/write a procedure, or closure, or continuation.

SISC provides a special composed port type and procedures for reading and writing any Scheme value using a binary representation. The (de)serialization preserves the referential structure of the object graph comprising the serialized values.

Serial ports are composed onto and thus are binary ports, i.e. all operations applicable to binary ports also apply to serial ports.

#### Port Creation and Identification

> procedure: `(open-serial-input-port binary-input-port) => serial-input-port`

> > Creates a serial input port which reads external representations of Scheme values from the given binary input port.

> procedure: `(open-serial-output-port binary-output-port [auto-flush]) => serial-output-port`

> > Creates a serial output port which can be used to write external representations of Scheme values to the provided binary output port.

> procedure: `(serial-input-port? value) => #t/#f`

> > Returns #t if `value` is a serial input port, #f otherwise.

> procedure: `(serial-output-port? value) => #t/#f`

> > Returns #t if `value` is a serial output port, #f otherwise.

#### Serial Port Wrappers

> procedure: `(call-with-serial-input-port binary-input-port procedure) => value`

> > Calls `procedure` with a new serial input port attached to `binary-input-port`. The result of the thunk is returned.

> procedure: `(call-with-serial-output-port binary-output-port procedure) => value`

> > Calls `procedure` with a new serial output port attached to `binary-output-port`. The result of the thunk is returned.

> procedure: `(with-serial-input-from-port binary-input-port thunk) => value`

> > Evaluates `thunk` with a serial input port attached to the specified `binary-input-port` as the `current-input-port` for the duration of the evaluation. The port is not closed when `thunk` returns.

> procedure: `(with-serial-output-to-port binary-output-port thunk) => value`

> > Evaluates `thunk` with a serial input port attached to the specified `binary-output-port` as the `current-output-port` for the duration of the evaluation. The port is not closed when `thunk` returns.

> procedure: `(call-with-serial-input-file url procedure) => value`

> > Calls `procedure` with a new serial input port attached to `url`. The result of the thunk is returned.

> procedure: `(call-with-serial-output-file url procedure) => value`

> > Calls `procedure` with a new serial output port attached to `url`. The result of the thunk is returned.

> procedure: `(with-serial-input-from-file url thunk) => value`

> > Evaluates `thunk` with a serial input port attached to a file opened for reading from `url` as the `current-input-port` for the duration of the evaluation. The port is closed when `thunk` returns normally.

> procedure: `(with-serial-output-to-file url thunk) => value`

> > Evaluates `thunk` with a serial input port attached to a file opened for writing to `url` as the `current-output-port` for the duration of the evaluation. The port is closed when `thunk` returns normally.

#### Serialization Procedures

> procedure: `(deserialize [serial-input-port]) => value`

> > Reads a Scheme value from an external representation retrieved from `serial-input-port`. If `serial-input-port` is absent the data is read from the current input port.

> procedure: `(serialize value [serial-output-port]) => undefined`

> > Writes an external representation of `value` to `serial-output-port`. If `serial-output-port` is absent the data is written to the current output port.

## Networking

Requires: (import networking)

The SISC Networking library provides a mechanism for creating and manipulating IP network protocols as standard Scheme ports. SISC supports TCP, UDP, and Multicast UDP. Each is described in the sections that follow.

Each protocol provides one or more socket constructors. These functions produce a Socket handle, which is represented in SISC as `#<socket>`. A socket handle is then used to obtain Scheme ports.

IP addresses and network hostnames are represented as strings in SISC. Unless otherwise noted, the network library functions that require an address may take a network address as a string which may be any of:

- A hostname, to be resolved through the domain name system.

- An IPv4 network address in the standard dotted quad format. (RFC-791)

- An IPv6 network address in colon separated hexadecimal form, and zero-shortened form. (RFC-2373)

IPv6 addresses must be supported by the underlying operating system. An error may be raised if the address is not supported. All IP port values must be exact integers in the proper range.

### IP Addressing

Several utility functions are provided for manipulating IP addresses. These are described below.

> procedure: `(get-host-ip-by-name hostname) => string`

> > Attempts to resolve a hostname provided as a string into an IP address in dotted-quad form. If the host cannot be found, #f is returned.

> procedure: `(get-host-name-by-ip ip-address) => string`

> > Attempts a reverse lookup of the given dotted-quad address to determine a registered domain name. If unsuccessful, #f is returned.

> procedure: `(get-local-host) => string`

> > Attempts to determine the Internet visible IP address of the local machine. If successful, this address is returned in dotted-quad notation. #f is returned otherwise.

### Socket Operations

Once obtained using a protocol specific constructor, a Socket Handle allows manipulation of common socket options, the creation of Scheme input/output ports, and closing of the socket.

> procedure: `(socket? value) => #t/#f`

> > Returns true if and only if the provided value is a socket.

> procedure: `(open-socket-input-port socket [encoding]) => input-port`

> > Opens a character input port to the socket. If the optional `encoding` parameter is provided, the character port created will use the specified encoding rather than the default.

> procedure: `(open-socket-output-port socket [encoding] [auto-flush]) => output-port`

> > Opens a character output port to the socket. If provided, the boolean argument specifies whether the given port should be set to auto-flush mode. If unspecified, the port does not auto-flush. If the optional `encoding` parameter is provided, the character port created will use the specified encoding rather than the default.

> procedure: `(open-binary-socket-input-port socket) => binary-input-port`

> > Opens a binary input port to the socket.

> procedure: `(open-binary-socket-output-port socket [auto-flush]) => binary-output-port`

> > Opens a character output port to the socket. If provided, the boolean argument specifies whether the given port should be set to auto-flush mode. If unspecified, the port does not auto-flush.

> procedure: `(close-socket socket) => unspecified`

> > Closes an IP socket.

The port-obtaining functions above work on most sockets. An exception applies for TCP server sockets, which are used only to obtain connected TCP sockets.

### TCP

The most commonly used Internet protocol maps most favorably to Scheme's input/output model. Writing to an output port retrieved from a TCP socket writes the data to that socket. Reading from an input port reads from the connected socket. One important note is that one can control the amount of data that fills a TCP packet by using an output port that does not auto-flush. Data is written to the port until one considers the packet complete, and then uses `(flush-output-port port)` to complete the packet. Note also that this does not guarantee that one gets the desired packet size, but does allow one to construct reasonably sized packets.

TCP sockets are obtained one of two ways. Either one creates an outgoing connection to another listening host and then subsequently obtains a socket handle, or one creates a listening socket and then obtains a socket by waiting for an incoming connection on the specified port. In either case, the result is a socket handle with an available input and output port that can be obtained using a function in the previous section.

> procedure: `(open-tcp-socket host port) => socket`

> > Attempts to connect to the host at the given hostname or IP address encoded as a string, at the given TCP port specified as an integer. An error is raised if the host cannot be found or the connection fails. If successful, a socket is returned.

> procedure: `(open-tcp-listener port [interface-address]) => server-socket`

> > Creates a TCP server socket, which may only be used with accept-tcp-socket, or closed. The server socket will listen on the integer port specified. If provided, the interface-address, a string specifies the address of a local interface to bind to. If not provided, the port is bound on all available interfaces. An error is raised if the socket cannot be bound and set listening.

> procedure: `(server-socket? value) => #t/#f`

> > Returns true if and only if the provided value is a server socket.

> procedure: `(accept-tcp-socket server-socket) => socket`

> > Accepts an incoming connection on the provided server-socket, and returns a TCP socket handle. This function will block until an incoming connection is made, or, if set, the socket timeout is exceeded. If the latter happens, an error will be raised.

> procedure: `(set-so-timeout! socket timeout) => undefined`

> > Sets the socket timeout on a socket. The socket can be either a server socket or connected socket. In the former case, this value specifies the number of milliseconds that an accept-tcp-socket can wait before timing out. In the latter, the value specifies the number of milliseconds that can elapse during a read call before timing out.

### TLS and SSL

Sockets which are encrypted using the Secure Sockets Layer (SSL) or Transport Security Layer (TLS) can be created an accepted as well. This functionality can heavily depend on the underlying support in the JVM, including security settings, restrictions, installed certificates, etc. For information on the Java Secure Socket Extension (JSSE) and its setup, refer to the JSSE documentation page at Sun.

The SSL functionality is built atop the sockets and server sockets functionality as in TCP networking above. A separate constructor for sockets (`open-ssl-socket`) and listening sockets (`open-ssl-listener`) exist, and produce sockets which are used identically to TCP sockets as previously described.

> procedure: `(open-ssl-socket host port [auto-close]) => socket`

> > Attempts to connect to the host at the given hostname or IP address encoded as a string, at the given TCP port specified as an integer, and establish an SSL connection using the default cipher suites and protocols available. An error is raised if the host cannot be found or the connection fails. If successful, a socket is returned.

> procedure: `(open-ssl-listener port [interface-address]) => server-socket`

> > Creates an SSL TCP server socket, which may only be used with accept-tcp-socket, or closed. The server socket will listen on the integer port specified. If provided, the interface-address, a string specifies the address of a local interface to bind to. If not provided, the port is bound on all available interfaces. An error is raised if the socket cannot be bound and set listening.

> > The cipher suites, protocols, and modes which this server socket will accept are set with the following functions.

> procedure: `(get-enabled-cipher-suites ssl-server-socket) => list`

> > Returns a list of strings naming the cipher suites which are enabled for this server socket.

> procedure: `(set-enabled-cipher-suites! ssl-server-socket suite-list) => list`

> > Accepts an ssl server socket and a list of strings naming the cipher suites which should be available for negotiation with the remote end. The previous list is returned.

> procedure: `(get-enabled-protocols ssl-server-socket) => list`

> > Returns a list of strings naming the security protocols which are enabled for this server socket.

> procedure: `(set-enabled-protocols! ssl-server-socket protocol-list) => list`

> > Accepts an ssl server socket and a list of strings naming the protocols which should be available for negotiation with the remote end. The previous list is returned.

> procedure: `(session-creation-permitted? ssl-server-socket) => boolean`

> > Returns true if new SSL/TLS sessions can be created by the sockets obtained from this server socket.

> procedure: `(set-session-creation-permitted! ssl-server-socket boolean) => boolean`

> > Sets whether new SSL/TLS sessions can be created by the sockets obtained from this server socket. The previous value is returned.

> procedure: `(is-client-mode? ssl-server-socket) => boolean`

> > Returns true if the SSL server socket will be in the rare client mode after accepting the connection, rather than the more common server mode.

> procedure: `(set-client-mode! ssl-server-socket boolean) => boolean`

> > Sets whether the SSL server socket will be in client mode when accepting new connections. The previousvalue is returned.

> procedure: `(get-client-auth ssl-server-socket) => symbol or #f`

> > Returns the necessity of the remote client to authenticate to this server socket. The returned values are either `needed`, indicating the remote must authenticate, `wanted`, indicating the remote will be requiested to authenticate but is not required to, or `#f`, indicating the remote will not be requested to authenticate.

> procedure: `(set-client-auth! ssl-server-socket client-auth-mode) => symbol or #f`

> > Sets the client authentication requirements, as described in `get-client-auth` above. One of the above values must be specified. The previous value is returned.

### UDP

UDP sockets can be obtained for both receive only and send/receive sessions.

### Note

The behavior of the `char-ready?` function is somewhat more difficult to predict on a UDP input port. The function will return `#t` only when a previous datagram contained more bytes than were requested by the read operation that received it.

> procedure: `(open-udp-listen-socket listen-port [interface-address] [datagram-size]) => udp socket`

> > Opens a UDP socket that listens on `listen-port` (optionally bound to only the interface on `interface-address`). If provided, `datagram-size` specifies the buffer size (in bytes) for receiving UDP datagrams. Datagrams larger than `datagram-size` are truncated to that size. If unspecified, the default datagram size is 1500 bytes.

> procedure: `(open-udp-socket remote-host remote-port) => udp socket`

> > Opens a UDP socket for sending datagrams to the Internet host specified by `remote-host`, on port `remote-port`.

After obtaining a UDP socket, input and output ports can be obtained in the usual manner. It is an error to attempt to obtain an output-port from a listening UDP socket, or an input port from a sending UDP socket.

UDP input ports behave as ordinary input ports. When a datagram arrives as a result of any read operation on the port, their entire contents are stored in a buffer of length `datagram-size` bytes. Successive read operations return data from that buffer until it is exhausted, at which point a read operation will cause the UDP socket to listen for another datagram.

UDP output ports should be treated with some care, however. If a UDP output port was obtained in auto-flush mode, each write operation to the output port will cause a new datagram to be sent. Control over the size of the datagram must be maintained by using a port that does not auto-flush, writing the desired data, and flushing once the amount of data that the user wants to occupy a single UDP datagram is reached. The behavior of constructing very large UDP packets is undefined. The packet may be silently dropped or (more likely) fragmented at the IP layer.

### Multicast UDP

SISC provides support for IP multicast UDP datagrams as well. This allows a program to both send and receive to an IP multicast group. Multicast UDPs are an extension of ordinary UDP. Thus all I/O operations on a Multicast UDP socket are subject to the same semantics as an ordinary UDP socket.

### Note

The Multicast UDP library requires that the underlying operating system's IP networking stack support Multicast. The functions described here may produce an error if the operating system does not.

A program wishing to use multicast UDP sockets must first obtain a multicast socket for either listening to a multicast group, or for both listening and sending to such a group.

> procedure: `(open-multicast-socket listen-port [interface-address] [datagram-size]) => multicast udp socket`

> > Opens a multicast UDP socket that listens on `listen-port` (optionally bound only to the interface addressed by `interface-address`. If provided, `datagram-size` specifies the buffer size (in bytes) for receiving UDP datagrams. Datagrams larger than `datagram-size` are truncated to that size. If unspecified, the default datagram size is 1500 bytes.

> procedure: `(open-multicast-socket group port [interface-address] [datagram-size]) => multicast udp socket`

> > Opens a multicast UDP socket for sending datagrams to the specified multicast `group`, on the specified `port`. The returned socket will also be capable of listening to that group on the same port (and optionally bound only to the interface addressed by `interface-address`), though the socket will not initially be a member of the group. If provided, `datagram-size` specifies the buffer size (in bytes) for receiving UDP datagrams. Datagrams larger than `datagram-size` are truncated to that size. If unspecified, the default datagram size is 1500 bytes.

Once a sending socket has been obtained (the second form), an output-port can be obtained in the usual manner, and datagrams can be immediately sent to the multicast group. To receive datagrams, sockets returned from both forms must join a multicast group.

A multicast group is specified by a class D IP address and by a standard UDP port number. Class D IP addresses are in the range 224.0.0.0 to 239.255.255.255, inclusive. The address 224.0.0.0 is reserved and should not be used.

Groups are joined and left using the following functions:

> procedure: `(join-multicast-group multicast-socket group) => undefined`

> > Causes the given multicast socket to join the group specified by the Internet address in `group`. Once joined, read operations on an obtained input-port will be able to receive datagrams destined to that group.

> procedure: `(leave-multicast-group multicast-socket group) => undefined`

> > Causes the given multicast socket to leave the group specified by the Internet address in `group`. Read operations on any input ports obtained from this socket will no longer receive datagrams from that group.

A single multicast socket can simultaneously listen to more than one multicast group. A socket can only send to one group, however: the group it was constructed with.

Multicast packets are limited in extent by their time-to-live. Each time a multicast packet crosses a router, its ttl is decremented. In this manner, one can send datagrams only to local networks or subnetworks, as well as more grand scopes. The TTL of a socket is set using `set-multicast-ttl!`

> procedure: `(set-multicast-ttl! multicast-socket ttl) => undefined`

> > Sets the multicast TTL of the given socket to `ttl`, an integer. All datagrams sent after this call will have their TTL set to the new value.

Valid multicast TTLs are in the range 0 (restricted to the same host) to 255 (unlimited in scope).

## User Defined Ports

Requires: (import custom-io)

SISC provides the ability to create new I/O port types from within Scheme. To add a new port type, one invokes a custom port constructors described below, passing procedures (Scheme or otherwise) which implement the operations required by that port. Each custom port constructor returns a Scheme port which may be used with any of the SISC or R5RS I/O functions. Port implementors may also use an associated port-local value to coordinate state for some specialized ports.

> procedure: `(set-port-local! custom-port value) => value`

> > Sets the port-local value of the given custom port.

> procedure: `(port-local wrapper-stream) => value`

> > Gets the port-local value of the given custom port.

> procedure: `(make-custom-character-input-port read read-string ready? close) => character-input-port`

> > Creates a character-input-port whose functionality is implemented by the four provided fundamental procedures.

> > The fundamental procedures required are as follows:

> > `(read port) => integer`

> > > Return a character as an integer value, or -1 if the end of stream has been reached. `port` is a reference to the custom Scheme port which contains the procedure.

> > `(read-string port mutable-string offset count) => integer`

> > > Reads up to `count` characters into the given mutable string at position `offset`. The number of actual characters read are returned as an integer value, or -1 if the end of stream has been reached. `port` is a reference to the custom Scheme port which contains the procedure.

> > `(ready? port) => boolean`

> > > Returns a non-false value if one or more characters are available for reading, false otherwise.

> > `(close port) => undefined`

> > > Closes the port.

> procedure: `(make-custom-binary-input-port read read-block available close) => binary-input-port`

> > Creates a binary-input-port whose functionality is implemented by the four provided fundamental procedures.

> > The fundamental procedures required are as follows:

> > `(read port) => integer`

> > > Return a byte as an integer value, or -1 if the end of stream has been reached. `port` is a reference to the custom Scheme port which contains the procedure.

> > `(read-block port buffer offset count) => integer`

> > > Reads up to `count` bytes into the given binary buffer at position `offset`. The number of actual bytes read are returned as an integer value, or -1 if the end of stream has been reached. `port` is a reference to the custom Scheme port which contains the procedure.

> > `(available port) => integer`

> > > Returns a count of the number of bytes which are available for reading. This number need not be accurate.

> > `(close port) => undefined`

> > > Closes the port.

> procedure: `(make-custom-character-output-port write write-string flush close) => character-output-port`

> > Creates a character-output-port whose functionality is implemented by the four provided fundamental procedures.

> > The fundamental procedures required are as follows:

> > `(write port byte) => undefined`

> > > Writes a character represented as an integer value. `port` is a reference to the custom Scheme port which contains the procedure.

> > `(write-string port string offset count) => void`

> > > Writes `count` characters from the given string at position `offset`. `port` is a reference to the custom Scheme port which contains the procedure.

> > `(flush port) => undefined`

> > > Flushes any unwritten characters to the stream.

> > `(close port) => undefined`

> > > Closes the port.

> procedure: `(make-custom-binary-output-port write write-block flush close) => binary-output-port`

> > Creates a binary-output-port whose functionality is implemented by the four provided fundamental procedures.

> > The fundamental procedures required are as follows:

> > The fundamental procedures required are as follows:

> > `(write port byte) => undefined`

> > > Writes a byte represented as an integer value. `port` is a reference to the custom Scheme port which contains the procedure.

> > `(write-block port buffer offset count) => void`

> > > Writes `count` bytes from the given binary buffer at position `offset`. `port` is a reference to the custom Scheme port which contains the procedure.

> > `(flush port) => undefined`

> > > Flushes any unwritten characters to the stream.

> > `(close port) => undefined`

> > > Closes the port.

Finally, the underlying procedures which implement a custom port can be retrieved with the following function:

> procedure: `(custom-port-procedures custom-port) => list`

> > Returns the procedures which implement a custom port as a list.

As an example, here are String Ports, implemented as user defined ports:

Example 5.1. User defined string ports

    (import custom-io)

    ;; String Input Ports

    (define (sio/read port)
      (let ([local (port-local port)])
        (let ([ptr (vector-ref local 1)])
          (if (= (vector-ref local 2) ptr)
              -1
              (let ([c (char->integer
                        (string-ref (vector-ref local 0) ptr))])
                (vector-set! local 1 (+ ptr 1))
                c)))))

    (define (sio/read-string local buffer offset length)
      (let ([local (port-local port)])
        (let ([str (vector-ref local 0)]
              [strlen (vector-ref local 2)])
          (do ([i offset (+ i 1)]
               [j (vector-ref local 1) (+ j 1)]
               [c 0 (+ c 1)])
              ((or (= i (+ offset length))
                   (= j strlen))
               (vector-set! local 1 (+ ptr 1))
               c)
            (string-set! buffer i (string-ref str j))))))

    (define null (lambda args (void)))

    (define (open-input-string str)
      (unless (string? str)
        (error 'open-input-string "expected string, got '~a'.~%" str))
      (let ([port (make-custom-character-input-port
                   sio/read sio/read-string null null)])
        ; Use the port local value to store the string and a pointer
        ; for the current position
        (set-port-local! port (vector str 0 (string-length str)))
        port))

    ;; String Output Ports

    (define (sio/write port char)
      (set-port-local! port
        (cons char (port-local port))))

    (define (sio/write-string port string offset length)
      (set-port-local! port
       (append (reverse (string->list (substring string offset (+ length offset))))
               (port-local port))))

    (define (open-output-string)
      (let ([port
             (make-custom-character-output-port
              sio/write sio/write-string null null)])
        (set-port-local! port '())
        port))

    (define (get-output-string port)
      (flush-output-port port)
      (let ([str (apply string
                        (reverse (port-local port)))])
        (set-port-local! port '())
        str))

## Miscellaneous

### Pretty-Printing

SISC includes a pretty-printer, a function that behaves like `write`, but introduces whitespace in order to make the output of data more readable to humans.

> procedure: `(pretty-print value [output-port]) => unspecified`

> > Pretty-prints the specified value, either to the specified output-port, or to the console if no output-port is specified.

### Source Loading

The `load` procedure accepts URLs as well as ordinary file names. See the section called “URLs” for details on what kinds of URLs are supported.

The file name passed to `load` is resolved relative to the `current-url` parameter. During the execution of `load`, `current-url` is set to the loaded file, so that any invocations of `load` from the loaded file resolve the given file name relative to the file currently being loaded. For example, lets assume we have a web site that serves the following files:

     ;;;contents of http://foo.com/bar/bar1.scm ;;;
    (load "bar2.scm")
    ;;;contents of http://foo.com/bar/bar2.scm ;;;
    (load "/baz/baz1.scm")
    ;;;contents of http://foo.com/baz/baz1.scm ;;;
    (load "../baz/baz2.scm")
    ;;;contents of http://foo.com/baz/baz2.scm ;;;
    (display "Hello")

Invoking

    (load "http://foo.com/bar/bar1.scm")

results in each file being loaded; with the last file in the chain, `baz2.scm`, displaying `Hello`.

The `load` function supports many types of files which contain executable code. `load` will attempt to determine (primarily by the file's extension) which type of file is being loaded to load that file in the correct manner. The file types currently supported are those described in the section called “Running Scheme Programs”.

When a pure source file is loaded, each s-expression is evaluated in sequence, exactly as if entered into the REPL one s-expression at a time.

### Location Tracking

SISC allows the location of input, i.e. the file name, line number, and column number, to be tracked when reading from an input port.

> procedure: `(open-source-input-file url) => input-port`

> > This procedure behaves the same as `open-input-file`, except that it also tracks the location of the input.

> procedure: `(input-port-location input-port) => list`

> > Returns the current location information associated with `input-port`. The return value is an association list containing the following keys: `source-file`, `line-number`, `column-number`. If no location information is available, #f is returned.

### Locating Resources

SISC provides a mechanism for locating and subsequently loading named resources, such as Scheme source files, Scheme data files, property files. The resources are located using the mechanism described in the section called “Class Loading”. This allows Scheme programs to load resources in a portable, J2EE-compliant manner.

> procedure: `(find-resource string) => url`

> > Locates the resource named by `string` on the Java class path. The resource location is returned as a URL suitable for SISC I/O operations. If the resource cannot be found, #f is returned.

> procedure: `(find-resources string) => url-list`

> > Locates the resource named by `string` on the Java class path. The resource locations are returned as a list of URLs suitable for SISC I/O operations. If the resource cannot be found, an empty list is returned.

### File Manipulation

Requires: (import file-manipulation)

The file-manipulation library provides access to a number of functions for reading and manipulating files and their attributes. The file-manipulation library acts on filenames in the same manner as other Scheme file related functions, e.g. it accepts file and directory names as strings, which are resolved relative to the current URL.

The following functions act on both files and directories. With the exception of `file-exists?` and `get-parent-url`, the behavior when applying these to non-existant files or directories is undefined.

> procedure: `(file-delete! filename) => #t/#f`

> > Attempts to remove the given file or directory. If successful, `#t` is returned.

> procedure: `(file-exists? filename) => #t/#f`

> > Returns true if the given file or directory exists.

> procedure: `(file-is-directory? filename) => #t/#f`

> > Returns true if the given string names an existing directory.

> procedure: `(file-is-file? filename) => #t/#f`

> > Returns true if the given string names an existing file.

> procedure: `(file-last-modified filename) => integer`

> > Returns the number of milliseconds since the Unix epoch (Jan 1, 1970) of the date the file or directory was last modified.

> procedure: `(file-rename! source-filename dest-filename) => #t/#f`

> > Renames the given source file or directory to the destination. This can be used both to rename a file or directory or to move a file/directory in the same filesystem. If successful, `#t` is returned.

> procedure: `(file-set-last-modified! filename unixtime) => #t/#f`

> > Sets the last modified date of the given filename to the given integer (in number of milliseconds since the epoch). Returns `#t` if successful.

> procedure: `(get-parent-url filename) => string`

> > Given any URL, returns the URL of its parent. For filenames, as an example, the parent directory is returned.

The following functions operate only on files. Their behavior when applied to directories or non-existant files is undefined.

> procedure: `(file-is-readable? filename) => #t/#f`

> > Returns `#t` if the file can be opened for reading.

> procedure: `(file-is-writeable? filename) => #t/#f`

> > Returns `#t` if the file can be opened for writing.

> procedure: `(file-length filename) => integer`

> > Returns the length, in bytes, of the given file.

> procedure: `(file-set-read-only! filename) => #t/#f`

> > Sets the given file read-only. Returns `#t` if successful.

Finally, the following functions are specific to directories. Their behavior on files is undefined. The behavior of `directory-list` is undefined on non-existant directories.

> procedure: `(directory-list directory) => list of strings`

> > Retrieves the children of the given directory, as a list of strings. Each string names one child, and is a filename relative to the given directory.

> procedure: `(make-directory! directoryname) => #t/#f`

> > Attempts to creates the given directory. Returns `#t` if successful.

> procedure: `(make-directories! directoryname) => #t/#f`

> > Attempts to creates the given directory and all non-existing parent directories. Returns `#t` if successful.

### Class Loading

Some SISC features require classes and other resources to be loaded. By default, SISC will use the current thread's class loader, or, if none is present, the system class loader. SISC maintains a list of class path extensions onto which class and resource loading falls back. This list can be inspected and extended using the following functions:

> parameter: `(class-path-extension) => string-list`

> > Retrieves the current class path extension. The class path extension is a list of strings representing URLs, typically pointing to jar files or directories. It is used a fall back during class and resource loading.

> parameter: `(class-path-extension-append! class-path) => undefined`

> > Appends a list of URLs to the current class path extension.

> > The elements of the `class-path` list are normalized using the `current-url` (see the section called “URLs”), thus permitting the usage of relative URLs.

Note that the class path extension is part of the dynamic environment,, so each thread has its own setting, initially inherited from the parent thread. See Chapter 6, Threads and Concurrency for more details on SISC's threading semantics.

[3] Handling of JAR files in URLS may be dependent on the SISC host language, as well as some uncommon protocols. FILE, HTTP and FTP should be expected to work with any host language.

Prev |  | Next
Chapter 4. Debugging Facilities | Home | Chapter 6. Threads and Concurrency