The simplest possible socket demonstration

AMG: It's not too hard.

In this example there are two computers, client and server. The client's IP address is, let's say, 10.0.0.1. It will connect to the server (IP: 10.0.0.2) on TCP port 12345. Here's the code each side runs:

Client (IP: 10.0.0.1)

 set chan [socket 10.0.0.2 12345]         ;# Open the connection
 puts $chan hello                         ;# Send a string
 flush $chan                              ;# Flush the output buffer
 puts "10.0.0.2:12345 says [gets $chan]"  ;# Receive a string
 close $chan                              ;# Close the socket

Server (IP: 10.0.0.2)

 proc accept {chan addr port} {           ;# Make a proc to accept connections
     puts "$addr:$port says [gets $chan]" ;# Receive a string
     puts $chan goodbye                   ;# Send a string
     close $chan                          ;# Close the socket (automatically flushes)
 }                                        ;#
 socket -server accept 12345              ;# Create a server socket
 vwait forever                            ;# Enter the event loop

Of course the server should already be up and running before the client starts.

What does it look like?

Client (IP: 10.0.0.1)

 10.0.0.2:12345 says goodbye

Server (IP: 10.0.0.2)

 10.0.0.1:49100 says hello

Discuss.


PT: You can in Tcl 8.5 make a significantly shorter example by using apply. Here is an RFC868 [1 ] DAYTIME server:

 socket -server {apply {{c a p} {puts $c [clock format [clock seconds]];close $c}}} 13
 vwait forever

Chad observes that on the server side, doing something like

 set S [socket -server accept 12345]

won't result in being able to

 puts $S "anything"

later. This makes a lot of sense, I'm sure, but seems a bit counter-intuitive to us less-immersed types.

While the example above certainly works and is the simplest possible, something more will need to be done if you want to have an ongoing conversation between client and server once the connection is made. At this point the only handle we have on the readable/writable socket (which is NOT returned by the socket -server call) is $chan. $chan, which points to the socket channel, is local to the proc accept (and dies with it), but the channel itself is not. So, we can

 proc accept {chan addr port} {           ;# Make a proc to accept connections
     puts "$addr:$port says [gets $chan]" ;# Receive a string
     puts $chan "let's talk"; flush $chan ;# Send a string
     set ::realS $chan                    ;# don't forget name of socket when proc finishes
 }        

...and then, from anywhere else...

 gets $::realS

Since we're not relying on close $chan to flush the channel, we have to do it explicitly. If we're going to be communicating with newline-terminated strings only, we can use fconfigure to automate the flushing.

 proc accept {chan addr port} {           ;# Make a proc to accept connections
     fconfigure $chan -buffering line     ;# automate flushing
     puts "$addr:$port says [gets $chan]" ;# Receive a string
     puts $chan "let's talk"              ;# Send a string
     set ::realS $chan                    ;# don't forget name of socket when proc finishes
 }                                        ;#

(At the risk of straying too far from the intent of this page as declared in its title) if the conversation that will then ensue will not follow a rigid script, blocking issues will arise. This means that if the client has not sent anything, then

 gets $::realS

will hang your application until the client does send something. To avoid this, we should use fileevent to manage pulling data off the socket when available.

 proc handleComm S {puts [gets $S]}       ;# Make a proc to receive communications
 proc accept {chan addr port} {           ;# Make a proc to accept connections
     fconfigure $chan -buffering line     ;# automate flushing
     puts "$addr:$port says [gets $chan]" ;# Receive a string
     puts $chan "let's talk"              ;# Send a string
     fileevent $chan readable \
         [list handleComm $chan]          ;# set up to handle incoming data when necessary
 }                                        ;#

handleComm will be called every time the client side flushes data to the socket. In the meantime, the name of the socket channel is held in the event registered by fileevent. Of course, fconfigure and fileevent are identically applicable on the client side. We could then perhaps call this the simplest possible useful socket demonstration.


See also: