From: norman@flash.bellcore.com (Norman Ramsey)
Newsgroups: comp.lang.modula3
Subject: Interface for establishing internet connections
Date: 26 Jan 93 22:12:22 GMT
Article-I.D.: walter.1993Jan26.221222.25916
Organization: Bell Communications Research


I enclose an interface that makes it easy to write networked
applications.  A server calls Network.Serve to establish a port on the
local machine to which clients can connect.  Each call to
Network.Accept blocks until a client connects, then returns the new
connection.  Clients connect to a server at a known host and port by
calling Network.Connect.  The implementation uses the standard BSD IPC
tricks.

Norman


# To unbundle, "sed '1,/^# To unbundle/d' < thisfile | sh"
# To unbundle, make sure both lines appear in the file
# Tue Jan 26 17:10:37 EST 1993
echo Network.i3 1>&2
sed 's/^-//' >'Network.i3' <<'End of Network.i3'
-(* \section{Network interface}                                              *)
-(* A [[Network.T]] is a (reader, writer) pair used for talking to another   *)
-(* process.  A [[Network.Server]] is a server that can accept connections   *)
-(* from client processes, which call [[Network.Connect]].  The server       *)
-(* process should have a thread that loops and calls [[Network.Accept]]     *)
-(* over and over again.  Typical servers will fork a new thread for each    *)
-(* client connection returned by [[Network.Accept]].  Connections are       *)
-(* specified by Internet host name and port number.                         *)
-(*                                                                          *)
-(* The writer in a [[Network.T]] must be flushed explicitly to ensure       *)
-(* that the pending bits are actually sent over the network.                *)
-(*                                                                          *)
-(* <interface>=                                                             *)
-INTERFACE Network;
-IMPORT UnixError, Rd, Thread, Wr;
-
-TYPE T = RECORD rd: Rd.T; wr: Wr.T; END;
-
-PROCEDURE Connect(hostname:TEXT; port:INTEGER):T RAISES {UnixError.E};
-  (* connect over the network *)
-
-TYPE 
-  Server <: REFANY;
-
-PROCEDURE Serve(port := 0):Server RAISES {UnixError.E};
-  (* serve named port, default any port *)
-
-PROCEDURE Accept(server: Server):T RAISES {UnixError.E, Thread.Alerted};
-  (* accept a connection on the server (blocks, serialized internally) *)
-
-PROCEDURE Host(server: Server):TEXT;
-  (* return name of host served by server *)
-
-PROCEDURE Port(server: Server):INTEGER;
-  (* return port number served by server *)
-
-END Network.
End of Network.i3
echo Network.m3 1>&2
sed 's/^-//' >'Network.m3' <<'End of Network.m3'
-(* The implementation of the [[Network]] interface is based on the C code   *)
-(* in the BSD 4.3 IPC tutorial.                                             *)
-(*                                                                          *)
-(* <*>=                                                                     *)
-UNSAFE MODULE Network;
-IMPORT Fmt, M3toC, Rd, RTScheduler, Text, Thread,
-       UFileRdWr, Uin, Unetdb, Unix, UnixError, Usocket, Wr;
-(* <toplevel>=                                                              *)
-PROCEDURE Connect(host:TEXT; port:INTEGER):T RAISES {UnixError.E} =
-VAR hp    := Unetdb.gethostbyname(M3toC.TtoS(host));
-    sock  := Usocket.socket(Usocket.AF_INET, Usocket.SOCK_STREAM, 0);
-    server:  Uin.struct_sockaddr_in;
-BEGIN
-  IF hp = NIL THEN UnixError.Fail("unknown host: " & host) END;
-  IF sock < 0 THEN UnixError.Fail("can't create socket") END;
-  server.sin_family := Usocket.AF_INET;
-  bcopy(hp.h_addr_list^, ADR(server.sin_addr), hp.h_length);
-  server.sin_port := Uin.htons(port);
-  IF Usocket.connect(sock, ADR(server), BYTESIZE(server)) < 0 THEN
-    UnixError.Fail(Fmt.F("Can't connect to server at %s %s", host, Fmt.Int(port)));
-  END;
-  RETURN FromFD(sock);
-END Connect;
-(* When creating a (reader, writer) pair from a socket, I duplicate the     *)
-(* socket, which makes it possible to close both the reader and writer      *)
-(* without barfing.  If such wasteful use of open file descriptors          *)
-(* offends, the solution is to implement new reader and writer classes      *)
-(* such that the underlying file descriptor is not closed until both the    *)
-(* reader and writer are closed.                                            *)
-(*                                                                          *)
-(* <toplevel>=                                                              *)
-PROCEDURE FromFD(sock:INTEGER):T =
-VAR 
-  input  := UFileRdWr.CreateFileReader(sock);
-  output := UFileRdWr.CreateFileWriter(Unix.dup(sock), buffered := TRUE);
-  <*FATAL Rd.Failure, Wr.Failure*>
-BEGIN
-  RETURN T {input, output};
-END FromFD;
-(* This implementation of [[bcopy]] is slightly embarrassing, but I         *)
-(* find address arithmetic too painful to contemplate.                      *)
-(*                                                                          *)
-(* <toplevel>=                                                              *)
-PROCEDURE bcopy(src:ADDRESS; dest:ADDRESS; n:INTEGER) =
-VAR s, d : UNTRACED REF ARRAY [0..1023] OF CHAR;
-BEGIN
-  IF n <= 0 THEN RETURN END;
-  s := src;
-  d := dest;
-  SUBARRAY(d^, 0, n) := SUBARRAY(s^, 0, n);
-END bcopy;
-(* To serialize acceptance of new connections, a [[Network.Server]]         *)
-(* is also a mutex.                                                         *)
-(*                                                                          *)
-(* <toplevel>=                                                              *)
-REVEAL
-  Server = MUTEX BRANDED "Network.Private" OBJECT
-              server : Uin.struct_sockaddr_in;
-              sock: INTEGER;
-              hostname : TEXT;
-              port : INTEGER;
-            END;
-
-PROCEDURE Host(server: Server):TEXT    = BEGIN RETURN server.hostname END Host;
-PROCEDURE Port(server: Server):INTEGER = BEGIN RETURN server.port     END Port;
-(* The code to create an accepting socket within the server                 *)
-(* is from the IPC tutorial.  I've added the call to [[gethostname]]        *)
-(* as a convenience.  I call [[Usocket.listen]] with the maximum backlog,   *)
-(* 5, because there seems no reason to make the potential backlog any       *)
-(* smaller than the maximum.                                                *)
-(*                                                                          *)
-(* <toplevel>=                                                              *)
-PROCEDURE Serve(port := 0):Server RAISES {UnixError.E} =
-VAR server := NEW(Server);
-    hostbuf : ARRAY[0..1023] OF CHAR;
-BEGIN
-  server.sock := Usocket.socket(Usocket.AF_INET, Usocket.SOCK_STREAM, 0);
-  IF server.sock < 0 THEN UnixError.Fail("creating socket") END;
-  IF Unix.gethostname(ADR(hostbuf[0]), BYTESIZE(hostbuf)) < 0 THEN
-    UnixError.Fail("gethostname");
-  END;
-  (* It's not safe to use [[M3toC.StoT]] here because the storage holding     *)
-  (* the host name may be re-used.                                            *)
-  (*                                                                          *)
-  (* <set [[server.hostname]] from [[hostbuf]]>=                              *)
-  VAR i := 0;
-  BEGIN
-    WHILE i < NUMBER(hostbuf) AND hostbuf[i] # '\000' DO INC(i) END;
-    server.hostname := Text.FromChars(SUBARRAY(hostbuf, 0, i));
-  END;
-  server.server.sin_family := Usocket.AF_INET;
-  server.server.sin_addr.s_addr := Uin.INADDR_ANY;
-  server.server.sin_port := Uin.htons(port);
-  IF Usocket.bind(server.sock, ADR(server.server), BYTESIZE(server.server)) # 0 THEN 
-    UnixError.Fail("binding socket");
-  END;
-  VAR length := BYTESIZE(server.server);
-  BEGIN
-    IF Usocket.getsockname(server.sock, ADR(server.server), ADR(length)) # 0 THEN
-      UnixError.Fail("getsockname");
-    END;
-  END;
-  server.port := Uin.ntohs(server.server.sin_port);
-  IF Usocket.listen(server.sock, 5) # 0 THEN UnixError.Fail("listen") END;
-  RETURN server;
-END Serve;
-(* To avoid blocking the entire address space on the [[accept]] system      *)
-(* call, [[Accept]] calls [[select]] first.  Because the socket is hidden   *)
-(* within the [[Network.Server]], and because this code is protected by     *)
-(* the mutex, other code can't get in and accept the connection between     *)
-(* the time [[select]] returns and the connection is accepted.              *)
-(*                                                                          *)
-(* Normal usage is to fork a thread that loops and calls [[Accept]]         *)
-(* continually.  The thread can be stopped by alerting it.                  *)
-(*                                                                          *)
-(* <toplevel>=                                                              *)
-PROCEDURE Accept(server: Server):T RAISES {UnixError.E, Thread.Alerted} =
-VAR sock : INTEGER;
-    reads := Unix.FDSet { server.sock };
-BEGIN
-  LOCK server DO
-    IF RTScheduler.IOAlertSelect(server.sock + 1, ADR(reads), NIL, NIL) < 0 THEN
-      UnixError.Fail("select");
-    END;
-    sock := Usocket.accept(server.sock, NIL, NIL);
-    IF sock < 0 THEN UnixError.Fail("accepting socket connection") END;
-    RETURN FromFD(sock);
-  END;
-END Accept;
-BEGIN
-END Network.
End of Network.m3
echo UnixError.i3 1>&2
sed 's/^-//' >'UnixError.i3' <<'End of UnixError.i3'
-(* [[UnixError.Fail]] encapsulates the normal action taken when             *)
-(* a Unix system call fails.                                                *)
-(*                                                                          *)
-(* <interface>=                                                             *)
-INTERFACE UnixError;
-
-TYPE
-  Erec = RECORD msg: TEXT := NIL; errno : INTEGER END;
-
-EXCEPTION E(Erec);
-
-PROCEDURE Fail(msg : TEXT := NIL; errno := 0) RAISES {E};
-  (* check the real errno, add an appropriate message, and RAISE E *)
-
-END UnixError.
End of UnixError.i3
echo UnixError.m3 1>&2
sed 's/^-//' >'UnixError.m3' <<'End of UnixError.m3'
-(* <*>=                                                                     *)
-UNSAFE MODULE UnixError;
-IMPORT M3toC, Uerror;
-
-PROCEDURE Fail(msg : TEXT := NIL; en := 0) RAISES {E} =
-BEGIN
-  IF en = 0 THEN en := Uerror.errno END;
-  WITH syserr = M3toC.StoT(Uerror.GetFrom_sys_errlist(en)) DO
-    IF msg = NIL THEN msg := syserr ELSE msg := msg & ": " & syserr END;
-  END;
-  RAISE E(Erec{msg, en});
-END Fail;
-
-BEGIN
-END UnixError.
End of UnixError.m3
Newsgroups: comp.lang.modula3