1
0
mirror of https://github.com/Bayselonarrend/OpenIntegrations.git synced 2025-08-10 22:41:43 +02:00

Пересборка TCP сервера с добавлением класса handler

This commit is contained in:
Anton Titovets
2025-01-22 21:03:09 +03:00
parent b76fe24891
commit 59d2f806a0
19 changed files with 586 additions and 355 deletions

View File

@@ -96,6 +96,7 @@ version = "0.1.0"
dependencies = [
"addin1c",
"dashmap",
"once_cell",
"serde_json",
]

View File

@@ -15,4 +15,5 @@ strip = true # Automatically strip symbols from the binary.
[dependencies]
addin1c = "0.5.0"
serde_json = "1.0.137"
dashmap = "6.1.0"
dashmap = "6.1.0"
once_cell = "1.20.2"

View File

@@ -0,0 +1,105 @@
use std::io::{Read, Write};
use std::net::TcpStream;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use dashmap::DashMap;
use once_cell::sync::Lazy;
use serde_json::json;
use crate::commons::getset::ValueType;
pub mod getset;
pub static CONNECTIONS: Lazy<DashMap<String, Arc<Mutex<TcpStream>>>> = Lazy::new(DashMap::new);
pub fn receive_data(connection_id: String, max_size: usize) -> Box<dyn ValueType> {
let mut stream = match get_stream(connection_id) {
Ok(s) => s,
Err(e) => return Box::new(create_error(&e)),
};
let mut buffer = Vec::new();
let mut temp_buffer = vec![0; 1024]; // Буфер для чтения данных
let mut total_read = 0; // Счетчик прочитанных байт
loop {
let remaining = if max_size > 0 {
max_size.saturating_sub(total_read) // Оставшееся место до max_size
} else {
usize::MAX // Без ограничения по размеру
};
if remaining == 0 {
break;
}
let to_read = remaining.min(temp_buffer.len());
match stream.read(&mut temp_buffer[..to_read]) {
Ok(0) => {
break;
}
Ok(size) => {
total_read += size;
buffer.extend_from_slice(&temp_buffer[..size]);
if total_read >= max_size && max_size > 0 {
break;
}
}
Err(e) => {
return Box::new(create_error(&format!("Error reading from stream: {}", e)));
}
}
}
Box::new(buffer)
}
/// Отправляет данные в соединение по указанному УИД
pub fn send_data(connection_id: String, data: Vec<u8>) -> String {
let mut stream = match get_stream(connection_id) {
Ok(s) => s,
Err(e) => return create_error(&e),
};
// Пытаемся отправить данные в поток
match stream.write_all(&data) {
Ok(_) => create_success(),
Err(e) => create_error(&format!("Error writing to stream: {}", e)),
}
}
/// Закрывает соединение по указанному УИД
pub fn close_connection(connection_id: String) -> String {
if CONNECTIONS.remove(&connection_id).is_some() {
create_success()
} else {
create_error("Connection not found")
}
}
pub fn get_stream(connection_id: String) -> Result<TcpStream, String> {
// Найти соединение по идентификатору
let stream = CONNECTIONS
.get(&connection_id)
.ok_or_else(|| format!("Connection '{}' not found", connection_id))?;
// Заблокировать доступ к потоку и клонировать его
let stream = stream.lock().map_err(|e| e.to_string())?;
match stream.try_clone().map_err(|e| e.to_string()) {
Ok(stream) => {
stream.set_read_timeout(Some(Duration::from_secs(30))).ok();
stream.set_write_timeout(Some(Duration::from_secs(30))).ok();
Ok(stream)
}
Err(e) => Err(e.to_string()),
}
}
pub fn create_error(message: &str) -> String {
json!({ "result": false, "error": message }).to_string()
}
pub fn create_success() -> String {
json!({ "result": true }).to_string()
}

View File

