Clients identify servers through a `host:port' Internet naming convention, where the host part can be a DNS-recognized name or an IP address (see Section 3.3.2 [Identity-Sensitive Server]). Servers only bind the port part explicitly, since the identity of the host providing the service is implicit. If the server host is multi-homed (i.e., has more than one IP address), clients can reach that service through any address to which they have a route. The I/O modes specified to open for the creation of client and server sockets are distinct, as shown in the following list:
This list of network-oriented I/O modes completes the list begun in Sections 2.2.2 [Pipes] and 2.3 [Sequential and Direct-Access I/O]. Again, the mode parameter to open is not case-sensitive.MODE AND SYNONYMS MEANING
`socket', `client-socket', TCP client socket
`server-socket', `tcp-server-socket' TCP server socket
`udp-client-socket' UDP client socket
`udp-server-socket' UDP server socket
To open a client socket connected to a TCP server on an Internet host at a particular port, specified as a string host_and_port in which the host name is followed by a colon and then the port number, the SETL program executes *
fd := open (host_and_port, `socket'); -- or `client-socket'For example, here is a complete SETL program to open a TCP client connection to port 13 (the ``daytime'' service) of host galt.cs.nyu.edu, read a line from the resulting stream, and print it: *
print (getline open (`galt.cs.nyu.edu:13', `socket'));This should produce that host's impression of the current weekday, date, and time, e.g.,
Thu Jul 16 21:31:52 1998For such ``well known'' port numbers as 13, which are generally listed with their familiar names in the file /etc/services on Unix hosts, the port number can be replaced by a service name such as daytime, allowing the above program to be written as *
print (getline open (`galt.cs.nyu.edu:daytime', `socket'));Running either of the above programs is approximately equivalent to issuing the following command on Unix or any system that has a telnet command:
telnet galt.cs.nyu.edu 13As we shall see, telnet can be very useful for testing servers.
The client above is a little unusual in that the file descriptor returned by open is not even saved anywhere, since it is only used once (by getline). The following is more typical, and illustrates error checking comparable to what might be done after attempting to open a file: *
fd := open (`galt.cs.nyu.edu:13', `client-socket');This program should print either the response from the server or something like
if fd om then
print (getline fd);
close (fd); -- redundant (automatic on exit)
print (command_name + `: ', last_error);
<program-name>: Connection refusedwhere <program-name> is the name of the SETL run-time interpreter or the name of the file containing the above SETL ``script'' if it is prefaced by a ``#!'' line and made executable as described in Section 2.1 [Invocation Environment].
Whereas a TCP client simply has to request a bidirectional stream connection and wait for it to be established, a TCP server has to be able to perform two quite distinct steps in providing a service. First, it must be able to listen for connection requests from anywhere, and second, it must be able to accept such requests, establishing a distinct bidirectional stream connection for each accepted client.
As in the Unix 98 C interface, and more recently the Java API, one file descriptor is used for listening, and another is used for each client-specific connection produced by the accept routine discussed below.
The listening file descriptor is the one created by open when the `server-socket' I/O mode is selected. The first argument is a string consisting of decimal digits identifying a port number on which to listen: *
server_fd := open (port_number, `server-socket'); -- ``listen''If the result of this call is not om, a listening TCP port has been created. To use the file descriptor associated with that listening port, the following call is used to wait for a new connection request from a client. When one arrives, this routine will unblock and yield a new bidirectional socket stream: *
fd := accept (server_fd); -- accept client connectionIf another connection request arrives in the interval when the server is busy after a successful accept and before it has managed to call accept again, that request is queued. It will be immediately satisfied when accept eventually is called. Up to five connection requests will be so queued; beyond that they will be refused. Most servers are structured so as to spend very little time dealing directly with clients, thus keeping this queue short. Often, this means spawning a child process to handle most of the interaction.
For our first example of a server, however, let us consider a ``sequential'' server which innocently trusts clients to read what it sends to them immediately rather than delaying and thereby blocking service to other potential clients. With appropriate authentication, this kind of simple arrangement can be useful for resource access serialization. Here, the service is just supposed to mimic the ``daytime'' service of which the opening example of Section 3.1.1 [A Client] was a client, except that this one listens on port 50013 rather than port 13: *
server_fd := open (`50013', `server-socket'); -- listenThere are two file descriptors involved here: server_fd is the one on which accept waits for client connections, and fd represents the TCP stream created by accept when a client does connect. It is not possible to send or receive data on server_fd: its one and only purpose is to listen for new client connections through accept. Conversely, accept does not create a listening server socket--that is open's job--the only purpose of accept is to create a new socket representing the server side of an individual connection after a client requests one, and return a file descriptor for the bidirectional stream embodied in the socket.
loop -- cycle indefinitely
fd := accept (server_fd); -- accept client connection
printa (fd, date); -- send current date and time to client
close (fd); -- close client connection
Once a TCP connection is established between a client and a server, the relationship between the two parties is essentially symmetric, and in the standard terminology, each one is called the peer of the other. From the SETL programmer's point of view, the symmetry is in fact complete enough that when a child process inherits from a server process a socket file descriptor that is connected to a client, it can open a SETL stream over it using the open mode `socket' (even though this nominally refers to a TCP client socket stream) instead of the more generic mode `rw' as suggested in Section 2.11 [Opening Streams Over File Descriptors]. In doing so, the child process exchanges some generality for the ability to make socket-specific enquiries on the stream such as those listed in Section 3.3.2 [Identity-Sensitive Server].
The above server should behave similarly to the standard one on port 13 of most Unix and VMS systems. Port numbers in the range 1-1023, however, can only be served by processes with superuser privileges in Unix, and are what the Internet Assigned Numbers Authority (IANA)  calls the well-known ports. The best known of these will always be listed in the file /etc/services on Unix systems, together with their associated names such as daytime for port 13. Ports 1024 through 49151 are the IANA registered ports, which simply means that the IANA registers and maintains a public list of them at ftp://ftp.isi.edu/in-notes/iana/assignments/port-numbers. Finally, ports 49152 through 65565 (the maximum possible port number) are called the dynamic and/or private ports by the IANA. These have no preassigned association, and are conventionally also called ephemeral port numbers.
Historically, many Unix systems, particularly those with a BSD heritage, have allocated port numbers dynamically from the range 1024-5000, so these will often also be included in the ``ephemeral'' ports on a Unix system. Solaris systems allocate ephemeral ports from the range 32768-65535. Indeed, there is no guarantee in general that a port number will be available at the time it is requested by a would-be server process, since the port may already be in use. This is true even for IANA registered port numbers.
New server software should strive to avoid depending on a specific port number, especially if it is user-level software that is not ineluctably tied to a well-known port. Fortunately, this is easily done by requesting port 0, which instructs the system to choose an ephemeral port number. The assigned number can be found out using the port operator. For example, the value of *
port server_fdimmediately after a successful open in the server of Section 3.1.2 [A Server] would be the integer 50013 because `50013' was the first argument of open. The following sequence of SETL statements will print a number in the range 1024-4999 on a Berkeley-derived TCP implementation, or in the range 32768-65535 under Solaris: *
server_fd := open (`0', `server-socket');A client socket implicitly uses an ephemeral port number for its own end of a connection. This also can be interrogated with the port operator, though there is rarely any reason to do so. By contrast, a server may sometimes wish to know the ephemeral port number associated with a client on the client's host, and the peer_port operator described in Section 3.3.2 [Identity-Sensitive Server] allows it to do so.
print (port server_fd);
The primordial question naturally arises as to how a client can know what port a desired service is currently being offered on, if the port number was arbitrarily chosen only after the server program began execution. One way of handling this is to have the server make the port number known to a Web server (httpd daemon), and have clients initially contact the Web server to find out the port number of the desired service--Web servers listen on the well-known port 80 or are configured to use some other fixed port number. This two-stage technique is used in the case study of Chapter 4 [WEBeye: A Case Study], where a Web document template is instantiated with dynamically assigned server port numbers and other information in response to initial client requests. In this chapter, however, for the sake of simplicity in server examples intended to illustrate other points, fixed port numbers are used.