我想写一个人们登录的服务器,发送/输入一些命令并注销。很多人可能同时连接,但我不希望每个人都有很多状态变量,比如“正在发送名字”,“正在发送密码”,“正处于上传命令的第二阶段“...对每个传入连接运行一次这个脚本的调用要容易得多:
puts -nonewline $out "login: "
gets $in login ;# check for EOF
puts -nonewline $out "password: "
gets $in password ;# check for EOF
while {[gets $in command] >= 0} {
switch -- $command {
...
}
}
即使有大约50个连接,每个连接创建一个解释器,内存和速度是否正常?或者你可以用线程做什么?
答案 0 :(得分:2)
进行一些实验(观察与系统工具的交互式会话)表明Tcl应用程序进程中的每个Tcl解释器(没有其他用户命令)需要介于300kB和350kB之间。用户命令和脚本是最重要的,堆栈帧(在解释器中运行任何内容所必需的)也是如此。相比之下,50个翻译环境可能会达到17MB,任何现代计算机都可以处理,而不会跳过一个节拍。请注意,口译员不允许同时执行。
线程更重,因为Tcl的线程模型让每个线程都有自己的主解释器(实际上所有解释器都严格绑定到单个线程,这种技术用于大大减少Tcl实现中的全局锁定量)。因此,建议的线程数将主要取决于部署硬件中可用CPU的数量以及代码与CPU绑定的程度,而不是IO绑定。
如果您可以使用Tcl 8.6(8.6.0在我编写此文件时标记为在存储库中发布,但未发布),那么您可以使用coroutines来建模连接状态。它们比解释器重量轻得多 ,可以用来做一种合作的多任务处理:
# Your code, with [co_gets] (defined below) instead of [gets]
proc interaction_body {in out} {
try {
puts -nonewline $out "login: "
co_gets $in login ;# check for EOF
puts -nonewline $out "password: "
co_gets $in password ;# check for EOF
if {![check_login $login $password]} {
# Login failed; go away...
return
}
while {[co_gets $in command] >= 0} {
switch -- $command {
...
}
}
} finally {
close $in
}
}
# A coroutine-aware [gets] equivalent. Doesn't handle the full [gets] syntax
# because I'm lazy and only wrote the critical bits.
proc co_gets {channel varName} {
upvar 1 $varName var
fileevent $channel readable [info coroutine]
while 1 {
set n [gets $channel var]
if {$n >= 0 || ![fblocked $channel]} {
fileevent $channel readable {}
return $n
}
yield
}
}
# Create the coroutine wrapper and set up the channels
proc interaction {sock addr port} {
# Log connection by ${addr}:${port} here?
fconfigure $sock -blocking 0 -buffering none
coroutine interaction_$sock interaction_body $sock $sock
}
# Usual tricks for running a server in Tcl
socket -server interaction 12345; # Hey, that port has the same number as my luggage!
vwait forever
如果您需要进行CPU密集型处理,并且需要注意保护登录(请考虑使用tls package来保护与SSL的连接),这是不合适的。