![]() |
ExtНебольшая библиотека для организации межпроцессного взаимодействия (Inter-Process Communication, IPC) между программными компонентами, реализованными на различных языках программирования. Идея, которая родилась из другого моего проекта, Guiserver, заключается в разделении функциональности приложения на несколько исполняемых файлов/процессов. |
Основная цель Ext — обеспечение возможности вызова функций, реализованных в отдельных исполняемых процессах, с минимальными требованиями к инфраструктуре и без необходимости использования сетевых протоколов или сложных IPC-механизмов.
Решение основано на концепции разделения функциональности приложения на клиентский и серверный процессы, где клиент инициирует выполнение операций, а сервер обрабатывает запросы и возвращает результаты. Связь между процессами осуществляется через два текстовых файла, используемых как каналы передачи команд и ответов. Сообщения передаются в JSON - формате.
Основной процесс (мы будем называть его клиентским процессом) запускает, когда это необходимо, другой процесс (серверный процесс) и устанавливает соединение с ним, которое осуществляется через два текстовых файла. Клиент может вызывать функции в серверном процессе и получать результаты.
- Клиентский процесс — основное приложение, запускающее и управляющее серверным процессом.
- Серверный процесс — автономный исполняемый модуль, реализующий определённый функционал (например, обработку данных, интеграцию с внешними библиотеками и т.п.).
- Клиент при необходимости запускает сервер (если он не активен) и устанавливает с ним канал связи.
Взаимодействие может осуществляться как в синхронном, так и в асинхронном режимею
Для лучшего понимания концепции Ext вспомним одно из близких по духу решений — UCI (Universal Chess Interface), протокол взаимодействия шахматных GUI с шахматными движками. Здесь
- Графический интерфейс (клиент) запускает шахматный движок (сервер) как отдельный процесс.
- Обмен происходит через стандартные потоки ввода-вывода (stdin/stdout), где GUI передаёт команды (position, go, stop), а движок возвращает ходы и анализ.
- Протокол текстовый, простой, не требует сетевой инфраструктуры.
Эта концепция может быть полезна в следующих случаях:
- если сложно или невозможно реализовать какую-то функциональность в том языке, на котором вы пишете основное (клиентское) приложение. Например, использование Python-библиотек (Pillow, NumPy) из приложения на Harbour;
- если вы хотите выделить какую-то функциональность в отдельный процесс, чтобы не "утяжелять" основную программу;
- для выделения ресурсоёмких операций. Перенос обработки больших данных, генерации отчётов, работы с графикой и т.п. в отдельный процесс;
- если вы хотите использовать какие-то функции в нескольких приложениях - серверный процесс может быть использован с любым из них;
- При миграции legacy-систем. Постепенная замена компонентов без полной переработки архитектуры.
Примеры использования
Простейшие клиентские программы:
на Harbour
FUNCTION Main
LOCAL hExt
// LOCAL cExe := "python test_srv_1.py" /* если серверный процесс сделан на Python */
LOCAL cExe := "test_srv_1.exe"
// Запускаем серверный процесс
hExt := ecli_run( cExe )
IF Empty( hExt )
? "Can't launch " + cExe
RETURN Nil
ENDIF
/* Посылаем команду серверному процессу выполнить функцию "fu1" с переданными параметрами
и выводим в консоль результат */
? ecli_RunFunc( hExt, "fu1",{"test","10"} )
?
RETURN Nil
на Python
import sys
sys.path.append('../source/python')
from extcli import ecli_Run, ecli_RunFunc, ecli_Close
def main():
hExt = None
exe_name = "test_srv_1.exe"
# Запускаем серверный процесс
hExt = ecli_Run(exe_name)
if hExt == None:
print(f"Can't launch {exe_name}")
input("Press any key to continue...")
return None
# Посылаем команду серверному процессу выполнить функцию "fu1" с переданными параметрами
# и выводим в консоль результат
print( ecli_RunFunc(hExt, "fu1", ("test","10")) )
return None
if __name__ == "__main__":
main()
на Go
package main
import (
"fmt"
ecli "tests/go/extcli"
)
func main() {
var exeName = "test_srv_1.exe"
// Запускаем серверный процесс
hExt := ecli.Init(exeName, 1, "", "")
if hExt == nil {
fmt.Println( "Can't launch program" )
return
}
// Посылаем команду серверному процессу выполнить функцию "fu1" с переданными параметрами
// и выводим в консоль результат
fmt.Println( ecli.RunFunc( hExt, "fu1", []string{"test","10"}, false ) )
ecli.Close(hExt)
}
Соответствующие серверные программы:
на Harbour
FUNCTION Main( ... )
LOCAL hExt
// Сначала выполняем esrv_Init() для инициализации соединения с вызывающим (клиентским) процессом
hExt := esrv_Init( hb_arrayToParams( hb_aParams() ) )
IF Empty( hExt )
RETURN Nil
ENDIF
// И ждем команды от вызывающего (клиентского) процесса
esrv_Wait( hExt )
RETURN Nil
// Функция, которую может запросить вызывающий (клиентский) процесс
FUNCTION FU1( aParams )
RETURN "fu1: " + Time() + " " + aParams[1] + " " + aParams[2]
на Python
import sys
sys.path.append('../source/python')
from extsrv import esrv_Init, esrv_Wait
# Функция, которую может запросить вызывающий (клиентский) процесс
def fu1( aparams ):
return "fu1: " + aparams[0] + " " + aparams[1]
def main( aparams ):
# Сначала выполняем esrv_Init() для инициализации соединения с вызывающим (клиентским) процессом
hExt = esrv_Init(aparams)
if hExt == None:
return None
# И ждем команды от вызывающего (клиентского) процесса
esrv_Wait(hExt)
return None
if __name__ == "__main__":
main( sys.argv[1:] )
на Go
package main
import "os"
import esrv "tests/go/extsrv"
func main() {
var sParam = ""
// Регистрация функций, которые могут быть вызваны клиентским процессом
esrv.RegFunc("fu1", fu1)
// Формируем строку аргументов для передачи в esrv.Init()
for i := 1; i < len(os.Args); i++ {
sParam = sParam + "\n" + os.Args[i]
}
// Выполняем esrv_Init() для инициализации соединения с вызывающим (клиентским) процессом
if esrv.Init(sParam) != 0 {
esrv.WriteLog( "Init failed" )
return
}
// И ждем команды от вызывающего (клиентского) процесса
esrv.Wait()
// Закрываем соединение
esrv.Exit()
}
// Функция, которую может запросить вызывающий (клиентский) процесс
func fu1(p []string) string {
return "fu1 is launched " + p[0] + " " + p[1]
}
Документация
Подробные инструкции и примеры кода доступны в репозитории проекта.
Обратите также внимание на статью в заметках.
Ваше имя:
Адрес электронной почты:
(не предназначено к показу)
|   |
