使用管道模拟在非阻滞通道上处理延迟

发布于 2025-02-13 22:34:28 字数 7607 浏览 0 评论 0 原文

我正在尝试使用非阻止频道了解如何使用TCL中的Web插座。我使用浏览器打开Web套接字,并且可以使用以下代码在Web套接字简单版本的代码来解码XOR框架以读取简单的单帧消息,无论是在阻止模式还是非阻止模式下强>。它是。我认为它在非阻滞模式下起作用,因为测试消息很短,并且浏览器在编写足够数据之前不会汇总其输出缓冲区。

尽管我认为我了解非阻止渠道与阻塞的概念,而 chan获得 v v v v v v chan read ,并且可以在Ashok Nadkarni在TCL上使用示例,但我不喜欢一般来说,对渠道有深刻的了解。我可以在他的书中遵循,复制和修改有关事件驱动的I/O的示例,但他们都读了线而不是字符。使用 chan read ,在事件驱动的I/O书中似乎没有一个示例; XOR框架不包括线路断路或EOF。您只需要知道何时根据有效载荷长度停止阅读,以及对于同一消息的期望是否有更多帧。

如果,如果使用之后,使用,将输出写入非块通道,则使用将输出写入非阻止频道,而不是基于浏览器的Web套接字来运行此代码,而是使用管道示例。 简单版本代码将按原样分解,尽管它仍然可以在阻塞模式下工作。这在与下面的问题1有关的代码中显示。

我有两个问题:

  1. 如果要使用非阻滞渠道,我可以跟踪“读取状态”吗?例如,标记为非阻滞管的代码w。读取状态是使用非阻止通道的管道示例,并分解递延每一部分的消息吗?当发射可读事件时,它会根据当前的读取状态和已经读取该状态的字节的数量从输入缓冲区读取字节,并评估直到读取所有缓冲区或所有有效负载为止。它产生了相同的结果,但似乎有点hackish(无论如何,这就是我的一切);但这通常是正确的方法吗?

  2. 如果像项目2这样的设置)要正确地工作,则使用TCL用作使用Web浏览器作为GUI的桌面应用程序的高度有限的本地服务器的用例获得了什么?在具有多个Web插座的网页的情况下,每个Web插座都有多个套接字,如果多个插座尝试同时发送消息会发生什么?如果使用了非阻滞通道,TCL可以同时处理消息,或者它会像单线程应用程序中的异步代码一样,例如JavaScript?

感谢您考虑我的问题。

更简单的版本

chan configure $sock -buffering line -blocking 1 -encoding iso8859-1 -translation crlf
chan event $sock readable [list ReadLine $sock]

# After the GET request for upgrade to web socket changed to:
chan event $sock readable [list ReadXOR $sock]

proc ReadXOR {sock} {
  chan configure $sock -translation binary
  if {[catch {chan read $sock 2} XORframe]} {
      chan puts stdout "Error in sock $sock"
      return
  }

  # Never get EOF with XOR frames unless channel is closed.
  if {[chan eof $sock]} {
      CloseSock $sock
      return
  }

  binary scan $XORframe B8B8 frop mpl

  set f [string range $frop 0 0]
  set r1 [string range $frop 1 1]
  set r2 [string range $frop 2 2]
  set r3 [string range $frop 3 3]
  set op [string range $frop 4 7]
  set m [string range $mpl 0 0]
  set pl 0[string range $mpl 1 7]
  set g [binary format B8 $pl]

  binary scan $g cu n

  # Realize that there is more to checking payload size than 
  # currently coded here. For small message example, know the
  # next 4 bytes are the mask key which is to be followed by
  # an additional $n bytes of payload.

  if {[catch {chan read $sock [expr {$n+4}]} XORframe]} {
      chan puts stdout "Error in sock $sock"
      return
  }

  binary scan $XORframe cu4cu* mKey encMsg

  set l [llength $encMsg]
  set decMsg {}

  for { set i 0 } { $i < $l } { incr i } {
    lappend decMsg [binary format c [expr { [lindex $encMsg $i] ^ [lindex $mKey [expr {$i % 4}]] } ]]
  }

  chan puts stdout "Decoded message: [join $decMsg ""]"

}

非阻滞管道W。阅读状态

# The Pipe Code

lassign [chan pipe] rchan wchan
chan configure $rchan -buffering full -blocking 0 -translation binary
chan configure $wchan -buffering none -blocking 0 -translation binary
chan event $rchan readable [list ReadXOR $rchan]

