Я работаю над приложением C++ Builder, используя библиотеку Indy. Есть две основные цели:
После реализации решения, описанного в:
Почему мне нужно отправить сообщение дважды, чтобы вызвать событие Indy OnExecute?
И доработав решение под свои нужды, приложение начало зависать. Судя по всему, приложение работает хорошо, пока я не захочу остановить компонент TIdMappedPortTCP или закрыть приложение.
Поскольку приложение пока небольшое, я создал новый проект и начал переносить части кода, чтобы попытаться определить причину, по которой это происходит.
Я смог воспроизвести нежелательное поведение, просто «распечатав» сообщения во время событий Инди.
Событие OnExecute еще не реализовано (в новом проекте), что означает, что данные TCP передаются в обе стороны.
Это моя реализация класса синхронизации:
// TTextToDisplay.cpp
TTextToDisplay::TTextToDisplay() {
lineToAdd = NULL;
stringsToAdd = NULL;
}
TTextToDisplay::TTextToDisplay(String str) {
lineToAdd = str;
stringsToAdd = NULL;
}
void __fastcall TTextToDisplay::AddSingleLine(String str)
{
lineToAdd = str;
}
void __fastcall TTextToDisplay::AddStringList(TStringList* strings)
{
stringsToAdd = strings;
}
void __fastcall TTextToDisplay::DoSynchronize(){
// Use the input parameters here...
if (stringsToAdd)
Form1->Display->Lines->AddStrings(stringsToAdd);
else if (lineToAdd != NULL) {
Form1->Display->Lines->Add(lineToAdd);
}
}
Где Display — это элемент управления TRichEdit, lineToAdd — это объект String, а stringsToAdd — это TStringList.
Indy управляет несколькими событиями, и я хотел бы добавить текст на каждое из них (если это не запрещено дизайном или каким-либо другим ограничением).
Я добавил это событие:
void __fastcall TForm1::MITMProxyBeforeConnect(TIdContext *AContext)
{
String tempStr;
// displaying remote address.
tempStr = "Received connection from " +
AContext->Connection->Socket->Binding->PeerIP;
TTextToDisplay *TextToDisplay = new TTextToDisplay(tempStr);
TextToDisplay->Synchronize();
delete TextToDisplay;
}
Приложение работало нормально. Затем я добавил это:
void __fastcall TForm1::MITMProxyDisconnect(TIdContext *AContext)
{
String tempStr;
// displaying remote address.
tempStr = "Client disconnected"; TTextToDisplay *TextToDisplay =
new TTextToDisplay(tempStr); TextToDisplay->Synchronize();
delete TextToDisplay;
}
Приложение по-прежнему работает нормально. Поэтому я добавил еще один
void __fastcall TForm1::MITMProxyConnect(TIdContext *AContext)
{
String tempStr;
// displaying remote address.
tempStr = "Attempting to connect to the remote server " +
MITMProxy->MappedHost + ":" + MITMProxy->MappedPort;
TTextToDisplay *TextToDisplay = new TTextToDisplay(tempStr);
TextToDisplay->Synchronize();
delete TextToDisplay;
}
И теперь приложение начинает зависать.
Работает нормально означает, что я могу закрыть TIdMappedPortTCP
MITMProxy->Active = False;
и повторно активировать его
MITMProxy->Active = True;
несколько раз, получите несколько сообщений, а затем закройте приложение, чтобы оно не переставало отвечать.
Я хотел бы, чтобы приложение было как можно более подробным, так есть ли лучший способ регистрировать каждое событие Indy?
Приложение, над которым я изначально работал, работало отлично, пока я не добавил настраиваемое отображение данных. Потом появилась проблема. Я не знаю, связана ли причина, но поведение такое же.
Итак, мое приложение вместо отображения этой строки:
008460000000190210703800000EC00000164593560001791662000000000000080000000002104302040235313531353135313531353153414C4535313030313233343536373831323334353637383930313233
Теперь показывает:
0000: 60 00 00 00 19 02 10 70 38 00 00 0E C0 00 00 16
0010: 45 93 56 00 01 79 16 62 00 00 00 00 00 00 08 00
0020: 00 00 00 02 10 43 02 04 02 35 31 35 31 35 31 35
0030: 31 35 31 35 31 53 41 4C 45 35 31 30 30 31 32 33
0040: 34 35 36 37 38 31 32 33 34 35 36 37 38 39 30 31
0050: 32 33
Это TStringList, потому что мне проще сразу добавить все эти строки в TRichEdit. Я хочу, чтобы второй TRichEdit показывал декодированное сообщение.
Чтобы дать вам представление, это будет что-то вроде этого:
000 MsgType : "0200"
001 BitMap : "70 24 06 80 20 C0 06 10"
002 PAN : "4593560001791662"
003 ProcessingCode : "000000"
004 TxnAmount : "000000080000"
011 SystemTraceNo : "000001"
014 ExpirationDate : "2411"
022 POSEntryMode : "520"
023 CardSequenceNo : "000"
025 POSConditionCode : "00"
035 Track2 : "4593560001791662=24111190000063900000"
041 TerminalID : "00064600"
042 AcquirerID : "000010585800001"
054 AddAmounts : "0"
055 Field55 : "9F 26 08 35 C2 C4 DF B5 FC 7B 0E 9F 27 01 80 9F 10 07 06 01 0A 03 A0 B8 03 9F 37 04 C1 5C 4B 3B 9F 36 02 01 3A 95 05 00 80 00 80 00 9A 03 22 04 02 9C 01 00 9F 02"
060 Field60 : "00 00 08"
--------------------------------------------------------------
Field 55 by Tag:
9F26 AppCryptogram : "35 C2 C4 DF B5 FC 7B 0E"
9F27 CryptogramInfoData : "80"
9F10 IssuerAppData : "06 01 0A 03 A0 B8 03"
9F37 UnpredictableNo : "C1 5C 4B 3B"
9F36 AppTxnCounter : "01 3A"
95 TermVerifResults : "00 80 00 80 00"
9A TxnDate : "220402"
9C TxnType : "00"
9F02 AmountAuthNum : "20"
И последнее соображение: я знаю приложения, которые выполняют пересылку, и я знаю приложения, которые декодируют, но я не видел приложений, которые делают и то, и другое, и поэтому я хочу создать его. Но до сих пор самой сложной частью была визуализация. Поэтому я не знаю, использую ли я правильные инструменты или правильный подход для создания того, что хочу. Любые советы будут высоко оценены.
Серверы Indy являются многопоточными, а отключение сервера является синхронным — он ожидает полного завершения потоков сервера, прежде чем вернуться к вызывающей стороне.
TIdSync
(и TThread::Synchronize()
и эквивалентные) делегируют основному потоку пользовательского интерфейса, блокируя вызывающий поток до тех пор, пока основной поток не завершит обработку запроса на синхронизацию.
Таким образом, вы можете легко попасть в тупиковую ситуацию, если вы Synchronize
с основным потоком, в то время как основной поток выключает сервер. Основной поток будет ожидать в потоках сервера, а поток синхронизации будет ожидать в основном потоке.
Итак, ваши варианты: либо:
Просто не Synchronize
во время отключения сервера. Установите флаг где-нибудь перед началом выключения, а затем посмотрите на этот флаг перед выполнением синхронизации. Хотя это не будет охватывать сценарий, когда отключение начинается после того, как синхронизация уже запущена.
Не выполняйте синхронную синхронизацию, вместо этого выполняйте асинхронную синхронизацию (TIdNotify
, TThread::Queue()
или эквивалент). Нет веской причины блокировать потоки вашего сервера, ожидающие, пока основной поток выполнит обновления состояния пользовательского интерфейса.
Завершите работу сервера в отдельном потоке, оставив основной поток свободным для обработки запросов синхронизации.
Большое спасибо @Remy После комментирования кода в моем событии OnDisconnect отзывчивость вернулась. Я понимаю, что сейчас происходило. Выключение сервера вызывает событие OnDisconnect, которое пытается выполнить синхронизацию, создавая ситуацию взаимоблокировки. Я скучаю по тексту «клиент отключен», но это можно решить позже. Мне нужно изучить два других решения. Извините за длинный вопрос.