Tcl I/O is based on the concept of channels. A channel is conceptually similar to a FILE * in C or a stream in shell programming. The difference is that a channel may be either a stream device, like a file, or a connection-oriented construct, like a socket.
A stream-based channel is created with the open command, as discussed in lesson 26. A socket-based channel is created with the socket command. A socket can be opened either as a client or as a server.
If a socket channel is opened as a server, then the Tcl program will 'listen' on that channel for another task to attempt to connect with it. When this happens, a new channel is created for that link (server -> new client), and the Tcl program continues to listen for connections on the original port number. In this way, a single Tcl server could be talking to several clients simultaneously.
When a channel exists, a handler can be defined that will be invoked when the channel is available for reading or writing. This handler is defined with the fileevent command. When a Tcl procedure does a gets or puts to a blocking device, and the device isn't ready for I/O, the program will block until the device is ready. This may be a long while if the other end of the I/O channel has gone off line. Using the fileevent command, the program only accesses an I/O channel when it is ready to move data.
Finally, there is a command to wait until an event happens. The vwait command will wait until a variable is set. This can be used to create a semaphore style functionality for the interaction between client and server, and let a controlling procedure know that an event has occurred.
Look at the example, and you'll see the socket command being used as both client and server, and the fileevent and vwait commands being used to control the I/O between the client and server.
Note: Since Tcl 8.5 many of the I/O features are also available via the chan command.
The code below sets up a simple "echo" server, sends it two strings - which are sent back - and then closes. While this happens in the same process, you can use this technique to run the server in a separate process as well. The link with the client will be via the socket and the port.
# # Define two auxiliary procs # proc serverOpen {channel addr port} { global connected set connected 1 fileevent $channel readable [list readLine Server $channel] puts "OPENED" } proc readLine {who channel} { global didRead if { [gets $channel line] < 0} { fileevent $channel readable {} after idle "close $channel;set out 1" } else { puts "READ LINE: $line" puts $channel "This is a return" flush $channel; set didRead 1 } }
The code to start the server and connect to it from a client:
set connected 0 # catch {socket -server serverOpen 33000} server set server [socket -server serverOpen 33000] after 100 update set sock [socket -async 127.0.0.1 33000] vwait connected puts $sock "A Test Line" flush $sock vwait didRead set len [gets $sock line] puts "Return line: $len -- $line" catch {close $sock} vwait out close $server
OPENED READ LINE: A Test Line Return line: 16 -- This is a return