Tcl HomeTcl Home Hosted by
ActiveState

Google SiteSearch

Example: Network Server

Here is a small network server that illustrates the sophisticated I/O and socket facilities in Tcl. The server can accept connections from multiple clients. For each client, the server just echoes what a client sends it. Tcl manages all the I/O with different clients so the server does not have to block servicing a client or waiting for connections. You can use this server as the basis for almost any network service.

First we present the program and a summary of the commands in it. After that we'll look at parts of the program in more detail.

Echo Service

Download Example

# Echo_Server --
#	Open the server listening socket
#	and enter the Tcl event loop
#
# Arguments:
#	port	The server's port number

proc Echo_Server {port} {
    set s [socket -server EchoAccept $port]
    vwait forever
}

# Echo_Accept --
#	Accept a connection from a new client.
#	This is called after a new socket connection
#	has been created by Tcl.
#
# Arguments:
#	sock	The new socket connection to the client
#	addr	The client's IP address
#	port	The client's port number
	
proc EchoAccept {sock addr port} {
    global echo

    # Record the client's information

    puts "Accept $sock from $addr port $port"
    set echo(addr,$sock) [list $addr $port]

    # Ensure that each "puts" by the server
    # results in a network transmission

    fconfigure $sock -buffering line

    # Set up a callback for when the client sends data

    fileevent $sock readable [list Echo $sock]
}

# Echo --
#	This procedure is called when the server
#	can read data from the client
#
# Arguments:
#	sock	The socket connection to the client

proc Echo {sock} {
    global echo

    # Check end of file or abnormal connection drop,
    # then echo data back to the client.

    if {[eof $sock] || [catch {gets $sock line}]} {
	close $sock
	puts "Close $echo(addr,$sock)"
	unset echo(addr,$sock)
    } else {
	puts $sock $line
    }
}

#
# A client of the echo service.
#

proc Echo_Client {host port} {
    set s [socket $host $port]
    fconfigure $s -buffering line
    return $s
}

# A sample client session looks like this
#   set s [Echo_Client localhost 2540]
#   puts $s "Hello!"
#   gets $s line

Command Summary

socket -server command port Create a server listening socket. Call command when clients connect.
vwait varname Enter the event loop and wait until the named global variable is modified.
fileevent channel what command Register command to be called when channel is ready for what sort of I/O: readable or writable
fconfigure channel options Control the way file I/O is done using channel
eof channel Test if channel is at end-of-file.
catch script varname Evaluate script and put the result or the error result into varname. Return the result code, which is 0 for success or 1 for failure.
puts string Write string to stdout, standard output
puts channel string Write string to channel
gets channel varname Read a line of input from channel and store the result in the variable named varname. The number of bytes read is returned.
close channel Close the I/O channel

The Server Socket

A network server listens for connection requests from clients. The operating system creates a new socket connection between each client and the server, so the server can have several clients connected simultaneously. The Tcl socket -server command associates the server's listening socket with a callback that Tcl invokes when clients make connections. If you are familiar with the C-level socket APIs, Tcl socket command call hides all the details of the bind(), listen(), and accept() system calls:

set s [socket -server EchoAccept $port]

In this case, EchoAccept is called each time a client connects to the server. For this to work, Tcl must enter the event loop so it can wait for the connection requests. The vwait command is used for this purpose. It puts Tcl into the event loop until the variable is modified. In this case, we never plan to modify the variable, so we call it "forever".

What is the Event Loop?

The event loop is a facility built into the Tcl runtime. It lets you register Tcl commands that are called later when various events occur. Example events include socket connections, data ready on an I/O channel, timer events, and window system events like mouse motion and keyboard presses. There are two commands in the example that register event callbacks: the socket -server command described above, and the fileevent command described in the next section.

The event loop needs to be active for the callbacks to work. If you use wish for a Tk application, then the event loop is active automatically. However, if you use tclsh then you must activate the event loop explicitly. The vwait command enters the event loop until a variable is modified. You can use it for a short-term wait that is ended by setting the variable, or you can wait indefinetly.

Client Connections

The EchoAccept command is called when a client connects to the server. Tcl adds three arguments to the command when it makes the callback: the new socket for the connection, the client's IP address, and the client's port. In the example, the client's address and port are saved in an array, and in general a server will keep some sort of state about each client.

Line Buffering

The server puts the new socket into line-buffering mode so that each puts results in a network transmission. Otherwise Tcl buffers data until the buffer fills or a flush command is used. The fconfigure command sets up the buffering mode:

fconfigure $sock -buffering line

Event-Driven I/O

The EchoAccept procedure sets up another callback to handle the data from the client on the new connection:

fileevent $sock readable [list Echo $sock]

The Echo command is called when the server can read the socket.

With fileevent the callback is a complete command that explicitly includes the new socket. The list command is used to create a well-formed Tcl command for the callback.

End-of-File and Errors

The Echo command checks for end-of-file:

eof $sock

Then it reads a line from the socket and catches error that might occur:

catch {gets file line}

The first argument to catch is a script. Tcl will put the script through the normal evaluation cycle, so you can brace the whole script in curly braces. This example will run the gets command to read a line of input. That command could raise an error if the socket is in certain error states, so we put it inside a catch.

Finally, the server replies simply by writing the line back to the client:

puts $sock $line

The Client Side

The client is very simple:

set s [socket $host $port]
fconfigure $s -buffering line

The first command opens the TCP socket connection to the server (which triggers its call to EchoAccept). The second command sets up line buffering so each puts command results in a network transmission.

As an experiment, you can also try using the Unix telnet program to connect to your server. Most telnets take the port number as the second argument:

telnet hostname port