dict set comPorts $rchan { state 0 bytes 0 bytesH {} bytesM {} bytesPL {} pl {} encMsg {} mKey {}}

writeFrames

after 4000 writeFrames
after 8000 [list set forever 1]

set forever 0
vwait forever

close $rchan
close $wchan

# Build fragmented binary data for simulating latency.
proc writeFrames {} {
  global wchan
  set binlist1 {}
  set binlist2 {}
  set binlist3 {}
  set binlist4 {}

  # This is the data from the MDN example for writing
  # a web socket server in Java.
  #set data {129 134 167 225 225 210 198 131 130 182 194 135}; # -- abcdef

  set data1 129
  set data2 {134 167 225}
  set data3 {225 210 198 131 130}
  set data4 {182 194 135}

  foreach i $data2 {
    lappend binlist2 [binary format cu $i]
  }
  foreach i $data3 {
    lappend binlist3 [binary format cu $i]
  }
  foreach i $data4 {
    lappend binlist4 [binary format cu $i]
  }


  # Misleading variable names because really one XOR frame
  # broken into four pieces.
  set XORframe1 [binary format cu $data1]
  set XORframe2 [join $binlist2 ""]
  set XORframe3 [join $binlist3 ""]
  set XORframe4 [join $binlist4 ""]

  after 500 [list chan puts -nonewline $wchan $XORframe1]
  after 700 [list chan puts -nonewline $wchan $XORframe2]
  after 800 [list chan puts -nonewline $wchan $XORframe3]
  after 1000 [list chan puts -nonewline $wchan $XORframe4]
}


# Read Status version of ReadXOR procedure
proc ReadXOR {sock} {
  global comPorts
  if { [dict get $comPorts $sock state] == 0 } {
    if {[catch {chan read $sock [expr { 2 - [dict get $comPorts $sock bytes] }] } head]} {
        chan puts stdout "Error in sock $sock"
        return
    }

    dict with comPorts $sock {
      set bytes [expr {$bytes + [string length $head]}]
      lappend bytesH $head
    }

    if { [dict get $comPorts $sock bytes] == 2 } {
      binary scan [join [dict get $comPorts $sock bytesH] ""] B8B8 frop mpl
      #set l 0[string range $mpl 1 7]
      binary scan [binary format B8 0[string range $mpl 1 7]] cu nt
      dict with comPorts $sock {
        set state 1
        set f [string range $frop 0 0]
        set r1 [string range $frop 1 1]
        set r2 [string range $frop 2 2]
        set r3 [string range $frop 3 3]
        set op [string range $frop 4 7]
        set m [string range $mpl 0 0]
        set pl $nt
        set bytes 0
        set bytesH {}
        chan puts stdout "state: $state, f: $f, r1: $r1, r2: $r2, r3: $r3, op: $op, m: $m, pl: $pl"
        # => state: 1, f: 1, r1: 0, r2: 0, r3: 0, op: 0001, m: 1, pl: 6
      }
    }
  }

  if { [dict get $comPorts $sock state] == 1 } {
    set b [dict get $comPorts $sock bytes]
    if {[catch {chan read $sock [expr {4-$b}]} mask]} {
        chan puts stdout "Error in sock $sock"
        return
    }

    dict with comPorts $sock {
      set bytes [expr {$bytes + [string length $mask]}]
      lappend bytesM $mask
    }
    if { [dict get $comPorts $sock bytes] == 4 } {
      binary scan [join [dict get $comPorts $sock bytesM] ""] cu4 mCode
      dict with comPorts $sock {
        set bytes 0
        set state 2
        set mKey $mCode
        set bytesM {}
        chan puts stdout $mKey
        # => 167 225 225 210
      }
    }
  }

  if { [dict get $comPorts $sock state] == 2 } {
    set b [dict get $comPorts $sock bytes]
    set l [dict get $comPorts $sock pl]
    if {[catch {chan read $sock [expr {$l-$b}]} load]} {
        chan puts stdout "Error in sock $sock"
        return
    }

    dict with comPorts $sock {
      set bytes [expr {$bytes + [string length $load]}]
      lappend bytesPL $load
    }
    if { [dict get $comPorts $sock bytes] == $l } {
      binary scan [join [dict get $comPorts $sock bytesPL] ""] cu* encoded
      dict with comPorts $sock {
        set bytes 0
        set state 3
        set encMsg $encoded
        set bytesPL {}
        chan puts stdout "encMsg: $encMsg"
        # => encMsg: 198 131 130 182 194 135
      }
    }
  }

  if { [dict get $comPorts $sock state] == 3 } {
    set decMsg {}
    set mKey [dict get $comPorts $sock mKey]
    set encMsg [dict get $comPorts $sock encMsg]
    set l [llength $encMsg ]
    for { set i 0 } { $i < $l } { incr i } {
      lappend decMsg [binary format c [expr { [lindex $encMsg $i] ^ [lindex $mKey [expr {$i % 4}]] } ]]
    }
    dict with comPorts $sock {
      set state 0
      set mKey {}
      set encMsg {}
    }
    chan puts stdout "Decoded message: [join $decMsg ""]"
    # => Decoded message: abcdef
  }
}

