Начну с «Вести с полей». Вышло обновление Updates in .NET Core 1.0.1. Главное из этого для меня было Access violation on Windows – coreclr 6460:
Из-за этой ошибки вылетало исключение при вызове статического .Net-метода в 64 разрядном клиенте 1С.
Сейчас починили и код прекрасно выполняется под 64 разрядным клиентом на 8.3.9. В примерах заменил библиотеки .NET Core на 1.0.1. Хотел написать про SignalR, но пока можно написать только сервер на .Net Core — ASP.NET Core SignalR for Windows 10 UWP App
aspnet/SignalR-Server
Клиента пока нет. В WCF пока только клиент под Web сервис. ServiceHost нет. Есть стороннее решение .NET core cross platform remote service invocation
Но решил написать решение из своего опыта 8 летней давности для обмена данными по TCP/IP между ТСД на Win CE и 1С еще 7-ки. Конечно с 1С можно обмениваться через Web-сервисы, но есть задачи, где нужно взаимодействие с оператором для выбора данных, брать данные подготовленные на клиенте, печать на мобильный принтер.
Основные проблемы связаны с сетями с плохим соединением на складах. Поэтому, нужно было уменьшить трафик за счет сжатия данных. Так при работе в терминальных сессиях были проблемы с проброской портов в медленных сетях — Тормозит печать чека на фискальный регистратор через RDP.
Также были проблемы при считывании двумерного штрих кода. Медленная печать с терминального сервера. Для решения этих проблем на машине клиента устанавливалась локальная 1С которая работала как клиент и сервер. Данные со сканеров отправлялись на терминальный сервер и там обрабатывались. Для печати на фискальный регистратор отправлялись данные с сервера по TCP/IP и с локальной 1С печатался чек. При печати этикеток с сервера оправлялись данные, на основании которых на локальной 1С формировался документ и отправлялся на печать.
Кроме того под многое оборудование для Linux нет драйверов. Можно используя виртуализацию держать Linux и Windows на одной машине, на Windows считывать данные и обмениваться с Linux по TCP/IP.
Сейчас много у кого есть ТСД под WinCe, WinMo (недавно предлагали работу по настройке обмена на них). Кроме того можно использовать ТСД на других осях используя UWP и Xamarin.
Кроме того можно обмениваться сообщениями между клиентами 1С, наподобие чата.
В большом .Net я часто использую обмен по TCp/IP
Использование сборок .NET в 1С 7.x b 8.x. Создание внешних Компонент.
Использование ТСД на WM 6 как беспроводной сканер с получением данных из 1С
Поэтому я решил написать этот же обмен, но на .Net Core и добавить новый подход.
Чистые 1С ники могут пропустить вражеский код и перейти к родному в конце статьи, как использовать данную компоненту.
Нужно было создать класс для обмена сообщениями с сжатыми данными. Для отправки данных использовался метод:
На стороне 1С принимается такой класс
Модуль для формирования сообщений который был написан 8 лет назад с небольшими изменениями.
Уже тогда я вовсю использовал Руслиш.
На сервере создается класс для прослушивания:
Все достаточно просто. При соединении считываем данные, создаем объект для отправки в 1С. Запускаем нового слушателя.
Отправка сделана на голых сокетах можно посмотреть в исходниках.
Упрощенно это выглядит так:
Вот как это обрабатывается в 1С:
Ответ передаем через полученный объект:
По умолчанию очередь событий в 1С равен 1. Поэтому 1 задача может выполняться, а еще одна дожидаться в очереди.
Так как можно работать с несколькими устройствами то нужно установить нужный размер очереди через:
Который возвращает текущий размер очереди.
Конечно, можно запустить несколько приложений 1С и запустить TCP/IP сервер под разными портами. но по практике операторы путаются. Чем проще для них, тем лучше.
Для установки нужных делегатов используются методы
В зависимости от типа делегата устанавливается нужный делегат:
Конечно, можно использовать события и динамическую компиляцию, см публикацию «Разработка → 1С,.Net Core. Динамическая компиляция класса обертки для получения событий .Net объекта в 1С».
Но раз пишем под 1С, то проще объявить делегаты нужного типа, и ��становить из 1С.
Для теста нужно использовать 3 клиентов 1С и вызвать ТестОбменПоTCPIP.epf для проверки очереди событий в 1С.
Исходники можно скачать здесь.
In Jitstartup, JIT creates a file descriptor for stdout and unconditionally passes it to setmode, without checking for failures. This happens at. Failure to check for invalid descriptors can result in setmode triggering failfast.
Из-за этой ошибки вылетало исключение при вызове статического .Net-метода в 64 разрядном клиенте 1С.
Необработанное исключение по адресу 0x00007FFD76FB8528 (ucrtbase.dll) в 1cv8.exe: Недопустимый параметр был передан функции, для которой недопустимые параметры вызывают неустранимую ошибку.
Сейчас починили и код прекрасно выполняется под 64 разрядным клиентом на 8.3.9. В примерах заменил библиотеки .NET Core на 1.0.1. Хотел написать про SignalR, но пока можно написать только сервер на .Net Core — ASP.NET Core SignalR for Windows 10 UWP App
aspnet/SignalR-Server
Клиента пока нет. В WCF пока только клиент под Web сервис. ServiceHost нет. Есть стороннее решение .NET core cross platform remote service invocation
Но решил написать решение из своего опыта 8 летней давности для обмена данными по TCP/IP между ТСД на Win CE и 1С еще 7-ки. Конечно с 1С можно обмениваться через Web-сервисы, но есть задачи, где нужно взаимодействие с оператором для выбора данных, брать данные подготовленные на клиенте, печать на мобильный принтер.
Основные проблемы связаны с сетями с плохим соединением на складах. Поэтому, нужно было уменьшить трафик за счет сжатия данных. Так при работе в терминальных сессиях были проблемы с проброской портов в медленных сетях — Тормозит печать чека на фискальный регистратор через RDP.
Также были проблемы при считывании двумерного штрих кода. Медленная печать с терминального сервера. Для решения этих проблем на машине клиента устанавливалась локальная 1С которая работала как клиент и сервер. Данные со сканеров отправлялись на терминальный сервер и там обрабатывались. Для печати на фискальный регистратор отправлялись данные с сервера по TCP/IP и с локальной 1С печатался чек. При печати этикеток с сервера оправлялись данные, на основании которых на локальной 1С формировался документ и отправлялся на печать.
Кроме того под многое оборудование для Linux нет драйверов. Можно используя виртуализацию держать Linux и Windows на одной машине, на Windows считывать данные и обмениваться с Linux по TCP/IP.
Сейчас много у кого есть ТСД под WinCe, WinMo (недавно предлагали работу по настройке обмена на них). Кроме того можно использовать ТСД на других осях используя UWP и Xamarin.
Кроме того можно обмениваться сообщениями между клиентами 1С, наподобие чата.
В большом .Net я часто использую обмен по TCp/IP
Использование сборок .NET в 1С 7.x b 8.x. Создание внешних Компонент.
Использование ТСД на WM 6 как беспроводной сканер с получением данных из 1С
Поэтому я решил написать этот же обмен, но на .Net Core и добавить новый подход.
Чистые 1С ники могут пропустить вражеский код и перейти к родному в конце статьи, как использовать данную компоненту.
Нужно было создать класс для обмена сообщениями с сжатыми данными. Для отправки данных использовался метод:
// Отправляем команду на сервер // Отправляем данные на сервер // string Команда имя метода который будет обрабатывать данные // string ДанныеДляКоманды это сериализованные данные в виде строки // bool ЕстьОтвет признак функции или процедуры метода обрабатывающего данные public ДанныеОтветаПоTCP ОтправитьКоманду(string АдресСервера, int порт, string Команда, string ДанныеДляКоманды, bool ЕстьОтвет)
На стороне 1С принимается такой класс
// Данные отправляемые в 1С для обработки запроса public class ДанныеДляКлиета1С { public bool ЕстьОтвет; public string Команда; public string Данные; TcpClient Клиент; public ДанныеДляКлиета1С(СтруктураСообщения Даннные, TcpClient Клиент) { this.ЕстьОтвет = Даннные.ЕстьОтвет; this.Команда = Даннные.Команда; this.Данные = Даннные.Данные; if (ЕстьОтвет) this.Клиент = Клиент; else // Если нет ответа то закрываем соединение { Клиент.Dispose(); this.Клиент = null; } } // Отсылаем данные клиенту //Создадим новую задачу, что бы основной поток 1С не ждал отпраки //Ответ пытаемся сжать public void Ответить(string Ответ) { Task.Run(() => { var strim = Клиент.GetStream(); ДляОбменаПоТСП.WriteCompressedString(strim, Ответ); // Закроем соединение strim.Dispose(); Клиент.Dispose(); }); } public override string ToString() { return $"ЕстьОтвет={ЕстьОтвет}, Команда={Команда}, Данные={Данные}"; } }
Модуль для формирования сообщений который был написан 8 лет назад с небольшими изменениями.
Уже тогда я вовсю использовал Руслиш.
Много кода
public class ДляОбменаПоТСП
{
public static readonly Encoding CurrentEncoder;//=Encoding.GetEncoding(1251);
static ДляОбменаПоТСП()
{
//Вот здесо особенность .Net Core
// Нужно зарегистрировать провайдера
// и прописать в зависимости «System.Text.Encoding.CodePages»
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
// CurrentEncoder = Encoding.GetEncoding(«windows-1251»);
// Так как мы используем Руслиш то используем кодировку 1251
CurrentEncoder = Encoding.GetEncoding(1251);
}
public static byte[] РасжатьДанные(byte[] массивДанныхДляКоманды)
{
var memStream = new MemoryStream(массивДанныхДляКоманды);
var DecompressStream = new MemoryStream();
using (GZipStream gzipStream = new GZipStream(memStream, CompressionMode.Decompress, false))
{
Byte[] buffer = new Byte[1 << 16];
int h;
while ((h = gzipStream.Read(buffer, 0, buffer.Length)) > 0)
{
DecompressStream.Write(buffer, 0, h);
}
}
return DecompressStream.ToArray();
}
//
public static byte[] СжатьДанные(byte[] Value)
{
var memStream = new MemoryStream();
memStream.Position = 0;
using (GZipStream gzipStream = new GZipStream(memStream, CompressionMode.Compress))
{
gzipStream.Write(Value, 0, Value.Length);
gzipStream.Flush();
}
return memStream.ToArray();
}
// Классичекое чтение из NetworkStream зная размер получаемых данных
private static byte[] МассивБайтовИзСтрима(NetworkStream стрим, int размерМассива)
{
byte[] result = new byte[размерМассива];
int количествоСчитанныхСимволов = 0;
while (размерМассива > количествоСчитанныхСимволов)
{
количествоСчитанныхСимволов += стрим.Read(result, количествоСчитанныхСимволов, размерМассива — количествоСчитанныхСимволов);
}
return result;
}
public static void ЗаписатьМассивБайтовВСтрим(NetworkStream стрим, byte[] Массив)
{
стрим.Write(Массив, 0, Массив.Length);
}
// Считываем из потока 1 байт и конвертируем в bool
public static bool ReadBool(NetworkStream стрим)
{
return BitConverter.ToBoolean(МассивБайтовИзСтрима(стрим,1), 0);
}
// Конвертирум bool в 1 байт и записываем в поток
public static void Write(NetworkStream стрим, bool Value)
{
ЗаписатьМассивБайтовВСтрим(стрим, BitConverter.GetBytes(Value));
}
// Считываем из потока 4 байта и конвертируем в int
public static Int32 ReadInt32(NetworkStream стрим)
{
return BitConverter.ToInt32(МассивБайтовИзСтрима(стрим,4), 0);
}
// Конвертирум int в 4 ��айта и записываем в поток
public static void Write(NetworkStream стрим, Int32 Value)
{
ЗаписатьМассивБайтовВСтрим(стрим, BitConverter.GetBytes(Value));
}
// Считываем строку. Сначала идет размер данных int
//затем считываем данные и получаем строку используя кодировку 1251
public static string ReadString(NetworkStream стрим)
{
int РазмерДанных=ReadInt32(стрим);
if (РазмерДанных == 0) return "";
return CurrentEncoder.GetString(МассивБайтовИзСтрима(стрим, РазмерДанных));
}
// Записываем строку. Сначала записываем размер строки, затем конвертируем в byte[] используя кодировку 1251
public static void Write(NetworkStream стрим, string Value)
{
if (Value.Length == 0)
{
Write(стрим, 0);
return;
}
byte[] result = CurrentEncoder.GetBytes(Value);
Write(стрим, result.Length);
ЗаписатьМассивБайтовВСтрим(стрим,result);
}
// Смотри WriteCompressedString это обратная операция
public static string ReadCompressedString(NetworkStream стрим)
{
// int РазмерДанных = ReadInt32(стрим);
// return CurrentEncoder.GetString(МассивБайтовИзСтрима(стрим, РазмерДанных));
bool ЭтоСжатаяСтрока = ReadBool(стрим);
if (! ЭтоСжатаяСтрока) return ReadString(стрим);
int РазмерДанныхДляКоманды = BitConverter.ToInt32(МассивБайтовИзСтрима(стрим, 4), 0);
byte[] массивДанныхДляКоманды = МассивБайтовИзСтрима(стрим, РазмерДанныхДляКоманды);
массивДанныхДляКоманды = РасжатьДанные(массивДанныхДляКоманды);
return CurrentEncoder.GetString(массивДанныхДляКоманды);
}
// Пытаемся сжать строку GZIP. Если размер сжатых данных меньше оригинала то записываем сжатые танные
//иначе оригинал
// Записываем данные в следующей последовательности
//bool флаг сжатия данных
//int размер данных
//byte[] данные
public static void WriteCompressedString(NetworkStream стрим, string Value)
{
if (Value.Length == 0)
{
Write(стрим, false);
Write(стрим, 0);
return;
}
byte[] result = CurrentEncoder.GetBytes(Value);
var СжатыеДанные=СжатьДанные(result);
if (result.Length>СжатыеДанные.Length)
{
Write(стрим, true);
Write(стрим, СжатыеДанные.Length);
ЗаписатьМассивБайтовВСтрим(стрим, СжатыеДанные);
}
else
{
Write(стрим, false);
Write(стрим, result.Length);
ЗаписатьМассивБайтовВСтрим(стрим,result);
}
}
// Отправляем данные на сервер
// string Команда имя метода который будет обрабатывать данные
// string ДанныеДляКоманды это сериализованные данные ввиде строки
// bool ЕстьОтвет признак функции или процедуры метода обрабатывающего данные
public static void ОтправитьКоманду(NetworkStream strim,string Команда, string ДанныеДляКоманды, bool ЕстьОтвет)
{
Write(strim, ЕстьОтвет);
Write(strim, Команда);
WriteCompressedString(strim, ДанныеДляКоманды);
}
// Прочитать данные с клиента
public static СтруктураСообщения ПринятьКоманду(NetworkStream strim)
{
bool ЕстьОтвет=ReadBool(strim);
string Команда=ReadString(strim);
string ДанныеДляКоманды=ReadCompressedString(strim);
return new СтруктураСообщения(Команда, ДанныеДляКоманды, ЕстьОтвет);
}
}
{
public static readonly Encoding CurrentEncoder;//=Encoding.GetEncoding(1251);
static ДляОбменаПоТСП()
{
//Вот здесо особенность .Net Core
// Нужно зарегистрировать провайдера
// и прописать в зависимости «System.Text.Encoding.CodePages»
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
// CurrentEncoder = Encoding.GetEncoding(«windows-1251»);
// Так как мы используем Руслиш то используем кодировку 1251
CurrentEncoder = Encoding.GetEncoding(1251);
}
public static byte[] РасжатьДанные(byte[] массивДанныхДляКоманды)
{
var memStream = new MemoryStream(массивДанныхДляКоманды);
var DecompressStream = new MemoryStream();
using (GZipStream gzipStream = new GZipStream(memStream, CompressionMode.Decompress, false))
{
Byte[] buffer = new Byte[1 << 16];
int h;
while ((h = gzipStream.Read(buffer, 0, buffer.Length)) > 0)
{
DecompressStream.Write(buffer, 0, h);
}
}
return DecompressStream.ToArray();
}
//
public static byte[] СжатьДанные(byte[] Value)
{
var memStream = new MemoryStream();
memStream.Position = 0;
using (GZipStream gzipStream = new GZipStream(memStream, CompressionMode.Compress))
{
gzipStream.Write(Value, 0, Value.Length);
gzipStream.Flush();
}
return memStream.ToArray();
}
// Классичекое чтение из NetworkStream зная размер получаемых данных
private static byte[] МассивБайтовИзСтрима(NetworkStream стрим, int размерМассива)
{
byte[] result = new byte[размерМассива];
int количествоСчитанныхСимволов = 0;
while (размерМассива > количествоСчитанныхСимволов)
{
количествоСчитанныхСимволов += стрим.Read(result, количествоСчитанныхСимволов, размерМассива — количествоСчитанныхСимволов);
}
return result;
}
public static void ЗаписатьМассивБайтовВСтрим(NetworkStream стрим, byte[] Массив)
{
стрим.Write(Массив, 0, Массив.Length);
}
// Считываем из потока 1 байт и конвертируем в bool
public static bool ReadBool(NetworkStream стрим)
{
return BitConverter.ToBoolean(МассивБайтовИзСтрима(стрим,1), 0);
}
// Конвертирум bool в 1 байт и записываем в поток
public static void Write(NetworkStream стрим, bool Value)
{
ЗаписатьМассивБайтовВСтрим(стрим, BitConverter.GetBytes(Value));
}
// Считываем из потока 4 байта и конвертируем в int
public static Int32 ReadInt32(NetworkStream стрим)
{
return BitConverter.ToInt32(МассивБайтовИзСтрима(стрим,4), 0);
}
// Конвертирум int в 4 ��айта и записываем в поток
public static void Write(NetworkStream стрим, Int32 Value)
{
ЗаписатьМассивБайтовВСтрим(стрим, BitConverter.GetBytes(Value));
}
// Считываем строку. Сначала идет размер данных int
//затем считываем данные и получаем строку используя кодировку 1251
public static string ReadString(NetworkStream стрим)
{
int РазмерДанных=ReadInt32(стрим);
if (РазмерДанных == 0) return "";
return CurrentEncoder.GetString(МассивБайтовИзСтрима(стрим, РазмерДанных));
}
// Записываем строку. Сначала записываем размер строки, затем конвертируем в byte[] используя кодировку 1251
public static void Write(NetworkStream стрим, string Value)
{
if (Value.Length == 0)
{
Write(стрим, 0);
return;
}
byte[] result = CurrentEncoder.GetBytes(Value);
Write(стрим, result.Length);
ЗаписатьМассивБайтовВСтрим(стрим,result);
}
// Смотри WriteCompressedString это обратная операция
public static string ReadCompressedString(NetworkStream стрим)
{
// int РазмерДанных = ReadInt32(стрим);
// return CurrentEncoder.GetString(МассивБайтовИзСтрима(стрим, РазмерДанных));
bool ЭтоСжатаяСтрока = ReadBool(стрим);
if (! ЭтоСжатаяСтрока) return ReadString(стрим);
int РазмерДанныхДляКоманды = BitConverter.ToInt32(МассивБайтовИзСтрима(стрим, 4), 0);
byte[] массивДанныхДляКоманды = МассивБайтовИзСтрима(стрим, РазмерДанныхДляКоманды);
массивДанныхДляКоманды = РасжатьДанные(массивДанныхДляКоманды);
return CurrentEncoder.GetString(массивДанныхДляКоманды);
}
// Пытаемся сжать строку GZIP. Если размер сжатых данных меньше оригинала то записываем сжатые танные
//иначе оригинал
// Записываем данные в следующей последовательности
//bool флаг сжатия данных
//int размер данных
//byte[] данные
public static void WriteCompressedString(NetworkStream стрим, string Value)
{
if (Value.Length == 0)
{
Write(стрим, false);
Write(стрим, 0);
return;
}
byte[] result = CurrentEncoder.GetBytes(Value);
var СжатыеДанные=СжатьДанные(result);
if (result.Length>СжатыеДанные.Length)
{
Write(стрим, true);
Write(стрим, СжатыеДанные.Length);
ЗаписатьМассивБайтовВСтрим(стрим, СжатыеДанные);
}
else
{
Write(стрим, false);
Write(стрим, result.Length);
ЗаписатьМассивБайтовВСтрим(стрим,result);
}
}
// Отправляем данные на сервер
// string Команда имя метода который будет обрабатывать данные
// string ДанныеДляКоманды это сериализованные данные ввиде строки
// bool ЕстьОтвет признак функции или процедуры метода обрабатывающего данные
public static void ОтправитьКоманду(NetworkStream strim,string Команда, string ДанныеДляКоманды, bool ЕстьОтвет)
{
Write(strim, ЕстьОтвет);
Write(strim, Команда);
WriteCompressedString(strim, ДанныеДляКоманды);
}
// Прочитать данные с клиента
public static СтруктураСообщения ПринятьКоманду(NetworkStream strim)
{
bool ЕстьОтвет=ReadBool(strim);
string Команда=ReadString(strim);
string ДанныеДляКоманды=ReadCompressedString(strim);
return new СтруктураСообщения(Команда, ДанныеДляКоманды, ЕстьОтвет);
}
}
На сервере создается класс для прослушивания:
// Класс для получения и отправки сообщений public class TCPConnector { TcpListener Server; // Будем записывать ошибки в файл // Нужно прописать в зависимости "System.Diagnostics.TextWriterTraceListener" // Файл будет рядом с этой DLL TextWriterTraceListener myTextListener; // Устанавливаем флаг при закрытии bool ЭтоЗакрытие = false; // Клиент для отпраки сообщений на сервер Socket клиент; // делегат для вызова внешнего события в 1С // Который ставит сообщение в очередь событий в 1С public Action<string, string, object> ВнешнееСобытие1С; //Делегат для вывода ошибки в окне сообщений public Action<string> СообщитьОбОшибкев1С; // Получаем директорию сборки содержащий данный класс string AssemblyDirectory { get { string codeBase = typeof(TCPConnector).GetTypeInfo().Assembly.Location; UriBuilder uri = new UriBuilder(codeBase); string path = Uri.UnescapeDataString(uri.Path); return Path.GetDirectoryName(path) + @"\"; } } public TCPConnector() { myTextListener = null; } // Записываем ошибку a файл и сообщаем об ошибке в 1С void ЗаписатьОшибку(string Ошибка) { if (myTextListener == null) { try { FileStream fs = new FileStream(AssemblyDirectory + @"ТрассировкаОтладки", FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite); StreamWriter myOutputWriter = new StreamWriter(fs, Encoding.GetEncoding(1251)); myTextListener = new TextWriterTraceListener(myOutputWriter); Trace.Listeners.Add(myTextListener); } catch (Exception) { // проглотим ошибку что бы приложение закрылось } } Trace.WriteLine(Ошибка); Trace.Flush(); СообщитьОбОшибкев1С?.DynamicInvoke(Ошибка); } // Откроем порт и количество слушющих задач которое обычно равно подсоединенным устройствам // Нужно учитывть, что 1С обрабатывает все события последовательно ставя события в очередь public void Открыть(int НомерПорта = 6891, int КоличествоСлушателей = 1) { ЭтоЗакрытие = false; IPEndPoint ipEndpoint = new IPEndPoint(IPAddress.Any, НомерПорта); Server = new TcpListener(ipEndpoint); Server.Start(); // Создадим задачи для прослушивания порта //При подключении клиента запустим метод ОбработкаСоединения // Подсмотрено здесь https://github.com/imatitya/netcorersi/blob/master/src/NETCoreRemoveServices.Core/Hosting/TcpServerListener.cs for (int i = 0; i < КоличествоСлушателей; i++) Server.AcceptTcpClientAsync().ContinueWith(ОбработкаСоединения); } // Метод для обработки сообщения от клиента private void ОбработкаСоединения(Task<TcpClient> task) { if (task.IsFaulted || task.IsCanceled) { // Скорее всего вызвано Server.Stop(); return; } // Получим клиента TcpClient client = task.Result; // И вызовем метод для обработки данных // ВыполнитьКоманду(client); // Если Server не закрыт то запускаем нового слушателя if (!ЭтоЗакрытие) Server.AcceptTcpClientAsync().ContinueWith(ОбработкаСоединения); } private void ВыполнитьКоманду(TcpClient client) { NetworkStream стрим = client.GetStream(); try { // Получим данные с клиента и на основании этих данных //Создадим ДанныеДляКлиета1С котрый кроме данных содержит //TcpClient для отправки ответа var Данные = new ДанныеДляКлиета1С(ДляОбменаПоТСП.ПринятьКоманду(стрим), client); // Вызвается метод 1С для постановки сообщения в очередь // Которое будет обработано через ВнешнееСобытие ВнешнееСобытие1С?.DynamicInvoke("TCPConnector", Данные.Команда, Данные); } catch (Exception e) { ЗаписатьОшибку(DateTime.Now.ToString() + e.ToString()); } } // Закроем ресурсы public void Закрыть() { if (Server != null) { ЭтоЗакрытие = true; Server.Stop(); Server = null; } if (myTextListener != null) { Trace.Listeners.Remove(myTextListener); myTextListener.Dispose(); } }
Все достаточно просто. При соединении считываем данные, создаем объект для отправки в 1С. Запускаем нового слушателя.
Отправка сделана на голых сокетах можно посмотреть в исходниках.
Упрощенно это выглядит так:
IPEndPoint ipEndpoint = new IPEndPoint(IPAddress.Parse(АдресСервера), порт); //6891 по умолчанию клиент = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); клиент.Connect(ipEndpoint); var поток= new NetworkStream(клиент); ДляОбменаПоТСП.ОтправитьКоманду(поток, Команда, ДанныеДляКоманды, ЕстьОтвет); // считываем сжатые данные в строку if (ЕстьОтвет) result = ДляОбменаПоТСП.ReadCompressedString(strim); поток.Dispose(); клиент.Dispose();
Вот как это обрабатывается в 1С:
// В Net core для NetStandard System.Threading.Tasks не существует Task=ъТип("System.Threading.Tasks.Task","System.Threading.Tasks"); Процедура СоздатьСерверTCP() Если СерверTCP<>Неопределено Тогда возврат КонецЕсли; TCPConnector=ъТип("TCPConnectTo1C.TCPConnector","ОбменПоTCPIPCore.dll"); СерверTCP=ъНовый(TCPConnector.ПолучитьСсылку()); Ссылка=СерверTCP.ПолучитьСсылку(); Врап.УстановитьДелегатДляВызоваВнешнегоСобытия(Ссылка,"ВнешнееСобытие1С"); Врап.УстановитьДелегатДляСообщенииОбОшибке(Ссылка,"СообщитьОбОшибкев1С"); КонецПроцедуры// СоздатьTCP() Процедура ТестTCPConnectНажатие(Элемент) // Установим размер очереди событий равный удвоенному количеству //обслуживаемых устройств // Но нужно учесть, что запросы без ответа ставятся в очередь 1С // и сразу закрывается соединение // Клиент не ждет // Если будут проблемы нужно посылать запрос с ответом Сообщить(Врап.УстановитьРазмерОчередиСобытий(3*2)); Сообщить(Врап.УстановитьРазмерОчередиСобытий(3*2)); СоздатьСерверTCP(); СерверTCP.Открыть(6891,3); ЭлементыФормы.ДанныеДляОтправки.Видимость=ложь; ЭлементыФормы.ОтправитьКоманды.Видимость=ложь; ЭлементыФормы.НадписьДанныеДляОтправки.Видимость=ложь; КонецПроцедуры Процедура СканированШК(знач Данные) // Съэмулируем долгую обработку для проверки очереди событий ъ(Task.Delay(1000)).Wait(); Ответ="Ответ на команду "+Данные.Команда+" |Данные "+Данные.Данные+" |ВремяНаСервере="+XmlСтрока(ТекущаяДата()); Данные.Ответить(Ответ); КонецПроцедуры Процедура ВыполнитьБезОтвета(знач Данные) // Съэмулируем долгую обработку для проверки очереди событий ъ(Task.Delay(1000)).Wait(); КонецПроцедуры // Для теста из других компонент Процедура ПолучениеДанныхПоTCP(знач Данные) Сообщить("Команда="+Данные.Команда); Сообщить("Данные="+Данные.Данные); Сообщить("ЕстьОтвет="+Данные.ЕстьОтвет); ъ(Task.Delay(1000)).Wait(); Если Данные.ЕстьОтвет Тогда Ответ="Ответ на команду "+Данные.Команда+" |Данные "+Данные.Данные+" |ВремяНаСервере="+XmlСтрока(ТекущаяДата()); Данные.Ответить(Ответ); КонецЕсли; КонецПроцедуры Процедура ВнешнееСобытие(Источник, Событие, Данные) Если Источник="TCPConnector" Тогда //Получим объект по переданной ссылке Данные=ъ(Данные); Сообщить("Данные="+Врап.ВСтроку(Данные.ПолучитьСсылку())); // Тест из отчета ТестNetObjectToIDispatch Если Событие="Тест Отправки Сообщения" Тогда ПолучениеДанныхПоTCP(Данные) иначе // Запускаем метод переданный в коанде Выполнить(Событие+"(Данные)"); КонецЕсли; КонецЕсли; КонецПроцедуры Процедура ОтправитьКоманду(знач КлиентTCP,ServerAdress,порт,Команда,ДанныеДляКоманды,ЕстьОтвет) резулт=ъ(КлиентTCP.ОтправитьКоманду(ServerAdress,порт,Команда,ДанныеДляКоманды,ЕстьОтвет)); Сообщить(Врап.ВСтроку(резулт.ПолучитьСсылку())); Если резулт.ОшибкаСоединения Тогда СтрОшибки="ОшибкаСоединения |"+резулт.Данные; Предупреждение(СтрОшибки); КонецЕсли; КонецПроцедуры Процедура ОтправитьКомандыНажатие(Элемент) СоздатьСерверTCP(); КлиентTCP=СерверTCP; ServerAdress="127.0.0.1"; порт=6891; Команда="Тест Отправки Сообщения"; ДанныеДляКоманды=XmlСтрока(ТекущаяДата()); ЕстьОтвет=истина; ЗакрытьСоединение=истина; ОшибкаСоединения=false; Для сч=1 По 3 Цикл ОтправитьКоманду(КлиентTCP,ServerAdress,порт,Команда,ДанныеДляКоманды,истина); ОтправитьКоманду(КлиентTCP,ServerAdress,порт,"ВыполнитьБезОтвета",ДанныеДляОтправки,ложь); ОтправитьКоманду(КлиентTCP,ServerAdress,порт,"СканированШК","12345678901",истина); КонецЦикла; КонецПроцедуры Процедура ПриЗакрытии() // Вставить содержимое обработчика. Если СерверTCP<> неопределено Тогда СерверTCP.Закрыть(); СерверTCP=Неопределено; КонецЕсли; GC=ъТип("System.GC"); GC.Collect(); GC.WaitForPendingFinalizers(); Врап=Неопределено; КонецПроцедуры
Ответ передаем через полученный объект:
Данные.Ответить(Ответ);
По умолчанию очередь событий в 1С равен 1. Поэтому 1 задача может выполняться, а еще одна дожидаться в очереди.
Так как можно работать с несколькими устройствами то нужно установить нужный размер очереди через:
Врап.УстановитьРазмерОчередиСобытий(размер очереди));
Который возвращает текущий размер очереди.
Конечно, можно запустить несколько приложений 1С и запустить TCP/IP сервер под разными портами. но по практике операторы путаются. Чем проще для них, тем лучше.
Для установки нужных делегатов используются методы
Врап.УстановитьДелегатДляВызоваВнешнегоСобытия(Ссылка,"ВнешнееСобытие1С"); Врап.УстановитьДелегатДляСообщенииОбОшибке(Ссылка,"СообщитьОбОшибкев1С");
В зависимости от типа делегата устанавливается нужный делегат:
if (ReturnType == typeof(Action<string, string, object>)) return new Action<string, string, object>(ВызватьВнешнееСобытиеСОбъектом); if (ReturnType == typeof(Action<string, string, string>)) return new Action<string, string, string>(AutoWrap.ВызватьВнешнееСобытие1С);
Конечно, можно использовать события и динамическую компиляцию, см публикацию «Разработка → 1С,.Net Core. Динамическая компиляция класса обертки для получения событий .Net объекта в 1С».
Но раз пишем под 1С, то проще объявить делегаты нужного типа, и ��становить из 1С.
Для теста нужно использовать 3 клиентов 1С и вызвать ТестОбменПоTCPIP.epf для проверки очереди событий в 1С.
Исходники можно скачать здесь.
