【问题标题】:Slow transfer speed on remote computer远程计算机上的传输速度慢
【发布时间】:2011-06-27 22:35:48
【问题描述】:

大家好,StackOverflow 的人!

我正在制作一个 IOCP 服务器,到目前为止我已经解决了大多数问题,但仍然存在一个问题,我不知道从哪里开始。当我在我的机器上运行客户端/服务器时,一切都很好。它与 Windows SDK 示例的速度相匹配,可能会快一点,并且肯定使用更少的 CPU 周期。但是,当我从另一台计算机运行客户端时,传输速度上限为 37 KB/s,往返延迟为 200 毫秒(而不是 0)。现在,如果我将客户端连接到 SDK 示例服务器,我就没有这个问题,所以我的代码有问题。据我所知,套接字以完全相同的方式使用相同的选项进行初始化。我还在探查器中运行了我的服务器以检查瓶颈,但我找不到任何瓶颈。此外,我尝试过的计算机连接到同一个千兆交换机(带有千兆适配器)。我知道这有点含糊,但那是因为到目前为止我无法确定问题所在,如果你们中的任何人能指出我正确的方向,我将永远感激不尽。

干杯,

-罗克西

编辑2: 在听从 Mike 的建议后,我对代码进行了一些研究,发现当远程客户端连接到服务器时,大部分时间代码都在等待 GetQueuedCompletionStatus。这表明 IO 请求只是需要很长时间才能完成,但我仍然不明白为什么。这只发生在客户端位于远程计算机上时。我认为这与如何设置套接字或我如何发布请求有关,但我看不出与示例代码有任何区别。

有什么想法吗?

编辑(添加示例代码):

好的,到了!虽然不好看!

如果您安装了 Windows SDK,您可以使用 iocpclient 示例 (Program Files\Microsoft SDKs\Windows\v7.1\Samples\netds\winsock\iocp\client) 连接到它,并在行更改它的默认端口73 到 5000。

我在自己尝试时注意到的奇怪的事情是,示例 iocpclient 似乎在 37KB/s 问题上不会导致相同的上限...但是看起来示例代码的限制设置为大约 800KB /秒。如果有任何帮助,我会发布一个客户。

#pragma comment(lib, "Ws2_32.lib")

#include <WinSock2.h>
#include <stdio.h>

unsigned int connection = 0;
unsigned int upload = 0;
unsigned int download = 0;

#define IO_CONTEXT_COUNT 5

class NetClientHost
{
friend class gNetProtocolHost;
public:
enum Operation
{
    kOperationUnknown,
    kOperationRead,
    kOperationWrite,
};

struct ClientData
{
    SOCKET           socket;
};

struct IOContext
{
    WSAOVERLAPPED    overlapped;
    WSABUF           wsaReceiveBuf;
    WSABUF           wsaSendBuf;
    char            *buf;
    char            *TESTbuf;
    unsigned long    bytesReceived;
    unsigned long    bytesSent;
    unsigned long    flags;
    unsigned int     bytesToSendTotal;
    unsigned int     remainingBytesToSend;
    unsigned int     chunk;
    Operation        operation;
};

NetClientHost()
{
    memset((void *) &m_clientData, 0, sizeof(m_clientData));
}

NetClientHost::IOContext *NetClientHost::AcquireContext()
{
    while (true)
    {
        for (int i = 0; i < IO_CONTEXT_COUNT; ++i)
        {
            if (!(m_ioContexts + i)->inUse)
            {
                InterlockedIncrement(&(m_ioContexts + i)->inUse);
                //ResetEvent(*(m_hContextEvents + i));

                if ((m_ioContexts + i)->ioContext.TESTbuf == 0)
                    Sleep(1);

                return &(m_ioContexts + i)->ioContext;
            }
        }
        //++g_blockOnPool;
        //WaitForMultipleObjects(IO_CONTEXT_COUNT, m_hContextEvents, FALSE, INFINITE);
    }   
}

const ClientData *NetClientHost::GetClientData() const
{
    return &m_clientData;
};

void NetClientHost::Init(unsigned int bufferSize)
{   
    _InitializeIOContexts(bufferSize ? bufferSize : 1024);
}

void NetClientHost::ReleaseContext(IOContext *ioContext)
{
    int i = sizeof(_IOContextData), j = sizeof(IOContext);
    _IOContextData *contextData = (_IOContextData *) (((char *) ioContext) - (i - j));
    InterlockedDecrement(&contextData->inUse);
    //SetEvent(*(m_hContextEvents + contextData->index));
}   

struct _IOContextData
{
    unsigned int index;
    volatile long inUse;        
    IOContext ioContext;
};

ClientData                    m_clientData;
_IOContextData               *m_ioContexts;
HANDLE                       *m_hContextEvents;

void _InitializeIOContexts(unsigned int bufferSize)
{
    m_ioContexts = new _IOContextData[IO_CONTEXT_COUNT];
    m_hContextEvents = new HANDLE[IO_CONTEXT_COUNT];

    memset((void *) m_ioContexts, 0, sizeof(_IOContextData) * IO_CONTEXT_COUNT);

    for (int i = 0; i < IO_CONTEXT_COUNT; ++i)
    {
        (m_ioContexts + i)->index = i;

        (m_ioContexts + i)->ioContext.buf = new char[bufferSize];
        (m_ioContexts + i)->ioContext.wsaReceiveBuf.len = bufferSize;
        (m_ioContexts + i)->ioContext.wsaReceiveBuf.buf = (m_ioContexts + i)->ioContext.buf;
        (m_ioContexts + i)->ioContext.TESTbuf = new char[10000];
        (m_ioContexts + i)->ioContext.wsaSendBuf.buf = (m_ioContexts + i)->ioContext.TESTbuf;

        *(m_hContextEvents + i) = CreateEvent(0, TRUE, FALSE, 0);
    }
}
void _SetSocket(SOCKET socket)
{
    m_clientData.socket = socket;
}
};



bool WriteChunk(const NetClientHost *clientHost, NetClientHost::IOContext *ioContext)
{
int status;

status = WSASend(clientHost->GetClientData()->socket, &ioContext->wsaSendBuf, 1, &ioContext->bytesSent, ioContext->flags, &ioContext->overlapped, 0);
if (status == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING)
{
    // ...
    return false;
}

return true;
}

bool Write(NetClientHost *clientHost, void *buffer, unsigned int size, unsigned int chunk)
{
//__ASSERT(m_clientHost);
//__ASSERT(m_clientHost->GetClientData()->remainingBytesToSend == 0);

NetClientHost::IOContext *ioContext = clientHost->AcquireContext();

if (!chunk)
    chunk = size;

ioContext->wsaSendBuf.buf = ioContext->TESTbuf;

ioContext->operation                = NetClientHost::kOperationWrite;
ioContext->flags                    = 0;
ioContext->wsaSendBuf.buf = new char[size];
memcpy((void *) ioContext->wsaSendBuf.buf, buffer, chunk);
ioContext->wsaSendBuf.len           = chunk;    
ioContext->chunk                    = chunk;
ioContext->bytesToSendTotal         = size;
ioContext->remainingBytesToSend     = size;

return WriteChunk(clientHost, ioContext);
}



void Read(NetClientHost *clientHost)
{   
NetClientHost::IOContext *ioContext = clientHost->AcquireContext();
int status;

memset((void *) ioContext, 0, sizeof(NetClientHost::IOContext));
ioContext->buf = new char[1024];
ioContext->wsaReceiveBuf.len = 1024;
ioContext->wsaReceiveBuf.buf = ioContext->buf;

ioContext->flags = 0;
ioContext->operation = NetClientHost::kOperationRead;

status = WSARecv(clientHost->GetClientData()->socket, &ioContext->wsaReceiveBuf, 1, &ioContext->bytesReceived, &ioContext->flags, &ioContext->overlapped, 0);
int i = WSAGetLastError();
if (status == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING)
{
    // ...
}   
}