@@ -1,221 +0,0 @@
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::time::{Duration, Instant};
use serde_json::{json};
use std::sync::{Arc, Mutex};
use crate::core::getset::ValueType;
use crate::component::AddIn;
/// Запускает сервер на указанном порту
pub fn start(tcp: &mut AddIn) -> String {
match TcpListener::bind(format!("0.0.0.0:{}", tcp.port)) {
Ok(listener) => {
tcp.listener = Some(listener);
json!({ "result": true }).to_string()
}
Err(e) => create_error(&e.to_string())
}
}
/// Ожидает новое соединение с возможностью указания таймаута (в секундах)
pub fn wait_for_connection(tcp: &mut AddIn, timeout: i32) -> String {
if let Some(listener) = &tcp.listener {
let start_time = Instant::now();
listener.set_nonblocking(true).ok();
while start_time.elapsed() < Duration::from_secs(timeout as u64) || timeout == 0 {
match listener.accept() {
Ok((stream, _)) => {
// Получаем id соединения
let id = tcp.next_id.to_string();
// Получаем адрес клиента
let addr = match stream.peer_addr() {
Ok(addr) => addr.to_string(),
Err(e) => e.to_string(),
};
// Обновляем id
tcp.next_id += 1;
let connection = Arc::new(Mutex::new(stream));
// Вставляем соединение
tcp.connections.insert(id.clone(), connection);
// Возвращаем успешный ответ
return json!({
"result": true,
"connection": {
"id": id,
"addr": addr,
}
}).to_string();
}
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
// Если нет соединений, ожидаем немного и пробуем снова
std::thread::sleep(Duration::from_millis(100));
}
Err(e) => {
return create_error(&e.to_string());
}
}
}
create_error("Timeout expired")
} else {
create_error("Listener not initialized")
}
}
/// Получает данные из соединения по указанному УИД
pub fn receive_data(tcp: &mut AddIn, connection_id: String, max_size: usize) -> Box<dyn ValueType> {
let mut stream = match get_stream(tcp, connection_id){
Ok(s) => s,
Err(e) => return Box::new(create_error(&e))
};
let mut buffer = Vec::new();
let mut temp_buffer = vec![0; 1024]; // Буфер для чтения данных
let mut total_read = 0; // Счетчик прочитанных байт
loop {
let remaining = if max_size > 0 {
max_size.saturating_sub(total_read) // Оставшееся место до max_size
} else {
usize::MAX // Без ограничения по размеру
};
if remaining == 0 {
break;
}
let to_read = remaining.min(temp_buffer.len());
match stream.read(&mut temp_buffer[..to_read]) {
Ok(0) => {
break;
}
Ok(size) => {
total_read += size;
buffer.extend_from_slice(&temp_buffer[..size]);
if total_read >= max_size && max_size > 0 {
break;
}
}
Err(e) => {
return Box::new(create_error(&format!("Error reading from stream: {}", e)));
}
}
}
Box::new(buffer)
}
/// Отправляет данные в соединение по указанному УИД
pub fn send_data(tcp: &mut AddIn, connection_id: String, data: Vec<u8>) -> String {
let mut stream = match get_stream(tcp, connection_id){
Ok(s) => s,
Err(e) => return create_error(&e)
};
// Пытаемся отправить данные в поток
match stream.write_all(&data) {
Ok(_) => create_success(),
Err(e) => create_error(&format!("Error writing to stream: {}", e)),
}
}
/// Закрывает соединение по указанному УИД
pub fn close_connection(tcp: &mut AddIn, connection_id: String) -> String {
if tcp.connections.remove(&connection_id).is_some() {
create_success()
} else {
create_error("Connection not found")
}
}
pub fn list_connections(tcp: &AddIn) -> String {
let ids: Vec<String> = tcp.connections.iter().map(|entry| entry.key().clone()).collect();
json!({ "result": true, "connections": ids }).to_string()
}
pub fn remove_inactive_connections(tcp: &mut AddIn) -> String {
let mut inactive_ids = Vec::new();
for element in tcp.connections.iter() {
let id = element.key();
let stream = element.value();
let mut temp_buffer = vec![0; 1];
match stream.lock() {
Ok(locked_stream) => match locked_stream.peek(&mut temp_buffer) {
Err(e) if e.kind() != std::io::ErrorKind::WouldBlock=> inactive_ids.push(id.clone()),
_ => continue
},
Err(e) => return create_error(&format!("Failed to lock stream: {}", e)),
}
}
// Удаляем неактивные соединения
for id in &inactive_ids {
match tcp.connections.remove(id) {
Some(_) => {}, // Успешное удаление
None => return create_error(&format!("Failed to remove connection with id: {}", id)),
}
}
json!({ "result": true, "removed_connections": inactive_ids }).to_string()
}
pub fn stop_server(tcp: &mut AddIn) -> String {
if let Some(listener) = tcp.listener.take() {
drop(listener); // Освобождаем ресурс TcpListener
} else {
return create_error("Listener not initialized");
}
tcp.connections.clear();
create_success()
}
fn get_stream(tcp: &mut AddIn, connection_id: String) -> Result<TcpStream, String> {
// Найти соединение по идентификатору
let stream = tcp.connections.get(&connection_id)
.ok_or_else(|| format!("Connection '{}' not found", connection_id))?;
// Заблокировать доступ к потоку и клонировать его
let stream = stream.lock().map_err(|e| e.to_string())?;
match stream.try_clone().map_err(|e| e.to_string()) {
Ok(stream) => {
stream.set_read_timeout(Some(Duration::from_secs(30))).ok();
stream.set_write_timeout(Some(Duration::from_secs(30))).ok();
Ok(stream)
},
Err(e) => Err(e.to_string())
}
}
fn create_error(message: &str) -> String {
json!({ "result": false, "error": message }).to_string()
}
fn create_success() -> String {
json!({ "result": true }).to_string()
}

