|
|
Многопоточный сервер
При проектировании сервера, важно предусмотреть возможность одновременной обработки запросов от нескольких клиентов. Одним из способов
реализации такого подхода - использование многопоточности. В Tcl это реализуется с помощью библиотеки thread.
В этой статье, мы рассмотрим пример сервера, обслуживающего клиентов с постоянным удержанием сетевого соединения.
# загружаем библиотеку потоков
package require Thread
# запускаем слушатель на 7000 tcp порту
# при получении запроса от клиента, будет запускаться процедура _Accept
socket -server _Accept 7000
# входим в обработчик tcl событий
# это важная строка, без неё tcl-приложение завершится.
# Для Tk-приложения данная строка не нужна.
vwait forever
# запуск процедуры создания потоков
proc _Accept {idsocket ipaddr port} {
# порождаем поток только в свободные циклы работы интерпретатора
after idle [list Accept $idsocket $ipaddr $port]
}
# Собственно, функция порождения потока
proc Accept { idsocket addr port } {
# переменная с указанием ваших действия перед порождением потока
set threadinit {
# если необходимо, загружаем сторонние библиотеки
foreach { p } { mkZiplib base64 Pgtcl md5 tls } {
package require $p
}
# если необходимо, загружаем исходный tcl код, расположенный в других файлах
foreach { s } { lib log protocol error mainthread } {
uplevel #0 source $s.tcl
}
# не завершаем поток, ибо будет запущен событийный сокетный обработчик
thread::wait
}
# порождаем поток, выполнив предварительные действия, описанные в переменной threadinit
set tid [thread::create $threadinit]
# передаем сокет с главного интерпретатора в интерпретатор порождённого потока
thread::transfer $tid $idsocket
# запускаем поток в асинхронном режиме
thread::send -async $tid [list Accept:Thread $tid $addr $idsocket]
}
# процедура создания событийного чтения данных из сокета
proc Accept:Thread { tid addr idsocket } {
# конфигурируем сокет
# важно, для событийной обработки чтения данных с сокета, сокет должен быть в неблокирующем режиме
fconfigure $idsocket -blocking 0 -buffering full -encoding binary -translation binary
# создаём событийный обработчик чтения данных с потока
# при возникновении данных на сокете, запускается процедура SocketEventHandler:Thread
fileevent $idsocket readable [list SocketEventHandler:Thread $idsocket]
}
# читаем данные с сокета
proc SocketEventHandler:Thread { idsocket } {
if { [eof $idsocket] || [catch {gets $idsocket pkt}] } {
# уничтожаем поток
Exit:Thread $idsocket
} else {
# здесь помещаем код по обработке полученного пакета
# переменная pkt содержит полученные данные от клиента
}
}
# процедура завершения потока
proc Exit:Thread { idsocket } {
# закрываем сокет
close $idsocket
# уничтожаем, останавливаем поток
thread::release
}
Такой тип организации сервера, лучше использовать для клиентов, которые постоянно удерживают сокетное соединение. В данном случае, для каждого клиента
создается новый поток. Этот подход приемлем при небольшом количестве подсоединяемых клиентов (50-100). Для обработки большего количества клиентов,
сервер необходимо строить по технологии пула потоков. Это мы рассмотрим в следующей статье.
|