Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Checking for an internet connection

99 views
Skip to first unread message

Cecil Westerhof

unread,
Mar 13, 2019, 3:28:05 PM3/13/19
to
Sometimes my internet provider has problems. For this I want to check
every minute if there is an internet connection and save the result.
In this way I have data I can use when I need to complain.

For this I connect with try and socket with async to an IP address on
port 80.
I would expect that there can be only an error when the network is
unreachable, but to be on the save side I create a 'NOT ALLOWED ERROR'
on other errors.
If after 30 seconds there is still no connection I generate my own
timeout, to make sure the proc does not take to much time.
When there is a connection I check if there is an error and if so
report it. Add the moment I only expect 'connection refused', other
errors I report with 'Unexpected error'.

The code:
# Default try IP-Address from www.opendns.com
proc ::dcblUtilities::getInternetConnection {{IPAddress 146.112.62.105}} {
try {
set errorMsg ""
set fd [socket -async ${IPAddress} 80]
# Maximum 30 seconds until timeout (120 * .25)
set ticks 250
set tries 120

while {${tries} > 0} {
if {[chan configure $fd -connecting] == 0} {
set error [chan configure $fd -error]

switch ${error} {
"" {
set status Connected
}
"connection refused" {
set status "Connection refused"
}
default {
set status {Unexpected error}
set errorMsg ${error}
}
}
break
}
after ${ticks}
incr tries -1
}
if {${tries} == 0} {
set status Timeout
}
close $fd
} on error msg {
set notConnRegexp "\^couldn't open socket: network is unreachable"
if {[regexp ${notConnRegexp} ${::errorInfo}]} {
set status {NOT connected}
} else {
set status {NOT ALLOWED ERROR}
set errorMsg ${::errorCode}
}
}
list ${status} ${errorMsg}
}


What do you think about this?

--
Cecil Westerhof
Senior Software Engineer
LinkedIn: http://www.linkedin.com/in/cecilwesterhof

Ashok

unread,
Mar 14, 2019, 9:34:18 AM3/14/19
to
I'm not sure the code below is guaranteed to work. The event loop must
be running when the -async option is specified and that's not the case
in your code. (The [after $ticks] does not cause the event loop to run.)

/Ashok

two...@gmail.com

unread,
Mar 14, 2019, 10:34:51 PM3/14/19
to
On Thursday, March 14, 2019 at 6:34:18 AM UTC-7, Ashok wrote:
> (The [after $ticks] does not cause the event loop to run.)

I've always wondered about how to best write a sleep routine.

Here's what I use, as otherwise using [after ms] does not let
me see any [puts] debugging statements to appear unless I were
to do an [update]. And update seems to always be frowned upon.

So, any ideas? Here's what I've come up with.

proc sleepms { ms } {
set uniq [ clock clicks ]
set ::__sleep__tmp__$uniq 0
after $ms set ::__sleep__tmp__$uniq 1
vwait ::__sleep__tmp__$uniq
unset ::__sleep__tmp__$uniq
}

The reason for the variable uniq is so that if while I
am waiting, an event (e.g. a button push in a window)
were to happen and the -command code also included a call
to this [sleepms], then it would not clobber the earlier
call by using a unique global variable name.

However, there is a problem here. If a second [sleepms]
does occur in an event, and the sleep time is longer than
the original sleep time, then it will end up waiting until
the event driven wait has finished. But at least I can get
my puts to work (on my windows system).

For example, if after starting the below, I click the
button before the 10 second sleep finishes, I get the
below output:

console show
proc ptime {arg} {
puts "[clock format [clock seconds]] $arg"
}
pack [button .b -text hello -command helloworld]
proc helloworld {} {
ptime "in helloworld 1"
sleepms 15000
ptime "in helloworld 2"
}
ptime begin
sleepms 10000
ptime after


produces:

Thu Mar 14 19:19:38 PDT 2019 begin
Thu Mar 14 19:19:43 PDT 2019 in helloworld 1
Thu Mar 14 19:19:58 PDT 2019 in helloworld 2
Thu Mar 14 19:19:58 PDT 2019 after
() 1 %


Rich

unread,
Mar 15, 2019, 10:30:33 AM3/15/19
to
two...@gmail.com wrote:
> On Thursday, March 14, 2019 at 6:34:18 AM UTC-7, Ashok wrote:
>> (The [after $ticks] does not cause the event loop to run.)
>
> I've always wondered about how to best write a sleep routine.

