06-26-2012 12:26 PM
Hello friends,
not so long time ago i found out about tcl scripts that can be executed on cisco devices and found this very exciting. So i got me some books with tcl basics and had some good time studying it, now i want to explore some more advanced topics, such as let's say client-server communication. I found examples and even some explanations, but i am just not getting it. Is there a nice fella here on this forum who could chew it over for me? Let's say for example i want to create a socket between server on one router and client on another and i want server and client to communicate via messages bidirectionaly (client sends a string then server receives it and prints out to stdout and vise versa).
So server side is something like:
proc OnConnectProcedure {channel clientaddr clientport} {
puts "$clientaddr connected"
puts $channel "Hello, Client"
close $channel
}
socket -server OnConnectProcedure 9990
vwait forever
and this server will wait for connection on port 9990, print out "x.x.x.x connected" and send hello client message to the client. Right?
What is on the other side?
set server x.x.x.x
set sckt [socket $server 9990]
gets $sock str
puts "Server said: $str"
close $sockChan
But that about when a lot of questions come to my mind... How should messages go the other way - from client to server, how and when do i use fileevent (readable, writable), fconfigure, sync option, when do i need to flush the channel? How and when should i wait for the EOF in channel?
I greatly appreciate any help and thank you in advance.
Solved! Go to Solution.
06-26-2012 01:07 PM
You can actually use straight examples of Tcl client/server communication on IOS with a few minor mods. Take a look at
http://www.tcl.tk/about/netserver.html . What you would need to do is wrap these scripts in EEM syntax. For example, on the server side:
::cisco::eem::event_register_none
namespace import ::cisco::eem::*
namespace import ::cisco::lib::*
array set arr_einfo [event_reqinfo]
# 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
}
}
Echo_Server $arr_einfo(arg1)
Register this as an EEM Tcl policy (assuming this script lives in flash:/policies as no_echo_server.tcl on the device):
event manager directory user policy flash:/policies
event manager policy no_echo_server.tcl
Then run it from EXEC mode:
event manager run no_echo_server.tcl 3333
That will start a listening socket on tcp/3333 on this device.
On the client device, create a script called no_echo_client.tcl with:
::cisco::eem::event_register_none
namespace import ::cisco::eem::*
namespace import ::cisco::lib::*
array set arr_einfo [event_reqinfo]
#
# A client of the echo service.
#
proc Echo_Client {host port} {
set s [socket $host $port]
fconfigure $s -buffering line
return $s
}
set sock [Echo_Client $arr_einfo(arg1) $arr_einfo(arg2)]
puts $sock "Hello!"
gets $sock response
puts "Response is $response"
Again, register this policy, then run it from EXEC mode with:
event manager run no_echo_client.tcl 10.1.1.1 3333
Where 10.1.1.1 is the IP address of the first (i.e., server) device.
That's it. That's a simple client/server example on IOS with EEM.
06-26-2012 01:07 PM
You can actually use straight examples of Tcl client/server communication on IOS with a few minor mods. Take a look at
http://www.tcl.tk/about/netserver.html . What you would need to do is wrap these scripts in EEM syntax. For example, on the server side:
::cisco::eem::event_register_none
namespace import ::cisco::eem::*
namespace import ::cisco::lib::*
array set arr_einfo [event_reqinfo]
# 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
}
}
Echo_Server $arr_einfo(arg1)
Register this as an EEM Tcl policy (assuming this script lives in flash:/policies as no_echo_server.tcl on the device):
event manager directory user policy flash:/policies
event manager policy no_echo_server.tcl
Then run it from EXEC mode:
event manager run no_echo_server.tcl 3333
That will start a listening socket on tcp/3333 on this device.
On the client device, create a script called no_echo_client.tcl with:
::cisco::eem::event_register_none
namespace import ::cisco::eem::*
namespace import ::cisco::lib::*
array set arr_einfo [event_reqinfo]
#
# A client of the echo service.
#
proc Echo_Client {host port} {
set s [socket $host $port]
fconfigure $s -buffering line
return $s
}
set sock [Echo_Client $arr_einfo(arg1) $arr_einfo(arg2)]
puts $sock "Hello!"
gets $sock response
puts "Response is $response"
Again, register this policy, then run it from EXEC mode with:
event manager run no_echo_client.tcl 10.1.1.1 3333
Where 10.1.1.1 is the IP address of the first (i.e., server) device.
That's it. That's a simple client/server example on IOS with EEM.
06-27-2012 01:59 AM
Thank you, Joseph. I really appreciate your help. I guess i've edited my initial message right at the same time you wrote your answer. Would you be so kind to reread it, please? I've got more questions. Now let's talk about the example you have provided. I registered these policies and ran scripts after that. Something isn't working as i assume it should.
I'm starting server part:
R1#event manager run server.tcl 19999
I'm checking if router is listening on specified port:
R1#show control-plane host open-ports
Active internet connections (servers and established)
Prot Local Address Foreign Address Service State
tcp *:23 *:0 Telnet LISTEN
tcp *:19999 *:0 EEM TCL Proc LISTEN
It does.
Now I'm starting client part and output is not quite right:
R2#event manager run client.tcl 10.10.10.1 19999
Response is
R2#
What do you think could be the reason of this partial output?
06-27-2012 06:15 AM
Likely the problem is on the server side. You need to increase the maxrun time. If you modify the server script so the event line looks like:
::cisco::eem::event_register_none maxrun 100000
That should do it.
As for your other questions, the article I mentioned has some good insight explaining what the flow of a client/server system is. In Tcl, fileevent is used to start an async handler for a connected client. You use fileevent to register a callback that will be executed whenever a client connects to the server. I'm not sure you'd ever use fileevent writeable on a server socket. You'd want to first make sure the socket is readable meaning the client is going to initiate some communication. I think the example above makes it clear how to check for EOF (you check just like you would on any file descriptor). You'd typically check for EOF whenever you read from the socket.
06-29-2012 04:36 AM
Joseph, thank you again.
I'm implementing server-client communication right now and facing a little problem here. When client connects to the server i want it to send a string of text, which server should store in the file on flash. The problem is this file actually - it's 0 size and also in few seconds after running the script ubsflash: if removed by system:
Jun 29 11:06:50.431: %USBFLASH-5-CHANGE: usbflash0 has been removed!
do you know what could be the reason? When i send the output to stdout - there is no problem at all, but storing into the file fails. I tried to close file descriptor and not to close file descriptor, i tried a lot of thing and now i'm asking for tour help again.
here is server code:
::cisco::eem::event_register_none maxrun 1000
namespace import ::cisco::eem::*
namespace import ::cisco::lib::*
array set arr_einfo [event_reqinfo]
proc START {port} {
set s [socket -server ACCEPT $port]
vwait forever
}
proc ACCEPT {sock addr port} {
global adr
set adr $addr
fconfigure $sock -buffering line
fileevent $sock readable [list WR $sock]
}
proc WR {sock} {
global adr
if {[eof $sock] || [catch {gets $sock line}]} {
close $sock
unset adr
} else {
puts "$line"; #to stdout
puts "$adr"; #to stdout
set fd [open flash:tchk.inf w]; #open file on flash for writing
puts $fd "host: $line - ip: $adr"; #writing to file
close $fd; #closing file by descriptor
close $sock; #closing socket to client
unset adr
}
}
START 19876
06-29-2012 08:11 AM
It sounds like a bug, but I have used a number of usbflash devices (e.g., the 1900s), and I have never seen this problem before. Maybe you have bad flash. What version of code are you using? A quick test of 15.2(2)T shows this to be working.
06-29-2012 08:46 AM
It does sound like a bug, since i do not really see where this problem would come from. I'm 2 x using 1921 with "c1900-universalk9-mz.SPA.150-1.M6.bin" image on it. Tomorrow i'll try to swap server and client sides to see if this is actually hardware related problem (although these routers are from the same shipment and are identical), anyway we will see what happens.
06-29-2012 10:11 AM
Could be a code issue, too. Like I said, I was unable to reproduce on 15.2(2)T. Does the same problem happen for you in tclsh:
tclsh
set fd [open "flash:tchk.inf" "w"]
puts $fd "This is a test"
close $fd
06-29-2012 10:23 AM
I tried this piece of code earlier today too - works just fine.
06-29-2012 10:58 AM
Yep, you were right - not a script problem - just ran it with no problems on other platform. Tomorrow i'll try another image with 1921 (which i use for all my experiments) to see if this problem is hardware related or image related.
Best regards,
Arseniy S. Ivanov
06-30-2012 12:56 AM
Ok, solved. For the record - the problem was with image
"c1900-universalk9-mz.SPA.150-1.M6.bin"
Best regards,
Arseniy S. Ivanov
Find answers to your questions by entering keywords or phrases in the Search bar above. New here? Use these resources to familiarize yourself with the community: