Saturday, February 24, 2024

Tcl socket example explained


# the source of the example code: https://www.tcl.tk/man/tcl8.5/tutorial/Tcl42.html

# paste it in a fixed width character terminal for better reading 


proc serverOpen {channel addr port} {
    puts "serverOpen: channel: $channel - from Address: $addr  Port: $port"
    puts "The default state for blocking is: [fconfigure $channel -blocking]"
    puts "The default buffer size is: [fconfigure $channel -buffersize ]"

    # Set this channel to be non-blocking.
    fconfigure $channel -blocking 0
    set bl [fconfigure $channel -blocking]
    puts "After fconfigure the state for blocking is: $bl"
 
    # Change the buffer size to be smaller
    fconfigure $channel -buffersize 12
    puts "After fconfigure buffer size is: [fconfigure $channel -buffersize ]\n"

    # When input is available, read it.
    # the script is executed at the (global level)
    #   in the interpreter in which the fileevent command was invoked
    #   the file event handler is deleted if it ever returns an error (man fileevent)
    # the event is triggered and the script is run only when the first data comes in
    # 'Server' is just a name, such that readLine can be used in other contexts, too
    fileevent $channel readable "readLine Server $channel"
}

proc readLine {who channel} {
    global didRead
    global blocked

    puts "readLine: There is input for $who on $channel"

    # on the first iteration of the while loop below there is no 'eol',
    #   the channel is in non-blocking mode, so the read fails and nothing is read, see 'man 3tcl gets'
    # on the second iteration of the 'while' loop below, this does succeed
    set len [gets $channel line]
    # the channel is non-blocking, but it may be "blocked", see 'man fblocked'
    # 1 if blocked, 0 if not blocked
    set blocked [fblocked $channel]
    puts "Characters Read: $len  Fblocked: $blocked"

    # -1 is not enough data, see 'man 3tcl gets'
    if {$len < 0} {
        if {$blocked} {
            # not enough to read
            puts "Input is blocked"
        } else {
            # the 'eof' case
            puts "The socket was closed - closing my end"
            # this closes the new channel created by 'socket -server' for the new connection
            #   the server itself is closed in the last line of the script
            close $channel;
        }
    } else {
        puts "Read $len characters:  $line"
        puts $channel "This is a return"
        flush $channel;
    }
    incr didRead;
    # puts "readLine: didRead = $didRead"
}

set server [socket -server serverOpen 33000]

# after 120 update;    # This kicks MS-Windows machines for this application

# no call for serverOpen here
# puts {before the first vwait}
# vwait didRead
# puts {after the first vwait}

# puts {before socket client}

# this sends a connection request to the server listening on port 33000
#   the server creates a (new channel) on a new port especially for this connection
#   and then it makes the client connect to this new channel
# also now the server triggers an event that will run serverOpen when processed
set sock [socket 127.0.0.1 33000]

# puts {after socket client}

# if the first vwait is commented, then serverOpen is called here
# puts {before the second vwait}
# vwait didRead
# puts {after the second vwait}

set bl [fconfigure $sock -blocking]
set bu [fconfigure $sock -buffersize]
puts "Original setting for sock: Sock blocking: $bl buffersize: $bu"

fconfigure $sock -blocking No
fconfigure $sock -buffersize 8;

set bl [fconfigure $sock -blocking]
set bu [fconfigure $sock -buffersize]
puts "Modified setting for sock: Sock blocking: $bl buffersize: $bu\n"

# Send a line to the server -- NOTE flush
set didRead 0
# this makes fileevent to trigger an event that calls lineRead when processed
puts -nonewline $sock "A Test Line"
flush $sock;

# if the first two vwait's are commented, then both serverOpen and readLine are called here
# puts {before the third vwait}
# vwait didRead
# puts {after the third vwait}

# this sends "NewLine\n" immediately after "Read 18 characters:  A Test Line"
# readLine is run only on 'wait didRead', which reads the fileevent event
#   so this iterates two times
while {$didRead < 2} {
    puts "while: didRead = $didRead"
    # Wait for didRead to be set
    vwait didRead
    if {$blocked} {
        puts $sock "Newline"
        flush $sock
        puts "SEND NEWLINE"
    }
}

# this reads the readLine "This is a return" message
set len [gets $sock line]
puts "Return line: $len -- $line"
# this closes the client socket
close $sock
# puts {before last vwait}
# this processes the last fileevent event that calls readLine for the last time
#   now readLine closes the new channel because it sees that it is closed on the client side
vwait didRead
# this closes the server, which is still listening on the 30000 port
catch {close $server}
# so 'socket' creates and manages the connection
#   similarly to a pipe or a fifo (see ~/linux/misc/bash/fifo.txt)

# 'man 3tcl gets' (three cases)
# the three non-blocking cases are:
#   successfully read
#   not enough data, returns -1 and it's blocked
#   'eof', returns -1 and it's not blocked


Friday, February 23, 2024

Tcl fileevent events explained

# paste it in a fixed width character terminal for better reading 

# do '$ touch test.txt' before you run it with '$ tclsh scriptname.tcl'

set f [open test.txt]
puts "f = $f"

set k 0
after 0 {puts "before: k = $k"}
# k is in the global space, the script is run in the global space
fileevent $f readable {incr k; puts "k = $k"}
after 500
# events are processed in the order in which they have been created
#   'vwait k' stops processing events when it finds the first k modification event
# one new fileevent event is created when fileevent is first called
#   and it is PERMANENTLY placed FIRST IN LINE in the event queue
#   this event probably creates a hook for vwait and update such that
#     two more fileevent events are created next time when
#       either the event loop is entered and there is no such a fileevent event first in the queue
#       or the queue is empty when the event loop is exited
#       these two fileevent events are placed normally last in line in the event queue
#     such a scenario is also suggested by
#       "To generate events in case of an empty queue
#       Tcl_DoOneEvent will first ask all registered event sources to setup themselves"
#       at https://tcl.sourceforge.net/c-api/notifier.html
#   error bug this is crazy and that's why it's not mentioned in man fileevent
for {set j 1} {$j<=6} {incr j} {
  puts {new for cycle}
  after 500
  after 0 {puts "before: k = $k"}
  after 500
  # update
  vwait k
  }

# update

close $f
puts "f = $f"