Socket / Client

Introduction

The Socket/Client box (only available in the
registered version of COGENT) is the client half of a TCP/IP interface for
COGENT. (See Socket/Server for the server
half of the TCP/IP interface.) The interface allows multiple models to be run
simultaneously (but asynchronously) on the same or different machines,
communicating with each other via TCP/IP, and it allows COGENT models to
interact with networked resources (such as web servers, Telnet clients or Java
applications).

Socket/Client boxes accept a special trigger,
open(Host : Port), where Host is an atom specifying a
hostname (eg 'cogent.psyc.bbk.ac.uk' or localhost) or an IP
address (eg '127.0.0.1'), and Port is a positive integer specifying a
port on which a suitable server is listening. When a Socket/Client receives
such a message from another box within a COGENT model, the box connects to the
specified server and port. Messages can then be sent/received by the
Socket/Client to/from the server.

Other than special triggers, all messages sent to a Socket/Client box along
a send arrow will be transmitted along all open connections from or to that
box. Similarly, any messages incoming through open connections will be
transmitted along all send arrows leading from the Socket/Client box.

Execution and termination

A model containing one or more Socket boxes will continue running if any
connections from or to a box in the model are still open, or if a server box
is listening for connections.
Sending a Socket/Client box a close message will close all open
connections from or to that box.

It should be noted that unlike COGENT models in general, model runs using
Socket boxes cannot be halted and restarted in a later OOS session. This is
because ending the OOS session will close any open connections, so clients
will not still be connected when the model is restarted. Initialisation is
usually required.

Data formats

Socket boxes support two data formats to transmit and receive data: raw
mode sends and receives lists of ASCII values, corresponding to lines of
textual output and input, whereas the default mode sends and receives prolog
terms. Raw mode is useful to allow COGENT models to interact with arbitrary
networked systems, such as web servers, or with Telnet clients for user
interaction. The default mode, by contrast, simply sends and receives prolog
terms, which is more appropriate for direct communication between COGENT
models.

Raw mode: If the box is configured to use raw mode, messages
corresponding to lines of output must be specified as
raw(LineString) where LineString is a prolog string,
or a list of ASCII values, for example raw("hello") or
equivalently
raw([104, 101, 108, 108, 111]). These
strings are each formatted on the output stream with a following
newline (ASCII 10).

Similarly, lines of input from connections, terminated with newlines, are
converted to raw(LineString) format before being passed along send
arrows to other boxes. The newline character is not included in the parsed
string, and any carriage returns (ASCII 13) in the input are ignored.

Default mode: In the default mode, any prolog term sent to a Socket
box (except special triggers) will be sent on along any open connections to
the peers, which must also be in default mode to ensure success.

Tagging and individual connections

So far, we have assumed that all outgoing messages will be sent to all open
connections, and incoming messages could come from any connection, without
identification. But a single Socket/Client may be connected to any number of
servers, and similarly a single server may be connected to any number of
Socket/Client boxes. Obviously there may be cases when we wish to send
different messages to different connections, and when we want to identify the
source of an incoming message. Both require a method for identifying
individual open connections uniquely.

The problem of unique reference can be addressed by creating a system of
identifiers. We have chosen to implement this as the state of the box, by
analogy with the state of a buffer. In the present case, the state of the box
comprises a list of unique identifiers for each currently open connection. It
is maintained by the system, so it is not possible to directly add or delete
elements to or from the state. Each element has the form
socket(Host, UniqueID) where Host gives the hostname
and port number, or a numeric IP address, and UniqueID is an integer
generated by the system. It is possible to use the standard match procedure to
read elements from the state.

Sending messages to individual connections can be achieved by simply
attaching this identifier to the message to be sent. For example, if
Socket=socket('localhost':15000, 8), then we can send a raw-mode
message to this connection alone by using the form
raw(Socket, LineString). For default mode, we have introduced a
special term message/2, which is used in an analogous way:
message(Socket, PrologTerm).

Attaching Socket identifiers to incoming messages happens analogously. It
is necessary to specify that the box should tag incoming messages using a
special property Tag Messages. If this is checked, all incoming
messages will be in the form raw(Socket, LineString) or
message(Socket, PrologTerm) instead of the default forms.

A final refinement of this tagging mechanism allows the setting of aliases
for socket identifiers. If a connection has an alias, it can be specified
instead of a socket/2 term on outgoing messages, and it will be used
instead of a socket/2 term on tagged incoming messages, and when
matching against the contents of the state. Aliases can be set in a variety of
ways: first, an alternative form of the open message has a second
argument that specifies the alias to be used for the connection, for example
open(localhost:15000, myalias). Secondly, for an
already-existing connection, it can be given an alias using a special message
alias(Socket, Alias). Finally, a remote peer can specify its own
alias, using a special message call_me(Alias). Setting an alias for a
connection overrides any previous aliases for that connection.

Example: hitting a web server

To hit a web server and collect its output, first of all your
Socket/Client box must be configured for Raw I/O. Now, using a
process, open a connection to the server (usually on port 80), then
send it a HTTP GET request, followed by a blank line, like this:

If all is well, the web server will reply with a stream of output,
corresponding in this case to its root document, in the form of
raw(...) messages representing individual lines. The output
lines can be collected in order, by using an unrefracted, triggered
rule to add the messages to a FIFO buffer with Duplicates
enabled. The lines can then be read, in order, and at your leisure,
from the buffer. In this example, there is no need to explicitly close
the connection, as it will be closed in due course by the server.

By the way, to make this example more useful, you can replace the
first "/" in the GET command with the path to some other page on that
server, eg "GET /path/to/mypage.html HTTP/1.0". Also, you
will notice that a few lines at the beginning of the web server's
output are headers, and these are separated from the usual HTML
content by a blank line. But this is not the place to teach
HTTP...

Properties

Raw IO (boolean)

This is used to specify whether information is transmitted and
received as lists of ASCII values (when enabled), or as prolog terms
(when disabled). This will affect all connections to or from the box.

Tag Messages (boolean)

This is used to specify whether incoming messages should be tagged
with a socket identifier or alias. In raw mode the resulting message
will be of the form raw(Socket, LineString) whereas in
default mode it will be of the form
message(Socket, PrologTerm). If this property is
unchecked, incoming messages will not be tagged.

Special triggers

open(Host : Port)

open(Host : Port, Alias)

Opens a connection to the server on Host:Port. Host must be an
atom, either a text hostname (eg 'cogent.psyc.bbk.ac.uk' or
localhost) or an IP address (eg '127.0.0.1'), and
Port should be a suitable positive integer. While an open connection
exists, the model will not stop executing. If the second argument is
specified, it will be used as an alias for the connection identifier.

close

close(Socket)

If a Socket/Client box receives a close message, all open
connections to or from that box will be closed, and the model may be
able to stop executing. The box itself is not stopped, however, and
this will not shutdown an active server. If an argument is specified,
it must be a socket identifier or alias, and only the specified
connection will be closed.

stop

Like the stop message generally, it causes the recipient
box to cease all operations until the box is next initialised. In the
case of Socket/Client boxes, there is an extra side effect: all
open connections with other Socket boxes.

raw(LineString)

raw(Socket, LineString)

If the box is configured for Raw IO any messages sent to it
must be in this format, and the LineString will be written to
the outgoing stream. In Raw IO mode, all incoming data is also
parsed into lines, and passed along send arrows as
raw(LineString) terms. This allows the box to interface with
arbitrary network processes, such as web servers. If two arguments are
specified, the first must be a Socket identifier or alias, and the
message will only be sent to the connection that is specified. When
Tag Messages is checked, incoming messages will be passed along
arrows to other boxes in this form.

message(PrologTerm)

message(Socket, PrologTerm)

The binary form (message/2) form is used to send a Prolog
term to a specified connection, and the unary form
(message/1) just passes the term to any connections without
interpreting it. In the binary form, Socket may be either a
socket identifier or an alias. When Tag Messages is checked,
incoming messages will be passed along arrows to other boxes in the
binary form. The unary form is used to 'escape' a message, that is, to
pass it on uninterpreted to any connections. The unary form is also
interpreted at the peer end of a connection, so any peer-end messages
(e.g. call_me/1) can be escaped too.

alias(Socket, Alias)

This message is used to specify an alias for a connection. The
first argument must be a socket identifier or previous alias, and the
second will become the new alias.

call_me(Alias)

This message is unusual in that it is not interpreted by the local
target box, but by the box at the other end of the connection. For
want of a better term, I call these peer-end
messages. call_me/1 allows a peer to specify its own
alias. You may also want to use the form
message(Socket, call_me(Alias)) to send a
call_me/1 to a specific connection.

Known bugs

Windows 9x client mode

The client side on MS windows 95 and 98 gets stuck when attempting
to make a connection. This only happens the first time a connection is
attempted during an oos session, and it can be unstuck by either
typing into the oos console (if enabled), or by selecting "End task"
for the oos process in the Task manager window (Accessible by
CTRL-ALT-DEL on a windows 95/98 machine). This will attempt to kill
the oos process, but will not succeed, and a side effect is to unstick
the connection. After a few moments a dialog box will appear, saying
the program is not responding: make sure you click Cancel here. The
bug seems to be due to an incompatibility between SWI prolog and the
windows 95/98 winsock.dll; it does not affect the UNIX version, and may
well not affect NT-derived windows platforms.

Raw mode and binary data

Raw mode should be adequate for text data, but it will
mangle incoming binary data, since it currently ignores CR characters
and splits the stream into lines terminated by LF characters. This
could presumably be fixed somehow, if anybody cares.

Acknowledgements

We would like to thank David Glasspool and John Fox for their
generous support, which allowed Peter Yule (p.yule@bbk.ac.uk) to
develop this box.