为什么通道事件可读在这段代码中不断触发?

Why does the channel event readable keep firing in this code?

你能告诉我这段代码中的什么会导致 chan event $sock readable [list ReadLine $sock] 在套接字重置后重复触发吗?

我试图通过重置套接字在第一行再次读取来在极其简单的本地服务器上保持套接字打开。

我不确定是浏览器问题还是Tcl代码问题。我在使用 chan flush $sock 发送每个响应之前刷新套接字;所以,我认为输入缓冲区中不应留下任何数据来触发 readable 事件。

一切正常,直到我停止使用该应用程序几分钟,然后程序 ReadLine 被重复调用 state1 但没有数据。

我在末尾包含了过程 GetLexi 因为它是我在开始发生时一直在测试的过程,也许我在那里做错了,浏览器不知道响应是完全的。我还应该补充一点,它是从过程 GetHandler 调用的,应该 return 返回到 switch 块并重置。我确实测试了调用 GetLexi.

后套接字被重置

感谢您提供的任何指导。

proc ResetSock {sock} {
  global state
  set state($sock) {1}
  chan configure $sock -encoding iso8859-1 -translation crlf
}; #close ResetSock

proc ClientConnect {sock client_ip client_port} {
   global state
   if {![info exists state($sock)]} {
     set state($sock) {1}; # 1 for first line; 2 for any other header line.
     chan configure $sock -buffering line -blocking 0 -encoding iso8859-1 -translation crlf
   }
   chan event $sock readable [list ReadLine $sock]
}; #close ClientConnect

proc ReadLine {sock} {
  global state
  set sptr state($sock)
  set row [lindex [set $sptr] 0]

  if {[catch {gets $sock line} len]} {
      # Handle this error.
      return
    }

  if {$len == 0} {
      #According to Tclhttpd, in between requests, a row 1 and len 0 
      #combination can occur. There, it is ignored.
      if {$row == 2 } {
        switch [lindex [set $sptr] 1] {
          "POST" {
                  set post [PostHandler $sock [lindex [set $sptr] 3]]
                  puts stdout "Posted: $post"
                  ResetSock $sock
                 }
          "GET" {
                 GetHandler $sock [lindex [set $sptr] 2]
                 ResetSock $sock
                }
          default { CloseSock $sock }
         }
       }
  } elseif {$len > 0} {
      switch $row {
        1 {
           # First row of request.
           lassign [split [string trim $line]] op arg rest
           lappend $sptr $op $arg
           lset $sptr 0 0 2
          }
        2 {
           # Read headers.
          }
        default { }
      }
  } else {
      # Only other option for $len is a negative value;
      # thus, an error to be handled.
  }
}; #close ReadLine

proc GetLexi { nbr sock } {
  chan flush $sock
  set sql { select img_base64 from lexi_raw where strongs_no = $nbr }
  dbws eval $sql {
    set lexi [binary format a* "{\"lexi\":\"$img_base64\"}"]
  }

  set headers ""
  append headers "HTTP/1.1 200 OK\n"
  append headers "Content-Type: application/json; charset: utf-8\n"
  append headers "Content-length: [string length $lexi]\n"
  append headers "Connection: Keep-Alive\n"
  puts $sock $headers

  chan configure $sock -translation binary
  puts $sock $lexi
}; #close GetLexi


set state(x) {}

if [catch {set listener [socket -server ClientConnect -myaddr 127.0.0.1 8000]}] {
  error "couldn't open listening socket"
}

vwait forever
catch {close $listener}

一个数据包中接收到的数据量可能不足以完成一行。当然,TCP 隐藏了大部分细节,但是当整行不可用时,仍然完全有可能触发可读事件。当您将套接字置于非阻塞模式时,这意味着 gets 将执行零长度读取(写入 line 的空字符串,len 变为 0);如果套接字阻塞,gets 将阻塞线程,直到有完整的行可用。如果通道关闭,您还可以获得零长度读取; TCP 的关闭检测不是很可靠(因为网络就是这样),但可能会发生。当套接字关闭时,所有以非阻塞模式从中读取的内容都会导致长度为零的结果。

你如何区分这些情况?

首先,我们检查流结束:

if {[chan eof $sock]} {
    # It's gone; there's not much else you can do at this point except log it I guess
    close $sock
    return
}

然后我们需要看看东西是否被阻塞,如果是,缓冲了多少:

if {[chan blocked $sock]} {
    set bufferSize [chan pending input $sock]
    if {$bufferSize > 4096} {  # 4k is enough for most things
        # Line is too long; client not well-behaved…
        # You *might* send an error here.
        close $sock
        return
    }
}

如果这两种情况都不是,我们实际上已经读了一行。

if {$len == 0} {
    # Empty line; end of HTTP header
} else {
    # etc.
}