本地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
相关产品推荐
相关产品推荐