I'm trying to understand how to work with a web socket in Tcl using a non-blocking channel. I have the web socket open with the browser and can decode the XOR frame to read simple single-frame messages at this point, whether in blocking or non-blocking mode, using the code below labeled Simple Version from Web Socket. It's the MDN example for writing a web socket server in Java. I assume that it works in non-blocking mode because the test messages are short and/or the browser doesn't flush its output buffer until adequate data has been written.

Although I think I understand the concept of non-blocking channels versus blocking, and chan gets versus chan read, and can work the examples in Ashok Nadkarni's book on Tcl, I don't have a strong understanding of channels in general. I can follow, reproduce, and modify the examples in his book concerning event-driven I/O but they all read lines rather than characters. There does not appear to be an example in the book of event-driven I/O using chan read; and XOR frames don't include line breaks or an EOF. You just have to know when to stop reading based on the payload length, and if more frames are expected for the same message.

If, instead of running this code based on the browser's web socket message, a pipe example is used similar to that in Nadkarni's book, writing output to a non-blocking channel using after to simulate a delay, the Simple Version code would break down as is, although it would still work in blocking mode. This is shown in the code relate to question 1 below.

I have two questions:

  1. If a non-blocking channel is to be used, can I just track the "read state"? For example, the code labelled Non-blocking Pipe w. Read State is a pipe example using a non-blocking channel and breaks up the message deferring each piece? When a readable event is fired, it tries to read bytes from the input buffer based on the current read state and the number of bytes already read for that state, and evaluates until reads all the buffer or all the payload. It generates the same results, but it seems sort of hackish (that's about all I am is a hack anyway); but is it the right approach in general?

  2. If a set up like item 2) were to work correctly, what is gained for the use case of using Tcl as a highly-limited local server specific to a desktop application using a web browser as a GUI? In the scenario of a web page with multiple web sockets or multiple tabs with one web socket each what would happen if more than one socket attempted to send a message concurrently? If non-blocking channels were used, could Tcl process the messages concurrently or would it sort of be like asynchronous code in a single-threaded application, such as JavaScript?

Thank you for considering my questions.

Simpler Version

chan configure $sock -buffering line -blocking 1 -encoding iso8859-1 -translation crlf
chan event $sock readable [list ReadLine $sock]

# After the GET request for upgrade to web socket changed to:
chan event $sock readable [list ReadXOR $sock]

proc ReadXOR {sock} {
  chan configure $sock -translation binary
  if {[catch {chan read $sock 2} XORframe]} {
      chan puts stdout "Error in sock $sock"
      return
  }

  # Never get EOF with XOR frames unless channel is closed.
  if {[chan eof $sock]} {
      CloseSock $sock
      return
  }

  binary scan $XORframe B8B8 frop mpl

  set f [string range $frop 0 0]
  set r1 [string range $frop 1 1]
  set r2 [string range $frop 2 2]
  set r3 [string range $frop 3 3]
  set op [string range $frop 4 7]
  set m [string range $mpl 0 0]
  set pl 0[string range $mpl 1 7]
  set g [binary format B8 $pl]

  binary scan $g cu n

  # Realize that there is more to checking payload size than 
  # currently coded here. For small message example, know the
  # next 4 bytes are the mask key which is to be followed by
  # an additional $n bytes of payload.

  if {[catch {chan read $sock [expr {$n+4}]} XORframe]} {
      chan puts stdout "Error in sock $sock"
      return
  }

  binary scan $XORframe cu4cu* mKey encMsg

  set l [llength $encMsg]
  set decMsg {}

  for { set i 0 } { $i < $l } { incr i } {
    lappend decMsg [binary format c [expr { [lindex $encMsg $i] ^ [lindex $mKey [expr {$i % 4}]] } ]]
  }

  chan puts stdout "Decoded message: [join $decMsg ""]"

}