bool AddSocket(HANDLE hIOCP, SOCKET socket)
{
++connection;

int bufSize = 0;
LINGER lingerStruct;
lingerStruct.l_onoff = 1;
lingerStruct.l_linger = 0;
setsockopt(socket, SOL_SOCKET, SO_SNDBUF, (char *) &bufSize, sizeof(int));
setsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char *) &bufSize, sizeof(int));
setsockopt(socket, SOL_SOCKET, SO_LINGER, (char *) &lingerStruct, sizeof(lingerStruct) ); 

NetClientHost *clientHost = new NetClientHost;

clientHost->_InitializeIOContexts(1024);
clientHost->Init(0);
clientHost->_SetSocket(socket);

// Add this socket to the IO Completion Port
CreateIoCompletionPort((HANDLE) socket, hIOCP, (DWORD_PTR) clientHost, 0);

Read(clientHost);
return true;
}

int read = 0, write = 0;

DWORD WINAPI WorkerThread(LPVOID param)
{
LPOVERLAPPED overlapped;
NetClientHost *clientHost;
HANDLE hIOCP = (HANDLE) param;
DWORD ioSize;
BOOL status;

while (true)
{
    status = GetQueuedCompletionStatus(hIOCP, &ioSize, (PULONG_PTR) &clientHost, (LPOVERLAPPED *) &overlapped, INFINITE);

    if (!(status || ioSize))
    {
        --connection;
        //_CloseConnection(clientHost);
        continue;
    }

    NetClientHost::IOContext *ioContext = (NetClientHost::IOContext *) overlapped;

    switch (ioContext->operation)
    {
    case NetClientHost::kOperationRead:
        download += ioSize;
        Write(clientHost, ioContext->wsaReceiveBuf.buf, ioSize, 0);
        write++;
        clientHost->ReleaseContext(ioContext);
        break;

    case NetClientHost::kOperationWrite:
        upload += ioSize;
        if (ioContext->remainingBytesToSend)
        {
            ioContext->remainingBytesToSend -= ioSize;
            ioContext->wsaSendBuf.len = ioContext->chunk <= ioContext->remainingBytesToSend ? ioContext->chunk : ioContext->remainingBytesToSend; // equivalent to min(clientData->chunk, clientData->remainingBytesToSend);
            ioContext->wsaSendBuf.buf += ioContext->wsaSendBuf.len;
        }

        if (ioContext->remainingBytesToSend)
        {       
            WriteChunk(clientHost, ioContext);
        }
        else
        {
            clientHost->ReleaseContext(ioContext);              
            Read(clientHost);
            read++;
        }
        break;
    }
}

return 0;
}

DWORD WINAPI ListenThread(LPVOID param)
{
SOCKET sdListen = (SOCKET) param;

HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
CreateThread(0, 0, WorkerThread, hIOCP, 0, 0);
CreateThread(0, 0, WorkerThread, hIOCP, 0, 0);
CreateThread(0, 0, WorkerThread, hIOCP, 0, 0);
CreateThread(0, 0, WorkerThread, hIOCP, 0, 0);

while (true)
{
    SOCKET as = WSAAccept(sdListen, 0, 0, 0, 0);
    if (as != INVALID_SOCKET)
        AddSocket(hIOCP, as);
}
}

int main()
{
SOCKET      sdListen;
SOCKADDR_IN si_addrlocal;   
int         nRet;   
int         nZero = 0;   
LINGER      lingerStruct;   

WSADATA wsaData;
WSAStartup(0x202, &wsaData);

sdListen = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_IP, NULL, 0, WSA_FLAG_OVERLAPPED);    
si_addrlocal.sin_family = AF_INET;   
si_addrlocal.sin_port = htons(5000);   
si_addrlocal.sin_addr.s_addr = htonl(INADDR_ANY);          
nRet = bind(sdListen, (struct sockaddr *)&si_addrlocal, sizeof(si_addrlocal));   
nRet = listen(sdListen, 5);

