TIdTCPServer 串口共享

TIdTCPServer sharing of serial port

最近我更改了一个程序,它充当 TCP 服务器以帮助共享连接到设备的串行端口上的流量。多个客户端连接并且应该可以访问串行端口并同时操作。

应用程序是使用 C++Builder 构建的,在服务器中使用 TIdTCPServer,在客户端应用程序中使用 TIdTCPClient

多个客户端需要连接并向串口发送命令。串行端口将在向它发送命令后立即响应,根据它所连接的设备的协议。

还有一个后台线程偶尔访问串行端口并更新服务器内存中保存的数据的内存缓存。从串行端口发送和接收的命令在它们上面有一个互斥锁,因此它们可以从 TIdTcpServerOnExecute 事件和后台线程访问。

我很难让 TIdTCPServerOnExecute 事件在不重叠的情况下工作。

如果 OnExecute 事件在没有来自另一个客户端的另一个请求导致重叠的情况下完全执行,那就太好了。

这是 TIdTCPServerOnExecute 事件处理程序:

void __fastcall TfrmMain::IServerExecute(TIdContext *AContext)
{
    int  i;
    int Len;
    TIdBytes TRB, TSB;
    unsigned char ARB[BUFFERLENGTH];
    int NumbSent, NumbReceived;

    // Read the command from the client.  Send the length first then the actual data.
    Len = AContext->Connection->Socket->ReadLongInt();
    AContext->Connection->Socket->ReadBytes(TRB, Len, false);

    memset(ARB,0,BUFFERLENGTH);
    for(i=0;i<Len;i++) AOB[i]=TRB[i];

    NumbSent=Len;


    // Now send it out to the Serial port
    ProcessSerialMessage(AOB, Len, ARB, &NumbReceived, false);

    sending=false;

    TSB.Length=NumbReceived;
    for(i=0;i<TSB.Length;i++) TSB[i]=ARB[i];
    AContext->Connection->Socket->Write(TSB.Length);
    AContext->Connection->Socket->Write(TSB);

    return;
}

下面是通过串口发送数据的例程:

int ProcessSerialMessage(unsigned char *SendBuf, int NumbSBytes, unsigned char *ReceiveBuf, int *NumbRBytes, bool CalledFromThread)
{

    // MMUtex is a global TMutex Object
    // Mutex required to help with the background thread trying to update memory cache.
    MMutex->Acquire();


    // Ok now send the data out over the serial port and receive it.
    // These routines are standard serial port I/O routines and aren't explained here.
    rawsend(SendBuf, NumbSBytes);
    rawreceive(ReceiveBuf, NumbRBytes);

    RetValue=*NumbRBytes;
    MMutex->Release();

    return(RetValue);
}

TIdTCPServer 是一个多线程组件。每个连接的客户端都在自己独立的线程中运行。 OnExecute 事件在这些线程中运行。因此,您有责任通过序列化对任何共享资源的访问来确保您的 OnExecute 代码是线程安全的。

您正在 ProcessSerialMessage() 内部使用互斥锁,因此您正在序列化对串行端口的访问(假设您的其他后台线程也进入同一个互斥锁)。所以这应该没问题(虽然,我建议使用 try..__finally 块或本地 RAII 样式 class 来保护互斥量 locking/unlocking,以确保互斥量被正确解锁,即使抛出异常)。

但是,我发现此代码的一个主要问题是您的 AOB(和 sending)变量未声明为 IServerExecute() 的局部变量,这意味着它必须是跨线程访问的共享变量(更新:您已在注释中确认:“[AOB] 已全局声明。”)。但是,它不受多线程并发访问的保护,这意味着多个客户端在将数据发送到串行端口时可以自由覆盖彼此的入站数据。

您正在将串行端口的响应读入局部变量,然后使用它们发送回请求客户端。所以该代码中没有并发问题。

我建议将您的 TRBTSB 数组直接传递给 ProcessSerialMessage()。您必须将字节从一个数组复制到另一个数组的 2 个循环并不是真正必要的,因此您可以从此代码中完全消除 AOBARB 变量。这可能足以解决您的问题。

试试这个:

void __fastcall TfrmMain::IServerExecute(TIdContext *AContext)
{
    TIdBytes TRB, TSB;
    int NumbSent, NumbReceived;

    // Read the command from the client.  Send the length first then the actual data.
    NumbSent = AContext->Connection->Socket->ReadLongInt();
    AContext->Connection->Socket->ReadBytes(TRB, NumbSent, false);

    TSB.Length = BUFFERLENGTH;

    // Now send it out to the Serial port
    ProcessSerialMessage(&TRB[0], NumbSent, &TSB[0], &NumbReceived, false);

    AContext->Connection->Socket->Write(NumbReceived);
    AContext->Connection->Socket->Write(TSB, NumbReceived);
}