gets
and puts
for Tcl). Only a few new commands must be used:
one command to associate a network connection with file descriptors,
one command for the server to identify the connecting client, and one
command to handle network timeouts.
server_open
Commandserver_open ?-nobuf? host serviceThe
server_open
command takes two parameters,
host, which must be either an Internet hostname or Internet
address, and service, which must be either a Unix service
name or an Internet port number. Service names are mapped to port
numbers via the file /etc/services
; here is an
excerpt:
echo 7/tcp echo 7/udp daytime 13/tcp daytime 13/udp telnet 23/tcp smtp 25/tcp mail http 80/tcp # World Wide Web finger 79/tcp nntp 119/tcp usenet # Network News TransferSo for example, an HTTP connection to CERN might be initiated with any of these equivalent commands:
server_open info.cern.ch http server_open info.cern.ch 80 server_open 128.141.201.214 http server_open 128.141.201.214 80
server_open
returns a list of two open file descriptors,
the first for reading and the second for writing. These file
descriptors are buffered. (The -nobuf
options returns a
single unbuffered file descriptor, opened for reading and writing.) I
usually use the Extended Tcl lassign
command with
server_open
:
lassign [server_open info.cern.ch http] r wAn error is signalled if the connection fails, so one often uses catch as well; also, because long network delays can cause your program to hang for some time, I usually use the
timeout
function
from the last lecture:
if [catch {lassign [timeout 30 {server_open info.cern.ch http}] r w} result] { puts stderr "Warning: $result" }
The Unix inetd
program solves these problems. It's a
single daemon that listens for connections on several ports at once;
when a connection comes in, it forks off the appropriate server and
connects the server's standard input and standard output connected to
the network connection. Thanks to the inetd abstraction,
servers are just as easy to write as clients.
fstat remotehost
Commandfstat fileID remotehostTo implement security, a server may want to know the name or IP address of the machine that's connecting to it (to implement access restrictions). The
fstat remotehost
command, when
invoked with a network file descriptor, returns a list of two
elements, the network address of the remote host, and (if available)
the hostname.
select
Commandselect readfileIds ?writefileIds? ?exceptfileIds? ?timeout?Opening a network connection can take a long time, and may even hang for a while before failing. My
timeout
command can be
used with server_open
to solve the problem. However,
network reads and writes are another story.
Its very important for a server to be able to timeout network I/O, because when the server is tied up it affects all the clients (whereas when a client is hung it affects no one else).
Network reads and writes can't be timed out with my
timeout
command because it uses signals, and when a read
or write system call is interrupted by a signal it is automatically
restarted! This is usually what you want, but not always in the case
of network I/O. In C, you have control over this restarting behavior,
but not in Tcl, so we need another solution.
The solution is the select
system call, which takes open
file descriptors as arguments and reports whether or not they are
ready for reading or writing. So you use select before
attempting to read or write. If select says there's data to be read,
you won't hang:
# server code while 1 { lassign [select [list stdin] {} {} 30] rfds _ _ if [lempty $rfds] { error "Timeout!" } gets $stdin line ... }
select
takes four arguments. The timeout
argument is a number of seconds (fractional) after which
select
will timeout; in this case, select
returns an empty list. The readfileIds argument is a list
of file descriptors which we would like to read from; the
writefileIds argument is a list of file descriptors we
would like to write to; and the exceptfileIds is a list of
file descriptors we are interested in checking for exceptional
conditions.
If select
doesn't timeout, that means that one of
the file descriptors is ready to be read or written.
select
returns a list of three elements: a list of the
file descriptors ready for reading, a list of the file descriptors
ready for writing, and a list of file descriptors for which some
exceptional condition occurred (these are error conditions).
Suppose, for example, that you need to add two numbers together, but
you don't want to do it on your local machine. Using the I/O-based
approach, you'd need to design a protocol to pass this addition
message from client to server, write your message, and read your
result. But suppose you could just invoke expr
in a Tcl
interpreter running on a remote machine?
The Tcl Distributed Programming extension (Tcl-DP) does just this. The client would be written as follows:
set server [dp_MakeRPCClient $serverhost $port] dp_RPC expr 4 + 1 => 5The
dp_MakeRPCClient
command makes this interpreter an
RPC client of the server at serverhost and port.
The dp_RPC command executes any Tcl command in the remote interpreter.
The server only needs to source any necessary Tcl code and declare itself a sever:
source server-code.tcl dp_MakeRPCServer 4545
Tcl-DP also supports asynchronous RPC with callbacks and distributed objects.