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 [L1 ] 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: