使用简单的 Indy 服务器代码进行访问冲突

Access Violation using simple Indy server code

本文关键字:代码 访问冲突 服务器 Indy 简单      更新时间:2023-10-16

在下面的代码中,我有一个简单的服务器,它每秒向所有客户端发送一条消息 2 次,另一条消息每分钟 8-10 次。

问题是我在运行时收到错误:

读取地址FFFFFFD0 00479740访问冲突

但仅在少数系统中,每天只有 1 或 2 次。该软件每天工作约10小时。

我尝试在 ICS 库中使用类似的代码,并且似乎运行良好。

这段代码出了什么问题? 有没有更好的编码方法?

void __fastcall TDataNet::DataModuleCreate(TObject *Sender)
{
listaClient= new TThreadList();
psTx= new TStringList();
psRx= new TStringList();
}
void __fastcall TDataNet::DataModuleDestroy(TObject *Sender)
{
IdTCPServer1->Active= false;
listaClient->Free();
delete psTx;
delete psRx;
}
void __fastcall TDataNet::Send( TStrings *ps, TIdContext *AContext)
{
TList *lista;
static int cntSend= 0;
try
{
lista= listaClient->LockList();
if( AContext != NULL )
{
AContext->Connection->IOHandler->Write( ps, true, TIdTextEncoding_UTF8);
}
else
{
for( int i=0; i < lista->Count; i++ )
((TDatiClient*)lista->Items[i])->pThread->Connection->IOHandler->Write( ps, true, TIdTextEncoding_UTF8);
}
}
__finally
{
listaClient->UnlockList();
}
}
void __fastcall TDataNet::SetCambioPilota( void)
{
unsigned short hh, mm, ss, ms, hh1, mm1, ss1, ms1;
unsigned short hh2, mm2, ss2, ms2, hh3, mm3, ss3, ms3;
unsigned short hh4, mm4, ss4, ms4, dd4;
unsigned short hh5, mm5, ss5, ms5, dd5;
TStrings *ps;
UnicodeString s;
try
{
ps= psTx;
ps->Clear();
s= "<CAMBIO_PILOTA>";
ps->Add( s);
for( int i=0; i < MAX_PILOTI; i++ )
{
s.sprintf( L"<Pilota%02x= I%x,"A%s","C%s","F%s",f%x>",
i+1, gara.pilota[i].idnome,
gara.pilota[i].nome.c_str(), gara.pilota[i].nick.c_str(),
gara.pilota[i].nomeTeam.c_str(), gara.pilota[i].idPilotaT );
ps->Add( s);
}
s= "<END_CAMBIO_PILOTA>";
ps->Add( s);
Send( ps );
}
catch(...){}
}
void __fastcall TDataNet::SetDatiGara( void)
{
TStrings *ps;
UnicodeString s;
try
{
ps= psTx;
ps->Clear();
s= "<DATI_GARA>";
ps->Add( s);
s.sprintf( L"<eve=%d,A%x,B%x,C%x,D%x,E%x,F%x,G%x,H%x,I%x,J%x,K%x>", DataB->GetEventoInCorso().idEvento,
DataB->GetEventoInCorso().numEvento, DataB->GetEventoInCorso().subEvento,
DataB->GetNextEvento().idEvento, DataB->GetNextEvento().numEvento, DataB->GetNextEvento().subEvento,
gara.tkTempo, gara.tkDurata - gara.tkTempo,
gara.laps, gara.gDurata > 0 ? (gara.gDurata - gara.laps):0, gara.flInCorso ? (gara.gDurata > 0 ? 2:1):0,
gara.flFineGara );
ps->Add( s);
s= "<END_DATI_GARA>";
ps->Add( s);
Send( ps );
}
catch(...){}
}
void __fastcall TDataNet::Timer1Timer(TObject *Sender)
{
Timer1->Enabled= false;
SetDatiGara();
Timer1->Enabled= true;
}
void __fastcall TDataNet::IdTCPServer1Connect(TIdContext *AContext)
{
TDatiClient* dati;
dati= new TDatiClient;
dati->pThread= AContext;
AContext->Connection->IOHandler->ReadTimeout= 200;
AContext->Data= (TObject*)dati;
try
{
TList* lista;
lista= listaClient->LockList();
lista->Add( dati);
connessioni= lista->Count;
if( FmainWnd )
PostMessage( FmainWnd, WM_EVENTO_TCP, ID_CONNESSO, lista->Count);
int idEvento= DataB->GetEventoInCorso().idEvento;
if( idEvento )
SetCambioStato( idEvento, STATO_EVENTO_START, AContext);
}
__finally
{
listaClient->UnlockList();
}
}
void __fastcall TDataNet::IdTCPServer1Disconnect(TIdContext *AContext)
{
TDatiClient* dati;
dati= (TDatiClient*)AContext->Data;
AContext->Data= NULL;
try
{
listaClient->Remove( dati);
TList* lista;
lista= listaClient->LockList();
connessioni= lista->Count;
if( FmainWnd )
PostMessage( FmainWnd, WM_EVENTO_TCP, ID_DISCONNESSO, lista->Count);
}
__finally
{
listaClient->UnlockList();
}
delete dati;
}
void __fastcall TDataNet::IdTCPServer1Execute(TIdContext *AContext)
{
Sleep( 100);
try
{
AContext->Connection->IOHandler->ReadStrings( psRx, -1);
if( psRx->Count >= 2 && psRx->Strings[0] == "<LAST_MINUTE>" && psRx->Strings[psRx->Count-1] == "<END_LAST_MINUTE>" )
{
psRx->Delete(0);
psRx->Delete(psRx->Count-1);
if( FmainWnd )
SendMessage( FmainWnd, WM_EVENTO_TCP, ID_LAST_MINUTE, (unsigned int)psRx);
}
psRx->Clear();
}
catch( ...) {}
AContext->Connection->CheckForGracefulDisconnect();
}

错误消息表示您正在访问与 NULL 指针偏移量为 -48 字节的内容。 我看到这段代码存在各种问题,其中至少是您以线程不安全的方式访问内容,因此您存在可能损坏内存的竞争条件以及其他可能的问题。 例如,OnExecute事件处理程序不保护psRx对象免受并发访问,因此多个客户端可能会同时使用数据填充它,从而损坏其内容。

TIdTCPServer是一个多线程组件,其事件是在工作线程的上下文中触发的,而不是在主 UI 线程的上下文中触发的,因此事件处理程序必须使用线程安全编码。

此外,您正在做的事情并不是处理与TIdTCPServer异步通信的最安全方法。我会建议更像以下内容:

class TDatiClient : public TIdServerContext
{
public:
TIdThreadSafeObjectList *Queue;
bool QueueHasObjects;
__fastcall TDatiClient(TIdTCPConnection *AConnection, TIdYarn *AYarn, TThreadList* AList = NULL)
: TIdServerContext(AConnection, AYarn, AList)
{
Queue = new TIdThreadSafeObjectList;
}
__fastcall ~TDatiClient()
{
delete Queue;
}
void __fastcall Send(TStrings *ps)
{
TStringList *toSend = new TStringList;
try
{
toSend->Assign(ps);
TList *lista = Queue->LockList();
try
{
lista->Add(toSend);
QueueHasObjects = true;
}
__finally
{
Queue->UnlockList();
}
}
catch (const Exception &)
{
delete toSend;
}
}
};
void __fastcall TDataNet::TDataNet(TComponent *Owner)
: TDataModule(Owner)
{
// this must be set before you activate the server...
IdTCPServer1->ContextClass = __classid(TDatiClient);
// do this at runtime instead of design-time so
// ContextClass can be set first...
IdTCPServer1->Active = true;
}
void __fastcall TDataNet::~TDataNet()
{
IdTCPServer1->Active = false;
}
void __fastcall TDataNet::Send(TStrings *ps, TIdContext *AContext)
{
static int cntSend = 0;
TList *lista = IdTCPServer1->Contexts->LockList();
try
{
if (AContext)
{
// make sure the client is still in the list...
if (lista->IndexOf(AContext) != -1)
static_cast<TDatiClient*>(AContext)->Send(ps);
}
else
{
for (int i = 0; i < lista->Count; ++i)
static_cast<TDatiClient*>(static_cast<TIdContext*>(lista->Items[i]))->Send(ps);
}
}
__finally
{
IdTCPServer1->Contexts->UnlockList();
}
}
void __fastcall TDataNet::SetCambioPilota()
{
UnicodeString s;
try
{
TStringList *ps = new TStringList;
try
{
s = _D("<CAMBIO_PILOTA>");
ps->Add(s);
for (int i = 0; i < MAX_PILOTI; ++i)
{
s.sprintf( _D("<Pilota%02x= I%x,"A%s","C%s","F%s",f%x>),
// TODO: if SetCambioPilota() is ever called in a worker thread,
// make sure these values are accessed in a thread-safe manner!
i+1, gara.pilota[i].idnome,
gara.pilota[i].nome.c_str(), gara.pilota[i].nick.c_str(),
gara.pilota[i].nomeTeam.c_str(), gara.pilota[i].idPilotaT);
ps->Add(s);
}
s = _D("<END_CAMBIO_PILOTA>");
ps->Add(s);
Send(ps);
}
__finally
{
delete ps;
}
}
catch (const Exception &)
{
}
}
void __fastcall TDataNet::SetDatiGara()
{
UnicodeString s;
try
{
TStringList *ps = new TStringList;
try
{
s = _D("<DATI_GARA>");
ps->Add(s);
s.sprintf( _D("<eve=%d,A%x,B%x,C%x,D%x,E%x,F%x,G%x,H%x,I%x,J%x,K%x>"),
// TODO: if SetDatiGara() is ever called in a worker thread,
// make sure these values are accessed in a thread-safe manner!
DataB->GetEventoInCorso().idEvento,
DataB->GetEventoInCorso().numEvento, DataB->GetEventoInCorso().subEvento,
DataB->GetNextEvento().idEvento, DataB->GetNextEvento().numEvento, DataB->GetNextEvento().subEvento,
gara.tkTempo, gara.tkDurata - gara.tkTempo, gara.laps,
(gara.gDurata > 0) ? (gara.gDurata - gara.laps) : 0,
gara.flInCorso ? ((gara.gDurata > 0) ? 2 : 1) : 0,
gara.flFineGara);
ps->Add(s);
s = _D("<END_DATI_GARA>");
ps->Add(s);
Send(ps);
}
__finally
{
delete ps;
}
}
catch (const Exception &)
{
}
}
void __fastcall TDataNet::Timer1Timer(TObject *Sender)
{
Timer1->Enabled = false;
SetDatiGara();
Timer1->Enabled = true;
}
void __fastcall TDataNet::IdTCPServer1Connect(TIdContext *AContext)
{
TDatiClient* dati = static_cast<TDatiClient*>(AContext);
AContext->Connection->IOHandler->DefStringEncoding = TIdTextEncoding_UTF8;
TList* lista = IdTCPServer1->Contexts->LockList();
try
{
// TODO: this event is fired in a worker thread, so make sure
// that connessioni, DataB, and SetCambioStato() are all being
// accessed in a thread-safe manner!
int connessioni = lista->Count;
if (FmainWnd)
PostMessage(FmainWnd, WM_EVENTO_TCP, ID_CONNESSO, connessioni);
int idEvento = DataB->GetEventoInCorso().idEvento;
if (idEvento)
SetCambioStato(idEvento, STATO_EVENTO_START, AContext);
}
__finally
{
IdTCPServer1->Contexts->UnlockList();
}
}
void __fastcall TDataNet::IdTCPServer1Disconnect(TIdContext *AContext)
{
TDatiClient* dati = static_cast<TDatiClient*>(AContext);
TList* lista = IdTCPServer1->Contexts->LockList();
try
{
int connessioni = lista->Count - 1;
if (FmainWnd)
PostMessage(FmainWnd, WM_EVENTO_TCP, ID_DISCONNESSO, connessioni);
}
__finally
{
IdTCPServer1->Contexts->UnlockList();
}
}
void __fastcall TDataNet::IdTCPServer1Execute(TIdContext *AContext)
{
TDatiClient* dati = static_cast<TDatiClient*>(AContext);
TStringList *ps;
if (dati->QueueHasObjects)
{
TObjectList *objs = new TObjectList(false);
try
{
TList *lista = dati->Queue->LockList();
try
{
objs->Assign(lista);
lista->Clear();
objs->OwnsObjects = true;
}
__finally
{
dati->QueueHasObjects = (lista->Count > 0);
dati->Queue->UnlockList();
}
for (int i = 0; i < objs->Count; ++i)
{
ps = static_cast<TStringList*>(objs->Items[i]);
AContext->Connection->IOHandler->Write(ps, true);
}
}
__finally
{
delete objs;
}
}
if (AContext->Connection->IOHandler->InputBufferIsEmpty())
{
AContext->Connection->IOHandler->CheckForDataOnSource(200);
if (AContext->Connection->IOHandler->InputBufferIsEmpty())
{
AContext->Connection->IOHandler->CheckForDisconnect();
return;
}
}
ps = new TStringList;
try
{
AContext->Connection->IOHandler->ReadStrings(ps, -1);
if ((ps->Count >= 2) && (ps->Strings[0] == _D("<LAST_MINUTE>")) && (ps->Strings[ps->Count-1] == _D("<END_LAST_MINUTE>")))
{
ps->Delete(0);
ps->Delete(ps->Count-1);
if (FmainWnd)
SendMessage(FmainWnd, WM_EVENTO_TCP, ID_LAST_MINUTE, reinterpret_cast<LPARAM>(ps));
}
}
__finally
{
delete ps;
}
}