View File

@@ -0,0 +1,85 @@
use addin1c::{name, Variant};
pub use crate::commons::{getset, CONNECTIONS};
use crate::commons;
// МЕТОДЫ КОМПОНЕНТЫ -------------------------------------------------------------------------------
// Синонимы
pub const METHODS: &[&[u16]] = &[
name!("Receive"), // 2
name!("Send"), // 3
name!("Close"), // 4
];
// Число параметров функций компоненты
pub fn get_params_amount(num: usize) -> usize {
match num {
0 => 2,
1 => 2,
2 => 1,
_ => 0,
}
}
// Соответствие функций Rust функциям компоненты
// Вызовы должны быть обернуты в Box::new
pub fn cal_func(_obj: &mut AddIn, num: usize, params: &mut [Variant]) -> Box<dyn getset::ValueType> {
match num {
1 => {
let connection_id = params[0].get_string().unwrap_or("".to_string());
let max_size = params[1].get_i32().unwrap_or(0);
commons::receive_data(connection_id, max_size as usize)
},
2 => {
let connection_id = params[0].get_string().unwrap_or("".to_string());
let data = params[1].get_blob().unwrap_or(&[]).to_vec();
Box::new(commons::send_data(connection_id, data))
},
3 => {
let connection_id = params[0].get_string().unwrap_or("".to_string());
Box::new(commons::close_connection(connection_id))
}
_ => Box::new(false), // Неверный номер команды
}
}
// -------------------------------------------------------------------------------------------------
// ПОЛЯ КОМПОНЕНТЫ ---------------------------------------------------------------------------------
// Синонимы
pub const PROPS: &[&[u16]] = &[];
pub struct AddIn {
}
impl AddIn {
/// Создает новый объект
pub fn new() -> Self {
AddIn {}
}
pub fn get_field_ptr(&self, index: usize) -> *const dyn getset::ValueType {
match index {
_ => panic!("Index out of bounds"),
}
}
pub fn get_field_ptr_mut(&mut self, index: usize) -> *mut dyn getset::ValueType { self.get_field_ptr(index) as *mut _ }
}
// -------------------------------------------------------------------------------------------------
// УНИЧТОЖЕНИЕ ОБЪЕКТА -----------------------------------------------------------------------------
// Обработка удаления объекта
impl Drop for AddIn {
fn drop(&mut self) {}
}

View File

@@ -0,0 +1,56 @@
pub mod component;
use addin1c::{name, RawAddin, Variant};
pub use component::AddIn;
use component::METHODS;
use component::PROPS;
use component::get_params_amount;
use component::cal_func;
use crate::commons::getset;
// Определение класса
impl RawAddin for AddIn {
fn register_extension_as(&mut self) -> &'static [u16] {
name!("Handler")
}
fn get_n_props(&mut self) -> usize {
PROPS.len()
}
fn find_prop(&mut self, name: &[u16]) -> Option<usize> {
PROPS.iter().position(|&x| x == name)
}
fn get_prop_name(&mut self, num: usize, _alias: usize) -> Option<&'static [u16]> { PROPS.get(num).copied() }
fn get_prop_val(&mut self, num: usize, val: &mut Variant) -> bool {let field: &dyn getset::ValueType = &self[num]; field.get_value(val) }
fn set_prop_val(&mut self, num: usize, val: &Variant) -> bool {let field: &mut dyn getset::ValueType = &mut self[num]; field.set_value(val); true }
fn is_prop_readable(&mut self, _num: usize) -> bool { true }
fn is_prop_writable(&mut self, _num: usize) -> bool { true }
fn get_n_methods(&mut self) -> usize { METHODS.len() }
fn find_method(&mut self, name: &[u16]) -> Option<usize> { METHODS.iter().position(|&x| x == name) }
fn get_method_name(&mut self, num: usize, _alias: usize) -> Option<&'static [u16]> { METHODS.get(num).copied() }
fn get_n_params(&mut self, num: usize) -> usize { get_params_amount(num) }
fn get_param_def_value(&mut self, _method_num: usize, _param_num: usize, _value: Variant, ) -> bool { true }
fn has_ret_val(&mut self, _num: usize) -> bool { true }
fn call_as_proc(&mut self, _num: usize, _params: &mut [Variant]) -> bool { false }
fn call_as_func(&mut self, num: usize, params: &mut [Variant], ret_value: &mut Variant, ) -> bool { cal_func(self, num, params).get_value(ret_value) }
}
impl std::ops::Index<usize> for AddIn {
type Output = dyn getset::ValueType;
fn index(&self, index: usize) -> &Self::Output {
unsafe { &*self.get_field_ptr(index) }
}
}
impl std::ops::IndexMut<usize> for AddIn {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
unsafe { &mut *self.get_field_ptr_mut(index) }
}
}