nZero = 0;   
nRet = setsockopt(sdListen, SOL_SOCKET, SO_SNDBUF, (char *) &nZero, sizeof(nZero));   
nZero = 0;   
nRet = setsockopt(sdListen, SOL_SOCKET, SO_RCVBUF, (char *)&nZero, sizeof(nZero));
lingerStruct.l_onoff = 1;   
lingerStruct.l_linger = 0; 
nRet = setsockopt(sdListen, SOL_SOCKET, SO_LINGER, (char *)&lingerStruct, sizeof(lingerStruct) );

CreateThread(0, 0, ListenThread, (LPVOID) sdListen, 0, 0);

HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);
while (true)
{
    COORD c = {0};
    SetConsoleCursorPosition(console, c);
    printf("Connections: %i                      \nUpload: %iKB/s               \nDownload: %iKB/s              ", connection, upload * 2 / 1024, download * 2 / 1024);
    upload = 0;
    download = 0;
    Sleep(500);
}



return 0;
}

【问题讨论】:

  • @Paul Y 2 懒惰 2 投票 2 关闭?
  • @Paul, @bzlm:这显然是一个编程问题。它不属于SF。
  • @Roxanne:你需要展示一些代码。尽量减少出现问题的代码,并将其添加到您的问题中。
  • IOCP 旨在为大量同时连接提供服务,不一定是每个连接的最大带宽,但即使只有一个连接,您也应该获得比这更好的吞吐量。

标签: windows performance sockets tcp


【解决方案1】:

这种异步系统应该能够以全数据链路速度运行。我发现错误的问题如下:

  • 超时设置导致不必要的重传
  • 在接收过程中,接收到的消息 A 可能会触发数据库更新,这样接收到的消息 B 必须等待,导致将消息 B 的响应返回给发送者,而数据库更新实际上可以在空闲时间。

有一种叫做 wireshark 的东西可以让您了解消息流量。 我过去常常用带时间戳的消息日志来做这件事。

顺便说一句:在进行异步分析之前,我会首先在各个进程上使用this method,以清除任何瓶颈。如果你还没有这样做,你可以打赌他们在那里。 只是任何旧的分析器都不可靠。有好的,包括Zoom

【讨论】:

  • 嗨,迈克,现在它什么都不做,只是将发送给它的任何内容发回,只是为了在转移到其他内容之前测试我的代码,以便排除由 DB 或类似内容引起的任何延迟。此外,当我在本地计算机上运行代码时,代码运行良好(大约 20Mb/s),这就是为什么我认为问题出在网络代码上的原因。我一定会查看您发布的链接并回复我的结果。谢谢:)
  • @ Mike:我已经阅读并遵循了您在另一个线程中发布的说明,并且大部分时间代码都在等待 GetQueuedCompletionStatus。这表明 IO 请求只是需要很长时间才能完成,但我仍然不明白为什么。这只发生在客户端位于远程计算机上时。我认为这与如何设置套接字或我如何发布请求有关,但我看不出与示例代码有任何区别。
  • @Roxanne:很好。它应该将大部分时间花在等待 IO 上。下一个问题是,目的是什么?我没有研究过代码,但是每次你暂停它时,如果你查看堆栈,你应该能够知道它是在等待“发送”完成还是“接收”,如果是,是什么类型消息(请求、数据、确认等)。如果正在交换不必要的消息,这应该会给你一个线索。
  • @Roxanne:我见过双方都花了很多时间说,实际上,“对不起,你说什么?你能听到我吗?你还在吗?”或“等我点击 DNS 服务器再次查找您的 IP 地址。”
  • @Mike:它似乎总是在等待接收。在我在问题中提供的代码中没有消息,服务器只是发送您发送给它的任何字节。在玩它时,我发现了一个有趣的事实:在客户端中,我在 recv 上设置了 MSG_WAITALL 以确保它在返回之前接收到所有 1080 字节的数据。如果我删除该标志,传输将以更快的速度开始,但最终会慢下来。我会继续调查,但现在我完全不知道为什么会这样。顺便说一句,感谢您提供的所有帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多