Non-blocking pipe w. Read State

# The Pipe Code

lassign [chan pipe] rchan wchan
chan configure $rchan -buffering full -blocking 0 -translation binary
chan configure $wchan -buffering none -blocking 0 -translation binary
chan event $rchan readable [list ReadXOR $rchan]

dict set comPorts $rchan { state 0 bytes 0 bytesH {} bytesM {} bytesPL {} pl {} encMsg {} mKey {}}

writeFrames

after 4000 writeFrames
after 8000 [list set forever 1]

set forever 0
vwait forever

close $rchan
close $wchan

# Build fragmented binary data for simulating latency.
proc writeFrames {} {
  global wchan
  set binlist1 {}
  set binlist2 {}
  set binlist3 {}
  set binlist4 {}

  # This is the data from the MDN example for writing
  # a web socket server in Java.
  #set data {129 134 167 225 225 210 198 131 130 182 194 135}; # -- abcdef

  set data1 129
  set data2 {134 167 225}
  set data3 {225 210 198 131 130}
  set data4 {182 194 135}

  foreach i $data2 {
    lappend binlist2 [binary format cu $i]
  }
  foreach i $data3 {
    lappend binlist3 [binary format cu $i]
  }
  foreach i $data4 {
    lappend binlist4 [binary format cu $i]
  }


  # Misleading variable names because really one XOR frame
  # broken into four pieces.
  set XORframe1 [binary format cu $data1]
  set XORframe2 [join $binlist2 ""]
  set XORframe3 [join $binlist3 ""]
  set XORframe4 [join $binlist4 ""]

  after 500 [list chan puts -nonewline $wchan $XORframe1]
  after 700 [list chan puts -nonewline $wchan $XORframe2]
  after 800 [list chan puts -nonewline $wchan $XORframe3]
  after 1000 [list chan puts -nonewline $wchan $XORframe4]
}


