The TCPNet.Mod module and TCPNet Device in Oberon Workstation provide client and server internet connectivity to Oberon programs. Each of the (max 32) connections works with 2 regular Oberon files. One file receives TCP data, the other supplies data to be transmitted.
The TCPNet Device works concurrently with RISC5 execution. As data transfers to the input file occur beyond the (Oberon) file-buffer-end, and data transfers from the output file are just read-only, the TCP data stream events are not perceived during regular Oberon command processing, and they do not involve Oberon's main memory.
To make the TCPNet Device's progress available to Oberon, a (periodic) synchronizing update is performed inside the TCPNet.Mod module, using an Oberon Task. This Task obtains a service request word from the Device, where each bit represents a connection that needs attention, and then updates that connection.
The update involves 3 main actions:
PROCEDURE Open* (conn: TCPConn; in, out: Files.File; address: ARRAY OF CHAR; port: INTEGER; handler: Handler; VAR res: INTEGER);
This procedure requests the establishment of a connection conform the given parameters. The action occurs asynchronously by macOS system calls to its sockets library. When the requested connection succeeds, streaming is potentially immediately started if either local or remote sides have data to send. The establishment of the connection (among other events) is reported via status calls to your handler procedure.
www.reactive-instruments.eu 126.96.36.199 localhost 127.0.0.1 myothercomp.localThe address string is max. 63 chars (excluding trailing null)
Handler* = PROCEDURE (conn: TCPConn; stat:SET): INTEGER;
The handler procedure is periodically called by TCPNet to inform your program of the latest status/progress of your connections, and allows your program to send a network response and/or operate on the new data. The handler typically controls (large) parts of your (network-based) program that are not governed by direct Oberon user-commands.
It is generally safe to call TCPNet.Mod procedures from your handling code, including when they affect other connections (opening, closing), but care should be taken to avoid accessing connections that may be part of a different program (unrelated programs). For example, a web browser and a terminal application should not access each other's connections, which would be nevertheless possible using TCPNet.Enumerate(). A recommended practical safety measure is by making use of the context field in the TCPConnDesc record, which can be extended to a unique type reserved for a particular program.
You may use the TCPConn.laddr and TCPConn.lport fields of the connection record to inspect local IP address, and local port. The TCPConn.raddr and TCPConn.rport contain the remote peer address and port. Addresses are formatted in IPv4 dotted quad notation. Port numbers are in Oberon native integer form.
PROCEDURE Close*(conn: TCPConn);
Closing a connection is useful when a protocol "cycle" has ended, and no new actions are planned, or an error occurred. Closing saves resources on server and client sides and prevents "leaking" the connection capacity (32 slots) of the TCPNet Device. The Close procedure may be called from within the body of the handler or as part of a normal Oberon command. A system- or program-wide disconnect or a specific server-shutdown can be accomplished with the Enumerate() procedure i.c.w. a EntryHandler procedure that closes connections according to the corresponding properties.
After closing, any references to the connection record and its files are erased in the TCPNet module and TCPNet Device.
The connection record is subject to normal Oberon garbage collection. Since closing the connection has no impact on the in- and out files, these files have kept their content as it existed when closing the connection. No Close, Register or Purge etc. operations are performed on the in- and out files.
PROCEDURE Listen* (conn: TCPConn; in, out: Files.File; port: INTEGER; handler: Handler; VAR res: INTEGER);
This procedure reserves a serving connection slot where potentially a single remote client can connect to. If multiple of such listening slots are created with the same port number, effectively a "server" is established. The capacity of the server is equal to the number of created slots; the capacity may be fixed to a target amount by implementing a refreshing scheme where a new listening connection is created each time another is done being served and therefore closed.
Listening connection slots, once connected, behave essentially the same as directly requested connections. Whereas directly requested connections specify a definite peer address and start contacting the peer immediately, the serving connection slots wait to be contacted by any remote computer.
The same type of handler procedure applies to a listening connection as to a directly created connection (described in "Responding to handler calls"). A single handler for listening connections with a given port number would typically be implemented for that port number.
The parameters descriptions of Listen() are the same as those of Open(), except for a slightly different meaning of port:
The TCPNet Device allows a maximum of 4 different serving ports to be in use, thus supporting 4 servers to be working at once.
As long as one or more listening connections on a given port exist (idle or already connected), the listening socket for that port is kept alive by the TCPNet Device. New connections are accepted and reported (via the hostc flag in the handler) until no free (idle) listening slots remain.
Without free listening slots, new visitors will still be accepted however, but "queued" instead, until new free listening slots are created. With this mechanism, in a busy server with short lived connections, the "availability" perceived by visitors is improved. Their connection request is not outright refused but the effective communication with Oberon is only delayed.
When all listening connections for a given port (idle and/or connected) are closed, the listening socket is automatically removed by the TCPNet Device, so any connection attempts will then result in a "connection refused" response.
Note that after the listening socket is removed, macOS inhibits the creation of a new listening socket for the same port during a few tens of seconds. If in that holdoff period you attempt to create new listen connections they will fail (i.e. lead to error flagged calls to your handler).
When testing server operation during development, the macOS terminal command 'lsof -i' might be useful to list the current connections.
PROCEDURE Send*(conn: TCPConn);
The TCPNet.Send() procedure is used to notify the TCPNet Device of new data appended to the output file; it prompts the TCPNet Device to resume transmission if it was pausing at the end of the previously appended data.
Explicitly prompting the transmission allows a client-determined accumulation of data in the output file before it is sent. E.g. in a chat program, the choice could be made to send each typed character individually, or to send only entire lines of text.
When data to be sent is produced within a handler body, the Send() procedure is not needed, since transmission of any appended new data in that case is implicitly taken care of by the service request mechanism of TCPNet.Mod
PROCEDURE InFile*(conn: TCPConn): Files.File; PROCEDURE OutFile*(conn: TCPConn): Files.File;
Return the input- and output files of the connection.
PROCEDURE Enumerate*(proc: EntryHandler);
Calls the supplied EntryHandler procedure successively with all current connection records kept by TCPNet.Mod and the TCPNet Device, until one sets its continue parameter to FALSE. Typically, to filter the connection records, your EntryHandler would inspect the TCPConn fields and in particular the custom extension type and contents of its context field