Висячие окна при использовании сокетов TCL
Я использую команду сокета TCL для связи между двумя приложениями TCL/Tk, скажем, A и B, где оба имеют связанный GUI. Сервер на B в основном принимает команды от A и возвращает результат выполнения. Проблема в том, что графический интерфейс для B зависает, как только выполнение завершено. Есть ли способ, позволяющий двум графическим интерфейсам работать независимо друг от друга? Я использую следующие сценарии для настройки соединения между двумя приложениями:
Запустите приложение A с помощью следующего сценария TCL client.tcl
proc execute { cmd } {
set cid [socket localhost 9900]
puts $cid $cmd
while { [gets $cid line] >= 0 } {
puts $line
}
close $cid
}
set pid [exec B server.tcl &]
execute {puts HelloWorld}
где server.tcl настраивает сервер через приложение B
proc server { cid addr port } {
set cmd [gets $cid]
catch $cmd result
puts $cid result
close $cid
}
socket -server server 9900
vwait forever
Цель состоит в том, чтобы позволить GUI для B быть активным, пока пользователь продолжает работать с GUI для A. Таким образом, пользователь может переключаться между двумя GUI по мере необходимости. И A, и B предоставляют разные функциональные возможности для работы с одними и теми же данными, которые должны быть доступны одновременно.
1 ответ
Код, который вы опубликовали, имеет две ключевые проблемы:
Клиент не сбрасывает свой сокет после записи в него части работы (т. Е. Команды для выполнения), потому что каналы по умолчанию (не stdin, stdout, stderr) полностью буферизируются по умолчанию из соображений производительности. Это может привести к тому, что сервер никогда не получит команду.
Сервер не обрабатывает принятый сокет в неблокирующем режиме через
fileevent
сценарий.
Кроме того, у вас есть небольшая проблема в том, что если вы когда-нибудь захотите отправить многострочные данные, ваш протокол окажется неадекватным. Вы можете или не можете заботиться об этом, и есть несколько различных способов исправить это.
Ключевая проблема 1
Лучший способ справиться с большой проблемой клиента - это использовать
fconfigure $cid -buffering none
сразу после открытия розетки. Это говорит Tcl, что он всегда должен отправлять вам данные puts
к каналу на розетке немедленно. (В качестве альтернативы используйте line
вместо none
получить буферизацию строки; это будет работать достаточно хорошо и в этом случае.) Вы также можете сделать явный сброс:
flush $cid
Это будет идти после puts
,
Ключевая проблема 2
В соответствии с передовой практикой серверный сценарий должен быть гораздо более похожим на это: одна процедура предназначена для настройки обслуживания принятого соединения с сокетом, а другая - для обработки чтения и записи сообщений. Процедура принятия (server
) отключает блокировку (и часто также регулирует буферизацию) и порядок работы (readLine
) использует eof
а также fblocked
работать, если gets
удалось или не удалось.
proc server { cid addr port } {
fconfigure $cid -blocking 0
fileevent $cid readable "readLine $cid"
}
proc readLine { cid } {
set cmd [gets $cid]
if { [eof $cid] } {
close $cid
} elseif { ![fblocked $cid] } {
catch $cmd result
puts $cid $result
close $cid
}
}
socket -server server 9900
vwait forever
Другая проблема
Другая большая проблема, если вы делаете это по-настоящему, состоит в том, что многострочные сообщения полезны. Либо вам нужно сделать readLine
выше накапливать строки, пока не получится целая команда (append
а также info complete
помогите здесь) или вам понадобится какой-то другой механизм для обработки данных. В производственном коде становится проще делегировать пакет, такой как…