View File

@@ -1,5 +1,6 @@
pub mod component;
mod core;
pub mod server;
pub mod handler;
pub mod commons;
use std::{
@@ -7,18 +8,25 @@ use std::{
sync::atomic::{AtomicI32, Ordering},
};
use component::AddIn;
use addin1c::{create_component, destroy_component, name, AttachType};
pub static mut PLATFORM_CAPABILITIES: AtomicI32 = AtomicI32::new(-1);
#[allow(non_snake_case)]
#[no_mangle]
pub unsafe extern "C" fn GetClassObject(_name: *const u16, component: *mut *mut c_void) -> c_long {
let addin = AddIn::new();
create_component(component, addin)
pub unsafe extern "C" fn GetClassObject(name: *const u16, component: *mut *mut c_void) -> c_long {
match *name as u8 {
b'1' => {
let addin = server::AddIn::new();
create_component(component, addin)
}
b'2' => {
let addin = handler::AddIn::new();
create_component(component, addin)
}
_ => 0,
}
}
#[allow(non_snake_case)]
@@ -30,7 +38,7 @@ pub unsafe extern "C" fn DestroyObject(component: *mut *mut c_void) -> c_long {
#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn GetClassNames() -> *const u16 {
name!("Main").as_ptr()
name!("Server|Handler").as_ptr()
}
#[allow(non_snake_case)]

View File

@@ -0,0 +1,118 @@
use std::net::{TcpListener};
use std::time::{Duration, Instant};
use serde_json::{json};
use std::sync::{Arc, Mutex};
use crate::commons::{CONNECTIONS, create_error, create_success};
use crate::server::component::AddIn;
/// Запускает сервер на указанном порту
pub fn start(tcp: &mut AddIn) -> String {
match TcpListener::bind(format!("0.0.0.0:{}", tcp.port)) {
Ok(listener) => {
tcp.listener = Some(listener);
json!({ "result": true }).to_string()
}
Err(e) => create_error(&e.to_string())
}
}
/// Ожидает новое соединение с возможностью указания таймаута (в секундах)
pub fn wait_for_connection(tcp: &mut AddIn, timeout: i32) -> String {
if let Some(listener) = &tcp.listener {
let start_time = Instant::now();
listener.set_nonblocking(true).ok();
while start_time.elapsed() < Duration::from_secs(timeout as u64) || timeout == 0 {
match listener.accept() {
Ok((stream, _)) => {
// Формируем уникальный идентификатор соединения
let id = format!("{}:{}", tcp.port, tcp.next_id);
// Получаем адрес клиента
let addr = match stream.peer_addr() {
Ok(addr) => addr.to_string(),
Err(e) => e.to_string(),
};
// Увеличиваем счётчик ID
tcp.next_id += 1;
let connection = Arc::new(Mutex::new(stream));
// Вставляем соединение в глобальное хранилище
CONNECTIONS.insert(id.clone(), connection);
// Возвращаем успешный ответ
return json!({
"result": true,
"connection": {
"id": id,
"addr": addr,
}
})
.to_string();
}
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
// Если нет соединений, ожидаем немного и пробуем снова
std::thread::sleep(Duration::from_millis(100));
}
Err(e) => {
return create_error(&e.to_string());
}
}
}
create_error("Timeout expired")
} else {
create_error("Listener not initialized")
}
}
pub fn list_connections() -> String {
let ids: Vec<String> = CONNECTIONS.iter().map(|entry| entry.key().clone()).collect();
json!({ "result": true, "connections": ids }).to_string()
}
pub fn remove_inactive_connections() -> String {
let mut inactive_ids = Vec::new();
for element in CONNECTIONS.iter() {
let id = element.key();
let stream = element.value();
let mut temp_buffer = vec![0; 1];
match stream.lock() {
Ok(locked_stream) => match locked_stream.peek(&mut temp_buffer) {
Err(e) if e.kind() != std::io::ErrorKind::WouldBlock => inactive_ids.push(id.clone()),
_ => continue,
},
Err(e) => return create_error(&format!("Failed to lock stream: {}", e)),
}
}
// Удаляем неактивные соединения
for id in &inactive_ids {
match CONNECTIONS.remove(id) {
Some(_) => {} // Успешное удаление
None => return create_error(&format!("Failed to remove connection with id: {}", id)),
}
}
json!({ "result": true, "removed_connections": inactive_ids }).to_string()
}
pub fn stop_server(tcp: &mut AddIn) -> String {
if let Some(listener) = tcp.listener.take() {
drop(listener); // Освобождаем ресурс TcpListener
} else {
return create_error("Listener not initialized");
}
// Удаляем все соединения, связанные с данным сервером
let prefix = format!("{}:", tcp.port);
CONNECTIONS.retain(|key, _| !key.starts_with(&prefix));
create_success()
}

View File

@@ -1,10 +1,10 @@
mod methods;
use addin1c::{name, Variant};
use crate::core::getset;
use std::net::{TcpListener, TcpStream};
use std::sync::{Arc, Mutex};
use dashmap::DashMap;
use std::net::{TcpListener};
pub use crate::commons::{getset, CONNECTIONS};
use crate::commons;
// МЕТОДЫ КОМПОНЕНТЫ -------------------------------------------------------------------------------
@@ -52,23 +52,23 @@ pub fn cal_func(obj: &mut AddIn, num: usize, params: &mut [Variant]) -> Box<dyn
let connection_id = params[0].get_string().unwrap_or("".to_string());
let max_size = params[1].get_i32().unwrap_or(0);
methods::receive_data(obj, connection_id, max_size as usize)
commons::receive_data(connection_id, max_size as usize)
},
3 => {
let connection_id = params[0].get_string().unwrap_or("".to_string());
let data = params[1].get_blob().unwrap_or(&[]).to_vec();
Box::new(methods::send_data(obj, connection_id, data))
Box::new(commons::send_data(connection_id, data))
},
4 => {
let connection_id = params[0].get_string().unwrap_or("".to_string());
Box::new(methods::close_connection(obj, connection_id))
Box::new(commons::close_connection(connection_id))
}
5 => Box::new(methods::list_connections(obj)),
6 => Box::new(methods::remove_inactive_connections(obj)),
5 => Box::new(methods::list_connections()),
6 => Box::new(methods::remove_inactive_connections()),
7 => Box::new(methods::stop_server(obj)),
_ => Box::new(false), // Неверный номер команды
}
@@ -87,7 +87,6 @@ pub const PROPS: &[&[u16]] = &[
pub struct AddIn {
port: i32,
connections: Arc<DashMap<String, Arc<Mutex<TcpStream>>>>,
next_id: i32,
listener: Option<TcpListener>,
}
@@ -97,7 +96,6 @@ impl AddIn {
pub fn new() -> Self {
AddIn {
port: 0,
connections: Arc::new(DashMap::new()),
next_id: 1,
listener: None,
}

View File

@@ -1,19 +1,21 @@
pub mod getset;
pub mod component;
use addin1c::{name, RawAddin, Variant};
use crate::component::METHODS;
use crate::component::PROPS;
use crate::component::get_params_amount;
use crate::component::cal_func;
use crate::component::AddIn;
pub use component::AddIn;
use component::METHODS;
use component::PROPS;
use component::get_params_amount;
use component::cal_func;
use crate::commons::getset;
// Определение класса
impl RawAddin for AddIn {
fn register_extension_as(&mut self) -> &'static [u16] {
name!("Main")
name!("Server")
}
fn get_n_props(&mut self) -> usize {
PROPS.len()

Binary file not shown.

Binary file not shown.

View File

@@ -1,4 +1,4 @@
// OneScript: ./OInt/core/Modules/OPI_TCP.os
// OneScript: ./OInt/core/Modules/OPI_TCP.os
// Lib: TCP
// CLI: tcp
@@ -105,6 +105,10 @@
OPI_ПреобразованиеТипов.ПолучитьЧисло(Таймаут);
OPI_ПреобразованиеТипов.ПолучитьЧисло(МаксимальныйРазмер);
Если ТипЗнч(Соединение) = Тип("Строка") Тогда
Соединение = ОткрытьСоединение(Соединение);
КонецЕсли;
Если ТипЗнч(Маркер) = Тип("Строка") Тогда
Маркер = ПолучитьДвоичныеДанныеИзСтроки(Маркер);
Иначе
@@ -157,6 +161,10 @@
// Булево - Признак успешного выполнения
Функция ОтправитьДвоичныеДанные(Знач Соединение, Знач Данные, Знач Таймаут = 5000) Экспорт
Если ТипЗнч(Соединение) = Тип("Строка") Тогда
Соединение = ОткрытьСоединение(Соединение);
КонецЕсли;
OPI_ПреобразованиеТипов.ПолучитьДвоичныеДанные(Данные);
OPI_ПреобразованиеТипов.ПолучитьЧисло(Таймаут);
@@ -244,7 +252,7 @@
OPI_ПреобразованиеТипов.ПолучитьЧисло(Порт);
OPI_ПреобразованиеТипов.ПолучитьБулево(Запустить);
TCPСервер = ПодключитьКомпонентуНаСервере("OPI_TCPServer");
TCPСервер = ПодключитьКомпонентуНаСервере("OPI_TCPServer", "Server");
TCPСервер.Port = Порт;
Если Запустить Тогда
@@ -310,71 +318,6 @@
КонецФункции
// Получить данные !NOCLI
// Получает данные из потока существующего соединения
//
// Параметры:
// TCPСервер - Произвольный - TCP сервер. См. СоздатьСервер - srv
// IDПодключения - Строка, Число - ID активного подключения. См. ОжидатьПодключение - conn
// МаксимальныйРазмер - Число - Максимальный размер данных. 0 > до конца потока - maxsize
//
// Возвращаемое значение:
// Структура Из КлючИЗначение, ДвоичныеДанные - Двоичные данные при успехе или структура с описанием ошибки
Функция ПолучитьДанные(Знач TCPСервер, Знач IDПодключения, Знач МаксимальныйРазмер = 0) Экспорт
Если Не ЭтоСервер(TCPСервер) Тогда
ВызватьИсключение "Переданное значение не является TCP-сервером!";
КонецЕсли;
OPI_ПреобразованиеТипов.ПолучитьСтроку(IDПодключения);
OPI_ПреобразованиеТипов.ПолучитьЧисло(МаксимальныйРазмер);
Данные = TCPСервер.Receive(IDПодключения, МаксимальныйРазмер);
Если ТипЗнч(Данные) = Тип("Строка") Тогда
Попытка
Результат = OPI_Инструменты.JsonВСтруктуру(Данные, Ложь);
Исключение
Результат = Новый Структура("result,error", Ложь, Данные);
КонецПопытки;
Иначе
Результат = Данные;
КонецЕсли;
//@skip-check constructor-function-return-section
Возврат Результат;
КонецФункции
// Отправить данные !NOCLI
// Отправляет данные клиенту по идентификатору подключения
//
// Параметры:
// TCPСервер - Произвольный - TCP сервер. См. СоздатьСервер - srv
// IDПодключения - Строка, Число - ID активного подключения. См. ОжидатьПодключение - conn
// Данные - ДвоичныеДанные - Данные для отправки - data
//
// Возвращаемое значение:
// Структура Из КлючИЗначение - Информация о выполнении
Функция ОтправитьДанные(Знач TCPСервер, Знач IDПодключения, Знач Данные) Экспорт
Если Не ЭтоСервер(TCPСервер) Тогда
ВызватьИсключение "Переданное значение не является TCP-сервером!";
КонецЕсли;
OPI_ПреобразованиеТипов.ПолучитьСтроку(IDПодключения);
OPI_ПреобразованиеТипов.ПолучитьДвоичныеДанные(Данные);
Результат = TCPСервер.Send(IDПодключения, Данные);
ОбработатьРезультат(Результат);
//@skip-check constructor-function-return-section
Возврат Результат;
КонецФункции
// Отключить сервер !NOCLI
// Останавливает запущенный сервер
//
@@ -398,32 +341,6 @@
КонецФункции
// Закрыть входящее соединение !NOCLI
// Закрывает существующее соединение по идентификатору
//
// Параметры:
// TCPСервер - Произвольный - TCP сервер. См. СоздатьСервер - srv
// IDПодключения - Строка, Число - ID активного подключения. См. ОжидатьПодключение - conn
//
// Возвращаемое значение:
// Структура Из КлючИЗначение - Информация о выполнении
Функция ЗакрытьВходящееСоединение(Знач TCPСервер, Знач IDПодключения) Экспорт
Если Не ЭтоСервер(TCPСервер) Тогда
ВызватьИсключение "Переданное значение не является TCP-сервером!";
КонецЕсли;
OPI_ПреобразованиеТипов.ПолучитьСтроку(IDПодключения);
Результат = TCPСервер.Close(IDПодключения);
ОбработатьРезультат(Результат);
//@skip-check constructor-function-return-section
Возврат Результат;
КонецФункции
// Получить входящие соединения !NOCLI
// Получает список соединений в пуле
//
@@ -474,6 +391,107 @@
КонецФункции
// Получить данные !NOCLI
// Получает данные из потока существующего соединения
//
// Параметры:
// TCPОбработчик - Произвольный - TCP сервер или идентификатор подключения - hnd
// IDПодключения - Строка, Число - ID активного подключения. См. ОжидатьПодключение - conn
// МаксимальныйРазмер - Число - Максимальный размер данных. 0 > до конца потока - maxsize
//
// Возвращаемое значение:
// Структура Из КлючИЗначение, ДвоичныеДанные - Двоичные данные при успехе или структура с описанием ошибки
Функция ПолучитьДанные(Знач TCPОбработчик, Знач IDПодключения, Знач МаксимальныйРазмер = 0) Экспорт
Если Не ЭтоСервер(TCPОбработчик) Тогда
OPI_ПреобразованиеТипов.ПолучитьСтроку(TCPОбработчик);
TCPОбработчик = ПодключитьКомпонентуНаСервере("OPI_TCPServer", "Handler");
КонецЕсли;
OPI_ПреобразованиеТипов.ПолучитьСтроку(IDПодключения);
OPI_ПреобразованиеТипов.ПолучитьЧисло(МаксимальныйРазмер);
Данные = TCPОбработчик.Receive(IDПодключения, МаксимальныйРазмер);
Если ТипЗнч(Данные) = Тип("Строка") Тогда
Попытка
Результат = OPI_Инструменты.JsonВСтруктуру(Данные, Ложь);
Исключение
Результат = Новый Структура("result,error", Ложь, Данные);
КонецПопытки;
Иначе
Результат = Данные;
КонецЕсли;
//@skip-check constructor-function-return-section
Возврат Результат;
КонецФункции
// Отправить данные !NOCLI
// Отправляет данные клиенту по идентификатору подключения
//
// Параметры:
// TCPОбработчик - Произвольный - TCP сервер или идентификатор подключения - hnd
// IDПодключения - Строка, Число - ID активного подключения. См. ОжидатьПодключение - conn
// Данные - ДвоичныеДанные - Данные для отправки - data
//
// Возвращаемое значение:
// Структура Из КлючИЗначение - Информация о выполнении
Функция ОтправитьДанные(Знач TCPОбработчик, Знач IDПодключения, Знач Данные) Экспорт
Если Не ЭтоСервер(TCPОбработчик) Тогда
OPI_ПреобразованиеТипов.ПолучитьСтроку(TCPОбработчик);
TCPОбработчик = ПодключитьКомпонентуНаСервере("OPI_TCPServer", "Handler");
КонецЕсли;
OPI_ПреобразованиеТипов.ПолучитьСтроку(IDПодключения);
OPI_ПреобразованиеТипов.ПолучитьДвоичныеДанные(Данные);
Результат = TCPОбработчик.Send(IDПодключения, Данные);
ОбработатьРезультат(Результат);
//@skip-check constructor-function-return-section
Возврат Результат;
КонецФункции
// Закрыть входящее соединение !NOCLI
// Закрывает существующее соединение по идентификатору
//
// Параметры:
// TCPОбработчик - Произвольный - TCP сервер или идентификатор подключения - hnd
// IDПодключения - Строка, Число - ID активного подключения. См. ОжидатьПодключение - conn
//
// Возвращаемое значение:
// Структура Из КлючИЗначение - Информация о выполнении
Функция ЗакрытьВходящееСоединение(Знач TCPОбработчик, Знач IDПодключения) Экспорт
Если Не ЭтоСервер(TCPОбработчик) Тогда
OPI_ПреобразованиеТипов.ПолучитьСтроку(TCPОбработчик);
TCPОбработчик = ПодключитьКомпонентуНаСервере("OPI_TCPServer", "Handler");
КонецЕсли;
OPI_ПреобразованиеТипов.ПолучитьСтроку(IDПодключения);
Результат = TCPОбработчик.Close(IDПодключения);
ОбработатьРезультат(Результат);
//@skip-check constructor-function-return-section
Возврат Результат;
КонецФункции
// Это сервер !NOCLI
// Определяет, является ли переданное значение объектом TCP сервера
//

View File

@@ -1,4 +1,4 @@
// OneScript: ./OInt/core/Modules/OPI_Telegram.os
// OneScript: ./OInt/core/Modules/OPI_Telegram.os
// Lib: Telegram
// CLI: telegram
@@ -213,6 +213,8 @@
// Соответствие из Строка - Соответствие данных с результатом проверки в поле passed
Функция ОбработатьДанныеTMA(Знач СтрокаДанных, Знач Токен) Экспорт
#Если Сервер Тогда
OPI_ПреобразованиеТипов.ПолучитьСтроку(Токен);
OPI_ПреобразованиеТипов.ПолучитьСтроку(СтрокаДанных);
@@ -280,6 +282,10 @@
СоответствиеВозврата.Вставить("passed", Ответ);
Возврат СоответствиеВозврата;
#Иначе
Возврат Неопределено;
#КонецЕсли
КонецФункции

View File

@@ -1,4 +1,4 @@
// OneScript: ./OInt/tools/Modules/OPI_ПолучениеДанныхТестов.os
// OneScript: ./OInt/tools/Modules/OPI_ПолучениеДанныхТестов.os
// MIT License
@@ -2097,6 +2097,12 @@
ОжидаетЧто(Результат["data"].Количество()).Равно(0);
КонецПроцедуры
Процедура Проверка_РезультатИстина(Знач Результат) Экспорт
ОжидаетЧто(Результат["result"]).Равно(Истина);
КонецПроцедуры
#КонецОбласти
#КонецОбласти

View File

@@ -1,4 +1,4 @@
// OneScript: ./OInt/tests/Modules/internal/OPI_Тесты.os
// OneScript: ./OInt/tests/Modules/internal/OPI_Тесты.os
// MIT License
@@ -16455,24 +16455,72 @@
КонецПроцедуры
Процедура TCP_ОжидатьПодключение(ПараметрыФункции) Экспорт
// -- Тестовый запрос для сервера
КлючКлиента = Новый УникальныйИдентификатор;
Сообщение = "Тестовая отправка данных на сервер";
ПараметрыКлиента = Новый Массив;
ПараметрыКлиента.Добавить("127.0.0.1:7788"); // Наш сервер
ПараметрыКлиента.Добавить(Сообщение); // Тестовая строка
ПараметрыКлиента.Добавить("UTF-8"); // Кодировка
ПараметрыКлиента.Добавить("20000"); // Таймаут, чтобы успеть запустить сервер
// Отправка тестового запроса через клиентские методы TCP
ФоновоеКлиента = ФоновыеЗадания.Выполнить("OPI_TCP.ОтправитьСтроку", ПараметрыКлиента, КлючКлиента);
TCPСервер = OPI_TCP.СоздатьСервер(7788, Истина);
// --
TCPСервер = OPI_TCP.СоздатьСервер(7788, Истина);
НовоеПодключение = OPI_TCP.ОжидатьПодключение(TCPСервер, 20);
Если НовоеПодключение["result"] Тогда
Подключение = НовоеПодключение["connection"]["id"];
Адрес = НовоеПодключение.connection.addr;
ЗапросДвочиные = OPI_TCP.ПолучитьДанные(TCPСервер, Подключение);
ЗапросТекст = ПолучитьСтрокуИзДвоичныхДанных(ЗапросДвочиные);
ОтправкаОтвета = OPI_TCP.ОтправитьДанные(TCPСервер, Подключение, ПолучитьДвоичныеДанныеИзСтроки("Yo"));
ЗакрытиеПодключения = OPI_TCP.ЗакрытьВходящееСоединение(TCPСервер, Подключение);
Для Н = 1 По 5 Цикл
КонецЕсли;
Отключение = OPI_TCP.ОтключитьСервер(TCPСервер);
ЗаданиеКлиента = ФоновоеКлиента.ОжидатьЗавершенияВыполнения();
// END
OPI_ПолучениеДанныхТестов.ЗаписатьЛог(TCPСервер, "ОжидатьПодключение (сервер)", "TCP");
OPI_ПолучениеДанныхТестов.Проверка_Компонента(TCPСервер, "AddIn.OPI_TCPServer.Main");
НовоеПодключение = OPI_TCP.ОжидатьПодключение(TCPСервер, 20);
OPI_ПолучениеДанныхТестов.ЗаписатьЛог(TCPСервер, "ОжидатьПодключение", "TCP");
OPI_ПолучениеДанныхТестов.Проверка_РезультатИстина(НовоеПодключение);
Если НовоеПодключение["result"] Тогда
Подключение = НовоеПодключение["connection"];
Иначе
Продолжить;
КонецЕсли;
OPI_ПолучениеДанныхТестов.ЗаписатьЛог(TCPСервер, "ОжидатьПодключение (подключение)", "TCP");
OPI_ПолучениеДанныхТестов.Проверка_Строка(Подключение, "1");
OPI_ПолучениеДанныхТестов.ЗаписатьЛог(TCPСервер, "ОжидатьПодключение (сообщение)", "TCP");
OPI_ПолучениеДанныхТестов.Проверка_Строка(ЗапросТекст, Сообщение);
Ответ = OPI_TCP.ОтправитьДанные(TCPСервер, Подключение, ПолучитьДвоичныеДанныеИзСтроки("Yo"));
Закрытие = OPI_TCP.ЗакрытьВходящееСоединение(TCPСервер, Подключение);
OPI_ПолучениеДанныхТестов.ЗаписатьЛог(TCPСервер, "ОжидатьПодключение (ответ)", "TCP");
OPI_ПолучениеДанныхТестов.Проверка_РезультатИстина(ОтправкаОтвета);
КонецЦикла;
OPI_ПолучениеДанныхТестов.ЗаписатьЛог(TCPСервер, "ОжидатьПодключение (закрытие)", "TCP");
OPI_ПолучениеДанныхТестов.Проверка_РезультатИстина(ЗакрытиеПодключения);
OPI_ПолучениеДанныхТестов.ЗаписатьЛог(TCPСервер, "ОжидатьПодключение (отвключение)", "TCP");
OPI_ПолучениеДанныхТестов.Проверка_РезультатИстина(Отключение);
Состояние = Строка(ЗаданиеКлиента.Состояние);
OPI_ПолучениеДанныхТестов.ЗаписатьЛог(TCPСервер, "ОжидатьПодключение (фоновое)", "TCP");
OPI_ПолучениеДанныхТестов.Проверка_Строка(Состояние, "Задание выполнено");
КонецПроцедуры
#КонецОбласти