我使用 Tcl 作为本地服务器在 Web 浏览器中运行应用程序,到目前为止,在构建它时,我已经能够在同一浏览器的多个选项卡中打开它,并在同时打开的多个浏览器中打开它同一个桌面没有任何问题。 Web 套接字用于大多数请求。到目前为止,所有请求都是短暂的 SQLite 查询。
我希望能够在应用程序中播放音频(讲座),同时让用户能够向数据库发出请求,以便他们可以在收听的同时查看一些信息。
我一定是在做一些愚蠢的事情或期望过高。首先,在一个浏览器选项卡中打开应用程序,打开一个 Web 套接字并请求从 SQLite 数据库返回数据。然后打开第二个选项卡(它最终不需要是两个选项卡,但这样做只是为了测试)并请求音频并开始播放。音频是一个单独的文件,不在数据库中;并且音频是通过 HTTP 请求的,而不是在用于数据请求的 Web 套接字上。请求音频后,Tcl 服务器不会响应其他请求,即使在 web 套接字上也是如此。如果播放音频的浏览器选项卡关闭,则会再次处理请求,而无需重新加载选项卡或重新启动服务器。
发送音频文件的过程非常简单。
proc ::HTTP::REQ::SendFile {sock filename type} {
set fp [open $filename rb]; # NB: rb — b is for BINARY; this is important.
set size [file size $filename]
# HTTP spec says headers are ISO 8859-1 and CRLF-separated
# chan configure $sock -encoding iso8859-1 -blocking 0 -translation crlf -buffering full
chan puts $sock "HTTP/1.1 200 OK"
chan puts $sock "Content-Type: text/$type; charset: utf-8"
chan puts $sock "Content-Length: $size"
chan puts $sock "Connection: Keep-Alive"
chan puts $sock ""
chan flush $sock
# Ship the data in binary mode; fcopy is VERY efficient.
chan configure $sock -translation binary
chan copy $fp $sock -size $size
close $fp
当我关闭播放音频的选项卡时,产生的错误是:
error writing "sock5605cc3a4720": connection reset by peer
while executing
"chan copy $fp $sock -size $size"
因此,我想音频文件不会在一个片段中写入浏览器。此外,如果通过单击进度条推进音频直到完成大约 2/3,则网络套接字将再次开始运行,并且当关闭音频选项卡时,不会生成上述错误。
你能告诉我 Tcl 服务器是否应该能够处理通过 HTTP 播放音频并同时响应 Web 套接字请求吗?而且,如果是这样,我做错了什么导致这种阻塞情况?
谢谢。
编辑:
我想我找到了答案,至少是有用的。它是
-command
选项的 fcopy 或 chan copy
命令。将上面的代码更改为下面的代码,现在可以立即并发地在 Web 套接字上收听音频和请求数据。
如果有更好的方法或更多关于这种方法的知识,请指出我。
另外,如果可以的话,请您解释一下 Tcl 和浏览器之间发生了什么,这样 fcopy 发生得很慢。我的意思是 Tcl 是否按照浏览器的请求分段发送文件,而不必编写代码来分段发送文件,或者类似的东西? 如果在复制完成之前播放 26 分钟的音频是否“可以”?我在 40MB 的 mp3 上进行了测试,长度约为 45 分钟,执行回调大约需要 26 分钟。
谢谢。
proc ::HTTP::REQ::SendFile {sock filename type} {
set fp [open $filename rb]; # NB: rb — b is for BINARY; this is important.
set size [file size $filename]
# HTTP spec says headers are ISO 8859-1 and CRLF-separated
# chan configure $sock -encoding iso8859-1 -translation crlf -buffering full
chan puts $sock "HTTP/1.1 200 OK"
chan puts $sock "Content-Type: text/$type; charset: utf-8"
chan puts $sock "Content-Length: $size"
chan puts $sock "Connection: Keep-Alive"
chan puts $sock ""
chan flush $sock
# Ship the data in binary mode; fcopy is VERY efficient
chan configure $sock -translation binary
chan copy $fp $sock -size $size -command [list ::HTTP::REQ::ChanCopyCleanup $fp $sock]
chan puts "chan copy a.k.a. fcopy is not blocking"
# close $fp
}
proc ::HTTP::REQ::ChanCopyCleanup {fp sock bytes {error {}} } {
close $fp
::HTTP::ResetSock $sock
chan puts stdout "Closed the file"
}
我想我找到了答案,至少是有用的。这是
或-command
命令的fcopy
选项。chan copy
此操作模式需要
-command
选项。 文档说:
如果没有 -command 选项,fcopy 会阻塞直到复制完成 [...]
-command 参数使 fcopy 在后台工作。
此外,如果可以的话,请您解释一下 Tcl 和浏览器之间发生了什么,以便 fcopy 发生得很慢。
fcopy
本身 很快,尤其是在进行二进制到二进制复制时,因为它会查看操作系统认为的最佳传输块大小并尝试避免缓冲区复制。
但是,当您将其包装在其他机器中以将其转换为网络套接字时,可能会产生额外的开销。 Websockets 内部相当复杂!如果您正在实施的子协议使用二进制帧,那么您看到的问题可能是由于帧头开销造成的;稍微缩小最大块大小会有所帮助,因为每个块都将作为合理数量的网络数据包工作。如果子协议是文本协议,那么二进制数据的开销会增加 lot;它很可能被转换为 base-64 文本(大量开销!)然后包装在 JSON 中(每帧一些相当小的开销)然后在其之上有整个帧头(OTOH,像那样使用 STOMP给你消息路由;你得到了所有的开销)。
如果您没有猜到,我认为大容量二进制数据最好通过二进制帧或什至是它们自己的直接 HTTP(S) 传输来移动,这对此非常有效。
到底出了什么问题还不是 100% 清楚,但是当你开始放入一堆层时,计算和开销肯定会增加。更加确定正在发生的事情需要对代码进行实际检测并在测试环境中运行。