If you have 8.6 available, then the best way to write a sleep routine
is likely by using a coroutine. You should not get any of the ordering
problems that occur with vwait that way.

But, if you don't have 8.6, then this is not an option.

two...@gmail.com

unread,
Mar 15, 2019, 12:09:38 PM3/15/19
to
On Friday, March 15, 2019 at 7:30:33 AM UTC-7, Rich wrote:

> If you have 8.6 available, then the best way to write a sleep routine
> is likely by using a coroutine. You should not get any of the ordering
> problems that occur with vwait that way.
>

First, I'd like to mention for Cecil, that my previous
[sleepms] proc should be a sufficient direct replacement for
his use of

after ${ticks}

with

sleepms $ticks

to satisify Ashok's concern, as the use of vwait does
allow the event loop to run during the sleep.

(${ticks} is not needed here, $ticks will work as well.)

Next, as for a universal sleep proc, there is an
objection to my version which is the possibility of a
race condition, so here is a slight variation I would
now propose,

proc sleepms { ms } {
set uniq [incr ::__sleep__tmp__counter]
set ::__sleep__tmp__$uniq 0
after $ms set ::__sleep__tmp__$uniq 1
vwait ::__sleep__tmp__$uniq
unset ::__sleep__tmp__$uniq
}

This does, however, leave some tracks as there is
always the one global variable left around to allow
for multiple concurrent sleeps, although it still
does not solve the ordering issue.

And I'm still not certain there isn't a race condition
here since I don't know if [incr] is atomic here.

Cecil's use should not have that problem, so I think this
sleepms proc should work for him.



Now if there's a way to build a better sleep routine that's
as simple to use as a direct replacement for [after] that
solves the event loop issue and the ordering problem, I'd
love to see it as I'm not sufficiently skilled to work with
coroutines.

Ralf Fassel

unread,
Mar 21, 2019, 8:15:04 AM3/21/19
to
* Cecil Westerhof <Ce...@decebal.nl>
| set fd [socket -async ${IPAddress} 80]
| # Maximum 30 seconds until timeout (120 * .25)
| set ticks 250
| set tries 120
>
| while {${tries} > 0} {
| if {[chan configure $fd -connecting] == 0} {
| set error [chan configure $fd -error]
--<snip-snip>--
| }
| after ${ticks}
| incr tries -1
| }
| if {${tries} == 0} {
| set status Timeout
| }
| close $fd

If you immediately run a blocking loop, you might as well just use a
blocking socket call, which makes the error handling much easier.

Basically the idea behind an async socket is:

proc start_connect {host port} {
# if the socket call itself fails, our caller should handle that error
set fd [socket -async $host $port]

# socket call succeeded, prepare to time out after 2 seconds
# (which usually is shorter than the TCP/IP timeout)
set timer [after 2000 [list cancel_connect $fd]]

# check_connect will get called on either successful connection or failure
fileevent $fd writable [list check_connect $fd $timer]
}

# socket is connected or connection failed
proc check_connect {fd timer} {
after cancel $timer
fileevent $fd writable {}
# could also use the 'chan' family of calls here in newer TCL versions
if {[catch {fconfigure $fd -error} err] || $err ne ""} {
# error in connecting, check $err for details
# handle "internet is not reachable" condition
} else {
# connection succeeded
# handle "internet is reachable" condition
}
close $fd
}

# 2 second timeout triggered
proc cancel_connect {fd} {
fileevent $fd writable {}
catch {close $fd}
# handle "internet is not reachable" condition
}

# initiate connection
start_connect $host $port

HTH
R'

two...@gmail.com

unread,
Mar 22, 2019, 1:19:12 PM3/22/19
to
Excellent post! I was not aware that fileevent worked with sockets.
There now seems to be only one time that polling a socket makes
sense - if you need to check a connection when opening multiple
concurrent client sockets using -async.

I think this example would make a good addition to the socket
manual page in the examples section.

Thanks for this tip, I have several scripts I will use this for.

Ralf Fassel

unread,
Mar 25, 2019, 5:48:03 AM3/25/19
to
* two...@gmail.com
| I was not aware that fileevent worked with sockets.

[fileevent] works on any TCL channel, regardless how it was created.
Whether the usage itself makes sense is a different thing: i.e. an
ordinary disk-based channel ([open $filename]) will always be readable
until EOF, so using [fileevent readable] on it will usually not what one
wants, since it will always fire until the channel is closed, basically
blocking the application.

But for sockets, fileevent IMHO is *the* way to go to handle connection
and I/O.

R'
0 new messages