You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

本地Tcl Socket服务器如何同时处理HTTP音频传输与WebSocket请求?

Tcl服务器并发处理HTTP音频与WebSocket请求问题

问题场景

我用Tcl搭建了本地服务器,支持多浏览器/多标签页同时连接,通过WebSocket处理短时长SQLite查询请求。现在需要实现用户播放音频(讲座)的同时仍能发起数据库查询,但测试中遇到以下问题:

  • 单个标签页通过WebSocket请求数据正常
  • 打开第二个标签页通过HTTP请求音频后,服务器不再响应任何请求,关闭音频标签后恢复正常

初始发送音频代码

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: $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时WebSocket恢复正常,关闭标签也无报错。

核心疑问1

Tcl服务器是否支持同时处理HTTP音频传输与WebSocket请求?若支持,我哪里操作导致了阻塞?


解决方案(编辑补充)

我发现通过chan copy(即fcopy)的-command选项可解决问题,修改后的代码如下,现在能同时播放音频和处理WebSocket请求:

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: $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"
}

核心疑问2

想了解:

  • Tcl与浏览器间的传输机制是怎样的?
  • 是否无需手动分块,Tcl会按需发送音频数据?
  • 大文件传输完成耗时久是否正常?

进一步测试与优化

测试发现浏览器会忽略preload请求,仅缓冲部分音频,无-command选项时chan copy会阻塞。我的目标是实现跳转至指定时间播放音频,通过Object URL实现了本地快速传输40MB音频并跳转至30分钟位置,前端代码如下:

let d = Date.now();
let iObj = { method: 'GET' };
fetch( "http://127.0.0.11:8000/audio.mp3", iObj )
.then( response => {
    console.log("Got a response.");
    if ( !response.ok ) {
      console.log( 'Network response was not ok.' );
    } else {
      const contentType = response.headers.get('content-type');
      if (!contentType || !contentType.includes('audio/mpeg')) {
        throw new TypeError("Oops, we haven't got an audio file!");
      }
    }
    return response.blob();
  })
.then( result => {
  let objectURL = URL.createObjectURL( result );
  let a = document.createElement("AUDIO");
  // a.preload="auto";
  a.src = objectURL;
  a.controls = true;
  a.style.width="100%";
  document.body.appendChild(a);
  /*
    Perhaps, it just needs time to load the source and then
    object url can be revoked and memory released.
  */
  a.oncanplaythrough = () => {
     URL.revokeObjectURL( objectURL );
     a.currentTime = 1800;
     console.log( Date.now() - d);
     /*
       WARNING This event fires every time the user clicks
       on the timeline UI element. Thus, once revoke the
       object url, reset this to nothing.
     */
     a.oncanplaythrough = null;
  };
 })
.catch( e => {
  console.log( "Error in fetch." );
  console.log( e )
 })
 ;

内容的提问来源于stack exchange,提问作者Gary

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 06:32:15