# Read Status version of ReadXOR procedure
proc ReadXOR {sock} {
  global comPorts
  if { [dict get $comPorts $sock state] == 0 } {
    if {[catch {chan read $sock [expr { 2 - [dict get $comPorts $sock bytes] }] } head]} {
        chan puts stdout "Error in sock $sock"
        return
    }

    dict with comPorts $sock {
      set bytes [expr {$bytes + [string length $head]}]
      lappend bytesH $head
    }

    if { [dict get $comPorts $sock bytes] == 2 } {
      binary scan [join [dict get $comPorts $sock bytesH] ""] B8B8 frop mpl
      #set l 0[string range $mpl 1 7]
      binary scan [binary format B8 0[string range $mpl 1 7]] cu nt
      dict with comPorts $sock {
        set state 1
        set f [string range $frop 0 0]
        set r1 [string range $frop 1 1]
        set r2 [string range $frop 2 2]
        set r3 [string range $frop 3 3]
        set op [string range $frop 4 7]
        set m [string range $mpl 0 0]
        set pl $nt
        set bytes 0
        set bytesH {}
        chan puts stdout "state: $state, f: $f, r1: $r1, r2: $r2, r3: $r3, op: $op, m: $m, pl: $pl"
        # => state: 1, f: 1, r1: 0, r2: 0, r3: 0, op: 0001, m: 1, pl: 6
      }
    }
  }

  if { [dict get $comPorts $sock state] == 1 } {
    set b [dict get $comPorts $sock bytes]
    if {[catch {chan read $sock [expr {4-$b}]} mask]} {
        chan puts stdout "Error in sock $sock"
        return
    }

    dict with comPorts $sock {
      set bytes [expr {$bytes + [string length $mask]}]
      lappend bytesM $mask
    }
    if { [dict get $comPorts $sock bytes] == 4 } {
      binary scan [join [dict get $comPorts $sock bytesM] ""] cu4 mCode
      dict with comPorts $sock {
        set bytes 0
        set state 2
        set mKey $mCode
        set bytesM {}
        chan puts stdout $mKey
        # => 167 225 225 210
      }
    }
  }

  if { [dict get $comPorts $sock state] == 2 } {
    set b [dict get $comPorts $sock bytes]
    set l [dict get $comPorts $sock pl]
    if {[catch {chan read $sock [expr {$l-$b}]} load]} {
        chan puts stdout "Error in sock $sock"
        return
    }

    dict with comPorts $sock {
      set bytes [expr {$bytes + [string length $load]}]
      lappend bytesPL $load
    }
    if { [dict get $comPorts $sock bytes] == $l } {
      binary scan [join [dict get $comPorts $sock bytesPL] ""] cu* encoded
      dict with comPorts $sock {
        set bytes 0
        set state 3
        set encMsg $encoded
        set bytesPL {}
        chan puts stdout "encMsg: $encMsg"
        # => encMsg: 198 131 130 182 194 135
      }
    }
  }

  if { [dict get $comPorts $sock state] == 3 } {
    set decMsg {}
    set mKey [dict get $comPorts $sock mKey]
    set encMsg [dict get $comPorts $sock encMsg]
    set l [llength $encMsg ]
    for { set i 0 } { $i < $l } { incr i } {
      lappend decMsg [binary format c [expr { [lindex $encMsg $i] ^ [lindex $mKey [expr {$i % 4}]] } ]]
    }
    dict with comPorts $sock {
      set state 0
      set mKey {}
      set encMsg {}
    }
    chan puts stdout "Decoded message: [join $decMsg ""]"
    # => Decoded message: abcdef
  }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

荒路情人 2025-02-20 22:34:30

如果使用TCL 8.6或更高版本,则可以将复杂性隐藏在Coroutine中。然后可以直接向前(未经测试)对该协议进行编程:

proc coread {chan count} {
    chan event $chan readable [list [info coroutine] data]
    set data {}
    set need $count
    while {![eof $chan]} {
        yield
        append data [read $chan $need]
        set need [expr {$count - [string length $data]}]
        if {$need <= 0} {return $data}
    }
    # Unexpected EOF encountered
    throw {COREAD EOF} "unexpected eof"
}

proc ReadXOR {sock} {
    try {
        set decMsg {}
        set fin 0
        while {!$fin} {
            binary scan [coread $sock 2] B16 bits
            scan $bits %1b%*3b%4b%1b%7b fin op m n
            if {$n == 126} {
                binary scan [coread $sock 2] Su n
            } elseif {$n == 127} {
                binary scan [coread $sock 8] Wu n
            }
            binary scan [coread $sock [expr {$n + 4}]] cu4cu* mKey encMsg
            for {set i 0} {$i < $n} {incr i} {
                lappend decMsg \
                  [expr {[lindex $encMsg $i] ^ [lindex $mKey [expr {$i % 4}]]}]
            }
        }
        chan puts stdout "Decoded message: [binary format c* $decMsg]"
    } trap {COREAD EOF} {} {
        chan puts stdout "Error in sock $sock"
    } finally {
        chan close $sock
    }
}

chan configure $sock -buffering none -blocking 0 -translation binary
coroutine $sock ReadXOR $sock

# Enter the event loop
vwait forever

coread PROC将准确读取所请求的字节数。如果在此之前遇到EOF,则可以使用一个可以在单个位置处理的唯一错误代码引发错误。因此,无需在每个步骤检查。

If using Tcl 8.6 or later, you can hide the complexities in a coroutine. Then the protocol can be programmed in a straight-forward manner (untested):

proc coread {chan count} {
    chan event $chan readable [list [info coroutine] data]
    set data {}
    set need $count
    while {![eof $chan]} {
        yield
        append data [read $chan $need]
        set need [expr {$count - [string length $data]}]
        if {$need <= 0} {return $data}
    }
    # Unexpected EOF encountered
    throw {COREAD EOF} "unexpected eof"
}

proc ReadXOR {sock} {
    try {
        set decMsg {}
        set fin 0
        while {!$fin} {
            binary scan [coread $sock 2] B16 bits
            scan $bits %1b%*3b%4b%1b%7b fin op m n
            if {$n == 126} {
                binary scan [coread $sock 2] Su n
            } elseif {$n == 127} {
                binary scan [coread $sock 8] Wu n
            }
            binary scan [coread $sock [expr {$n + 4}]] cu4cu* mKey encMsg
            for {set i 0} {$i < $n} {incr i} {
                lappend decMsg \
                  [expr {[lindex $encMsg $i] ^ [lindex $mKey [expr {$i % 4}]]}]
            }
        }
        chan puts stdout "Decoded message: [binary format c* $decMsg]"
    } trap {COREAD EOF} {} {
        chan puts stdout "Error in sock $sock"
    } finally {
        chan close $sock
    }
}

chan configure $sock -buffering none -blocking 0 -translation binary
coroutine $sock ReadXOR $sock

# Enter the event loop
vwait forever

The coread proc will read exactly the number of bytes requested. If EOF is encountered before that, it throws an error with a unique errorcode that can be handled in a single location. So there is no need to check at every step.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文