TCP/IP 服务器使用 IOCP。接收缓冲区中的偶尔数据损坏

TCP/IP server using IOCP. Occasional data corruption in receive buffers

我一直在开发 TCP/IP IOCP 服务器应用程序。

我一直在测试性能(这似乎与 TCP 吞吐量测试实用程序一致),现在一直在测试数据完整性——这是我感到有些“奇怪”的地方。

作为初始测试,我决定让测试客户端一遍又一遍地发送一个 1MB 的数据块,其中该块只是一个接一个递增的整数序列。这个想法是我可以验证每个接收到的数据缓冲区是否与该缓冲区中没有丢失的数据一致,独立于接收到的任何其他缓冲区,这意味着我不需要担心线程处理完成接收的顺序。 (为了验证,我提取缓冲区中的第一个整数并向前扫描,如果遇到客户端发送的最大值,则将预期值重置为 0。我还检查以确保每个接收到的数据都是 4 的倍数(因为它们是 4 字节整数))。

我似乎偶尔会从缓冲区中丢失随机数据块,值会增加 1 增加 1,然后会跳过一堆。代码虽然看起来很简单,但并不多。我最初在 Delphi 中编写了测试,但在遇到这些问题后,我在 Visual Studio 2010 C++ 中重写了一个版本,并且似乎遇到了相同的问题(或者至少非常相似)。

在实际系统中显然有更多的代码,但我可以在工作线程中将其归结为几乎这个,它只处理完成的接收,验证缓冲区中的数据然后 post又是他们。在我最初接受连接后,我创建了两个重叠结构并为每个结构分配 1MB 缓冲区,然后为每个结构调用 WSARecv。我已经仔细检查过我没有不小心在两者之间共享相同的缓冲区。然后,以下几乎就是重用这些运行的内容:

DWORD numberOfBytesTransferred = 0;
ULONG_PTR completionKey = NULL;
PMyOverlapped overlapped = nullptr;

while (true)
{
    auto queueResult = GetQueuedCompletionStatus(iocp, &numberOfBytesTransferred, &completionKey, (LPOVERLAPPED *)&overlapped, INFINITE);
    if (queueResult)
    {
        switch (overlapped->operation)
        {
            case tsoRecv:
            {
                verifyReceivedData(overlapped, numberOfBytesTransferred); // Checks the data is a sequence of incremented integers 1 after the other with no gabs
                overlapped->overlapped = OVERLAPPED(); // Reset the OVERLAPPED structure to defaults

                DWORD flags = 0;
                numberOfBytesTransferred = 0;
                auto returnCode = WSARecv(socket, &(overlapped->buffer), 1, &numberOfBytesTransferred, &flags, (LPWSAOVERLAPPED) overlapped, nullptr);
                break;
            }
            default:;
        }
    }
}

也许我在上面的简单测试中没有处理某种错误或附加信息?我最初有一个 IOCP 客户端发送数据,但在 Delphi 中使用 Indy 阻塞套接字编写了另一个非常简单的客户端。它基本上是连接后的一行代码。

while true do
begin
    IdTCPClient.IOHandler.WriteDirect(TIdBytes(BigData), Length(BigData));
end;

我还使用不同的异步套接字组件编写了另一个服务器,但我还没有像上面的 IOCP 示例那样检测接收到的数据的问题,至少现在是这样。我可以 post 更多代码和可能的版本来编译,但我想我会 post 上面的内容以防我错过了一些明显的东西。我认为每个套接字使用一个接收和一个发送可以正常工作,但我的理解是 post 多个有效以提高性能。

我相信这已经解决了——我的大部分假设和代码都是正确的,但是对于一个特定的套接字,似乎不能从多个线程同时调用 WSASend 或 WSARead。对于特定套接字的发送和接收可能有多个未完成的调用,但启动它们的实际调用需要使用关键部分(或类似部分)进行序列化。这是我对 MSDN 文档的一个轻微误解,我认为它可以完成,但如果没有一些额外的同步,您将不知道哪个缓冲区将首先被填充(而且我的测试并不关心哪个缓冲区首先被填充)。看起来它根本不安全,除非一次调用一个并且可能导致缓冲区内的数据损坏。

我更改的唯一代码是为每个连接添加一个关键部分以保护对它们的调用,到目前为止没有任何问题。我认为可能可以单独保护 WSASend 和 WSARecv,但还没有测试过。

我发布了一个与此相关的更深入的问题 ,其中包含更多代码示例。

去掉循环。您已经在检查接收到的数据并安排新的异步读取,这将或应该在读取完成时重新输入此代码。循环完全不正确。