next up previous contents index
Next: Running Unix Processes Up: No Title Previous: Input and Output

Sockets

  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].

Sockets in a Nutshell

A socket is a data structure which can be used for communication between Unix processes.

Domain
  Unix offers two domains for sockets: The Internet  domain and the Unix  domain. The first can be used for communication all over the world, whereas the latter is only applicable within one Unix system.
Type
  The types of sockets we are going to describe are stream  sockets and datagram  sockets. There are more than these types of sockets, which are used by Unix internally for implementing stream and datagram sockets themselves. See for instance socket(2)  . A stream socket is connection-oriented: Two processes will establish a one-to-one connection in using a socket. After the connection has been established a stream socket has the following features: A datagram has only the feature of being duplex: It provides no reliability and no sequencing (messages may arrive in any order). It supports sending and receiving data packets of a (usually) small size.
Protocol
  The protocol of a socket gives services used for performing communication on it. For example, the usual protocols for the Internet domain are TCP  (Internet Transmission Control Protocol) for stream sockets and UDP  (Internet User Datagram Protocol) for datagram sockets.

Internet Stream Sockets

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.

Initiating a Connection

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 gif. 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 end
    After 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 end
  initializes 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.

Convenient Connection Establishment

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)}
 

Exchanging Data

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.

Concurrency Issues

Both reading and writing to a socket might not be possible immediately. Possible reasons include:

In this case access to the socket has to be synchronized in some way. Synchronization by means of the object's state is not feasible here: it might be the case that reading is still possible even if writing is not. So, this control regime would be to restrictive. Instead the following weaker invariant holds: all read messages are performed in the order they were received by the object. The same holds for all write messages. But there is no order between reads or writes. From this more relaxed control regime you can block the state of the object by using the method flush. This method is described in Section gif.

Disconnecting

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])}
 

Unix Stream Sockets

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")]
end
          The 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 gif. In fact, after having connected to the socket, one can do the Unix.unlink immediately.

Datagram Sockets

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.

Initialization

First we will instantiate and initialize two socket objects S and T. We will choose the Unix domain. Hence
create S from Open.unixSocket 
   feat type: datagram 
   with init 
end
create T from Open.unixSocket 
   feat type: datagram
   with init 
end
  will 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.

Exchanging Data

Suppose we want to send the virtual string 'really '#"great" from socket S to socket T. Then we have to feed
{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.

Peer

An additional feature of the datagram socket is the ability to declare a socket as a peer . Suppose we want to declare socket S as a peer of socket T. This is done by
{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.

The Class Open.internetSocket

  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.

Initialization

init
   Prior to any access to a socket this method must be applied for initialization. See also socket(2)  .

Connection Establishment

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)  .

Data transfer

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)  .

Disconnect

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) 

Convenience

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.

Miscellaneous

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).

!!! Attention !!!

dOpen(+FileDescI)
   See Section gif.

!!! Attention !!!

getDesc(?FileDescIB)
   See Section gif.

The Class Open.unixSocket

  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.

Initialization

init
   Initializes the socket locally. As for Internet sockets.

Connection Establishment

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.

Data transfer

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.

Disconnect

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.

Convenience

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.

Miscellaneous

flush(how: +HowAs <= [receive send])
   Blocks the state of the object until all requests have been fulfilled. As for Internet sockets.

!!! Attention !!!

dOpen(+FileDescI)
   See Section gif.

!!! Attention !!!

getDesc(?FileDescIB)
   See Section gif.

Example: Are We Working Right Now?

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 gif) 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 gif.  
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

The FingerClient.

  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.



next up previous contents index
Next: Running Unix Processes Up: No Title Previous: Input and Output



Sven Schmeier
Tue Sep 5 10:43:51 MET DST 1995