A frequent need in programming open applications is to connect different processes such that they are able to exchange information. A general tool for solving this problem are sockets. We will explain how to use sockets from DFKI Oz. Before we start describing how to use them, we will explain some basic concepts. The explanation is short; for more detailed information see for example [7,8].
A socket is a data structure which can be used for communication between Unix processes.
Instead of giving detailed explanations, we will follow an example tailored for understanding how to use stream sockets for the Internet domain. For this purpose, we will make use of the class Open.internetSocket.
Before any data between the server and the client can be exchanged, they
must initiate a connection. How to obtain a connection is shown in
Figure . We will perform the different steps
shown in the figure as an example.
Figure: Initiating a Stream Connection
The starting point for server and client are the topmost ovals on the two sides of the figure. We first turn our attention to the Server object. Basic initialization is performed by:
create Server from Open.internetSocket with init endAfter execution of this message, Server has initialized the local data structures it needs. After locally initializing the socket we have to provide it with a global name. We will use this global name for connecting our client to this server. Feed:
{Server bind(port:{Browse})}The Internet protocol services generate a number for you. It is shown in the browser window. We will refer to this number as OurPort later on. The combination of the host computer on which Oz is running and the generated port number gives a unique address within the Internet domain. So any other party in the Internet domain may use this pair to refer unambiguously to this socket. As next step, we have to signal that our socket is willing to accept connections from another socket. To do so, we feed:
{Server listen}Now we are ready to accept our connection. Typing:
{Server accept(host:H port:P)} {Browse H#": "#P}does nothing at the moment. But if a connection request is signaled at port OurPort the connection will be accepted. In this case H is constrained to a string giving the name of the computer the connection request was sent from. In the same way P is constrained to the respective port. The next step will connect Client to Server.
create Client from Open.internetSocket with init endinitializes our client object . By typing
{Client connect(host:localhost port:OurPort)}our client will connect to the port OurPort on the same computer (thus the virtual string localhost) . Since our server was suspended on a connection request at port OurPort it will resume, and the name of the host and the port will appear in the browser window.
This was the whole story in detail, but for convenience we provide two methods, which perform the complete connection establishment. Instead of the various method applications, the following suffices for the server:
{Server server(host:H port:P)} {Browse H#": "#P}Here P will be constrained to the automatically generated port number, and after a connection has been accepted on this port, H will be constrained to the string containing the name of the connecting host. Symmetrically, for the client side, one simply can use:
{Client client(host: HostToConnectTo port: PortToConnectTo)}
Now we can await data at our server by feeding
{Server read(list:{Browse})}Sending data from our client can be accomplished via
{Client write(vs:'Hello, good'#" old Server!")}and in the browser window (on the server side) you will see the message. By simply flipping Server and Client in the last two expressions fed, you can send data from the server to the client. Notice, however, that in this example server and client were created in the same Unix process on the same host computer. Instead we could have used two different processes and two different host computers.
Both reading and writing to a socket might not be possible immediately. Possible reasons include:
If there is no further data to be exchanged between client and server, they should be disconnected. The simplest way to do so is by feeding
{Server close} {Client close}Suppose that the server closes before the client, and the client is still sending data to the server. In this case the data will still arrive at the socket. And only after a relatively long time, the data will be thrown away, consuming valuable memory resources. To prevent from this, you can signal that you are not interested in receiving any messages, simply by typing (before closing the object!)
{Server shutDown(how: [receive])}If we are not interested in sending data anymore, we can inform Unix that we are indeed not willing to send anything more, thus
{Server shutDown(how: [send])}Both applications could have been combined into
{Server shutDown(how: [receive send])}
The whole example was located in the Internet domain. The same example in the Unix domain would look like the following:
create Server from Open.unixSocket with [init bind(takePath:"/tmp/foo") listen accept(path:{Browse})] end create Client from Open.unixSocket with [init connect(path:"/tmp/foo")] endThe only difference is in the names identifying a socket: in the Internet domain a complete binding consists of the pair of port and host name, whereas in the Unix domain it consists of a single pathname. Since the name of a Unix domain socket is a valid path name, this name will be visible at the filesystem even after closing the socket. You can remove this name physically by the Unix rm (see rm(1) ) command, or by using the procedure Unix.unlink , see Section
Datagram sockets are for connection less use. This implies that there is no distinction between server and client. The basic patterns of use are as follows.
create S from Open.unixSocket feat type: datagram with init end create T from Open.unixSocket feat type: datagram with init endwill do what we want. Now we have to name both sockets. This can be achieved by typing
{S bind(takePath:'/tmp/sss')} {T bind(takePath:'/tmp/ttt')}Similar to the automatic generation of port numbers for the Internet domain you could have typed
{S bind(path:{Browse})}and in the browser window a yet unused filename would have appeared.
{S send(vs:'really '#"great" path:'/tmp/ttt')}Notice that the pathname '/tmp/ttt' gives the destination address of our message. Receiving data is also simple. You feed
{T receive(list:A path:P)} {Browse A#' from: '#P}and A is constrained to our message "really great" and P to the destination path "/tmp/sss". The roles of sender and receiver may be toggled as you like.
{S connect(path:'/tmp/ttt')}Now any message sent from socket S will be received by socket T. Furthermore, it will suffice to specify just the data to be sent. Hence
{S send(vs:"really great")}will have the same effect as the previous example. As an additional effect, only data sent by socket T will be received at socket S. After a peer has been assigned, you can even use the usual methods read and write. Disconnecting is the same as for stream sockets.
As seen above, we provide a class Open.internetSocket. Open.internetSocket has no public attributes and the following public features:
feat error: P type: stream protocol: "" time: ~1
P must be a ternary procedure. If an error occurs, this procedure will be called. The first actual parameter is the object itself, the second the label of the method where the error occurred, and the third a virtual string, giving a short description of what has happened. The default error handler prints an error message together with the label of the method and the print name (see [1] for the concept of print name) of the object. The feature type may be the atom datagram or stream, determining the type of the socket. The feature protocol takes a virtual string specifying the protocol to use. If the empty string "" is given, the socket chooses an appropriate protocol. Usually this will be the TCP protocol (you have to give "tcp") for stream sockets, and UDP (you have to give "udp") for datagram sockets. The feature time is an integer specifying for how long a time (in milliseconds) the socket attempts to accept a connection. The value 1 means infinite time. See the following description of the accept method for more details.
init
Prior to any access to a socket this method must be applied for initialization. See also socket(2) .
bind(takePort: +TakePortI <= _ port: ?PortI <= _)
Names a socket globally. If the field takePort is present, its value is chosen for binding. Otherwise, a fresh port number value is generated by the object. This port number is accessible at the field port. See also bind(2) .
listen(backLog: +LogI <= 5)
Signals that a socket is willing to accept connections. LogI describes the maximum number of pending connections to be buffered by the system. See also listen(2) .
accept(accepted: ?ObjectO <= _ host: ?HostSB <= _ port: ?PortIB <= _)
Accepts a connection from another socket. An application of this method will suspend until a connection has been accepted or the number of milliseconds as specified by the feature time has elapsed. After this period, no connection will be accepted, and both PortIB and HostSB will be constrained to False. If a connection is accepted within the given time, the following happens: HostSB and PortIB are constrained accordingly if their fields are present. If the field accepted is present, its value is constrained to a socket object representing the accepted connection. If not present one will loose access to the socket at which the connection was accepted, because any subsequent message will refer to the accepted socket. For example, if you want to accept two connections you should type the following (it is assumed that the object Con has been created from Open.internetSocket and is prepared to accept).{Con accept(accepted:AccA)} {Con accept(accepted:AccB)}The objects AccA and AccB use the same type and the same features as the socket where the connection was accepted. See also accept(2) .
connect(host: +HostV <= localhost port: +PortI)
Connects to another socket. The address of the socket to connect to is given by HostV and PortI. See connect(2) .
read(list: ?ListS tail: TailX <= nil size: +SizeI <= 1024 len: ?LenI <= _)
This method is used for receiving data from a stream-connected socket or from a datagram socket with peer specified. If no data is available for reading, application of this method will suspend, but no blocking of the state of the object will occur. An attempt is made to read SizeI bytes from the socket. ListS is constrained to the data while the tail of ListS is constrained to TailX. LenI is constrained to the number of bytes actually read. If the socket is of type stream and the other end of the connection has been closed LenI will be constrained to 0. See also read(2) .
receive(list: ?ListS tail: TailX <= nil len: ?LenI <= _ size: +SizeI <= 1024 host: ?HostS <= _ port: ?PortI <= _)
Used for receiving data from a socket. An attempt is made to read SizeI bytes from the socket. ListS is constrained to the data while the tail of the list is constrained to TailX. LenI is constrained to the number of bytes actually read. If the socket is of type stream and the other end of the connection has been closed LenI will be constrained to 0. The source of the data is signaled by constraining HostS and PortI. See also recvfrom(2) .
write(vs: +V len: ?I <= _)
May be used for writing the virtual string V to a stream-connected socket or to a datagram socket with peer specified. I is constrained to the number of characters written. See also write(2) .
send(vs: +V len: ?I <= _) send(vs: +V len: ?I <= _ port: +PortI host: +HostV <= localhost)
Sends data as specified by V to a socket. The destination of the data may be given by HostV and PortI. If they are omitted, the data will be sent to the peer of a datagram socket or to the other end of a connection in case of a stream socket. I is constrained to the number of characters written. See also send(2) .
shutDown(how: +HowAs <= [receive send])
Signals Unix to disallow further actions on the socket. HowAs has to be a non-empty list which may contain only the atoms receive and send. The atom send signals that no further data transmission is allowed, while receive signals that no further data reception is allowed. See also shutdown(2) .
close
Closes the socket. See also close(2)
server(port: ?PortI host: ?HostV <= localhost)
Initializes a stream socket as a server.
client(port: +PortI host: +HostV <= localhost)
Initializes a stream socket as a client.
flush(how: +HowAs <= [receive send])
Blocks the state of the object until all requests for reading, receiving, writing, and sending have been fulfilled. HowAs has to be a non-empty list which may include the atoms receive and send. The atom send signals that the state should be blocked until all send (or write) requests have been fulfilled, while receive signals the same for receive (or read).
dOpen(+FileDescI)See Section.
getDesc(?FileDescIB)See Section.
The functionality provided by Open.unixSocket is like that of the class Open.internetSocket. The only difference is that Unix domain sockets operate on pathnames, and not on hosts and ports. Open.unixSocket has no public attributes and the following public features:
feat error: P type: stream time: ~1
As for Internet sockets. The feature protocol is not supported.
init
Initializes the socket locally. As for Internet sockets.
bind(takePath: +TakePathV <= _ path: ?PathSB <= _)
Names a socket globally. As for Internet sockets.
listen(backLog: +LogI <= 5)
Signals that a socket is willing to accept connections. As for Internet sockets.
accept(accepted: ?ObjectO <= _ path: ?PathS <= _)
Accepts a connection from another socket. As for Internet sockets.
connect(path: +PathV)
Connects to another socket. As for Internet sockets.
read(list: ?ListS tail: TailX <= nil size: +SizeI <= 1024 len: ?LenI <= _ )
Used for receiving data from a stream connected socket or from a datagram socket with peer specified. As for Internet sockets.
receive(list: ?ListS tail: TailX <= nil len: ?LenI <= _ size: +SizeI <= 1024 path: ?PathS <= _)
Used for receiving data from a socket. As for Internet sockets.
write(vs: +V len: ?I <= _)
May be used for writing the virtual string V to a stream-connected socket or from a datagram socket with peer specified. As for Internet sockets.
send(vs: +V len: ?I <= _) send(vs: +V len: ?I <= _ path: +PathV)
Sends data as specified by V to a socket. As for Internet sockets.
shutDown(how: +HowAs <= [receive send])
Signals Unix to disallow further actions on the socket. As for Internet sockets.
close
Closes the socket. As for Internet sockets.
server(path: +PathV)
Initializes a stream socket as a server. As for Internet sockets.
client(path: +PathV)
Initializes a stream socket as a client. As for Internet sockets.
flush(how: +HowAs <= [receive send])
Blocks the state of the object until all requests have been fulfilled. As for Internet sockets.
dOpen(+FileDescI)See Section.
getDesc(?FileDescIB)See Section.
As an example, we will show how to use the finger service from Oz.
Services are programs running on a host, where
communication is provided by a socket connection. A well known example
is the finger service.
Services use the Internet domain, thus for connecting to them we need
the name of the host computer (i.e. its Internet address) and the port
of the socket. For this reason there is the procedure
Unix.getServByName (see
Section ) which gives the port of the socket
on which the service is available. Feeding
FingerPort={Unix.getServByName finger tcp}constrains FingerPort to the port number of the service. Note that one has to specify the protocol the service uses, here the TCP protocol (as specified by the virtual string tcp). The port number of a service is unique for all computers, so this number does not depend on your localhost. To get the information from this service we have to connect to the finger service. If you want to know whether the author is working right now, then you can connect to one of our computers with the Internet address wizard.dfki.uni-saarland.de on the FingerPort. This is done by feeding the code shown in Program
create FingerClient from Open.internetSocket with client(host:"wizard.dfki.uni-saarland.de" port:FingerPort) meth getInfo(?Info) Info=local Is Ir in case <<read(list:Is tail:Ir len:$)>> of 0 then nil else <<getInfo(Ir)>> Is end end end end
For getting the information whether the author is logged in, you have to give the username of the person (in this example schulte) appended by a newline to the service first, and receive the information:
{Browse {FingerClient [write(vs:schulte#"\n") getInfo($)]}}For receiving the information we use the method getInfo which reads the entire information from the socket until it is closed. Actually, the finger service closes the connection automatically after sending its information.