【问题标题】:Winsock using send() and FD_WRITEWinsock 使用 send() 和 FD_WRITE
【发布时间】:2015-12-30 06:45:37
【问题描述】:

我是 Winsock 的新手。我正在编写一个 HTTP 服务器,我的目标是通过读取和发送块来发送一个大文件。为了优化这个过程,我尝试使用非阻塞套接字,在发送当前块的同时从磁盘读取下一个块。

我现在的问题是,即使缓冲区看起来应该已满,并且我的函数过早地从内存中删除相关数据,我也会收到一条 FD_WRITE 消息。我相信这会导致我的响应包含的数据少于它们应该包含的数据 send() 过早地停止发送,并且客户端(这是一个众所周知的)接收到大约 70% 的数据。当我使用阻塞套接字时,它可以正常工作,只是更长。

我尝试使用 Wget(一个简单的 HTTP 客户端)来更好地了解正在发生的事情。据我所知,当我在使用 send() 后检查错误时检测到 WSAEWOULDBLOCK 错误时,数据流会变薄。看起来在这些发送期间,并非所有数据都已发送。

当我在检查 FD_WRITE 消息后将睡眠时间设置为超过 2000 毫秒时,一切正常,因为它基本上归结为使用阻塞套接字。我还尝试将时间设置为 100-200 毫秒左右,但也失败了。事实上,FD_WRITE 的条件检查总是在进入循环之前返回有效。

WSAEVENT event = WSACreateEvent();
const int sendBufferSize = 1024 * 64;
int connectionSpeed = 5; //estimated, in MBytes/s
const int sleepTime = sendBufferSize / (connectionSpeed * 1024 * 1024);
size = 0;
const int bufSize = 1024 * 1024 * 35;
int lowerByteIndex = 0;
int upperByteIndex = bufSize;
size = bufSize;
int totalSIZE = 0;
unsigned char* p;
unsigned char* pt;
clock_t t = std::clock();

p = getFileBytesC(resolveLocalPath(path), size, lowerByteIndex, upperByteIndex);
lowerByteIndex += bufSize;
upperByteIndex += bufSize;
totalSIZE += size;
while (upperByteIndex <= fileSize + bufSize)
{
    int ret = send(socket, (char*)p, size, 0);
    pt = getFileBytesC(resolveLocalPath(path), size, lowerByteIndex, upperByteIndex);
    totalSIZE += size;
    lowerByteIndex += bufSize;
    upperByteIndex += bufSize;
    if (ret == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
    {
        while (SOCKET_ERROR == WSAEventSelect(socket, event, FD_WRITE))
        {
            Sleep(50);
        }
    }
    Sleep(sleepTime); //should be around 30-50ms. Wait for the buffer to be empty
    delete[] p;
    p = pt;
    std::cout << std::endl << (std::clock() - t) / (double)CLOCKS_PER_SEC;
}
send(socket, (char*)p, size, 0);
delete[] p;
std::cout << std::endl << (std::clock() - t) / (double)CLOCKS_PER_SEC ;
if (totalSIZE == fileSize) std::cout << std::endl << "finished transfer. UBI=" << upperByteIndex;
else
{
    std::cout << std::endl << "ERROR: finished transfer\r\nBytes read=" << totalSIZE;
}
Sleep(2000);
closeSocket(socket);

【问题讨论】:

    标签: c++ sockets winsock send


    【解决方案1】:

    TransmitFile 函数的存在正是为了为您解决这个问题。它完全在内核模式下完成所有工作,因此它总是能胜过手工制作的版本。

    https://msdn.microsoft.com/en-us/library/windows/desktop/ms740565(v=vs.85).aspx

    编辑:在 Linux 上,有一些类似的 sendfile 调用。

    http://linux.die.net/man/2/sendfile

    (无处不在的 Web 服务器有助于激励操作系统设计人员解决这个问题。)

    【讨论】:

    • 哇,我不知道这个。我一定会尝试一下,尽管我也想要一些最终可以针对 Linux 进行调整的东西。
    • 我试过TransmitFile,但我得到的结果可能比使用阻塞套接字的原始函数要好一些。那是 200Mbps 与 localhost 上的 ~185Mbps。这是我的代码HANDLE hFile = CreateFile(resolveLocalPath(path).c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); TransmitFile(socket, hFile, fileSize, 1024 * 1024 * 35, NULL, NULL, TF_USE_KERNEL_APC | TF_WRITE_BEHIND); CloseHandle(hFile); 有没有办法让它更快?我尝试了几种缓冲区大小和 TransmitFile 块大小(大小)的组合。
    • 我一直只是将块大小设置为 0(使用默认值)。本地主机似乎是一个相当虚假的测试(肯定有更快的 IPC 方法)。无论如何,你需要它跑多快,限制因素是什么?
    • 好吧,我昨天在局域网上测试了它。结果是它使我的网络适配器崩溃,我不得不重新启动适配器和我的家庭路由器。知道为什么吗?!
    【解决方案2】:
    1. 如果不将返回的值存储在变量中,您将无法编写正确的非阻塞 send() 代码。它是实际发送的字节数。您不能假设整个缓冲区是在非阻塞模式下发送的。

    2. 如果 send() 返回 -1 和 WSAGetLastError() == WSAEWOULDBLOCK 或其他任何值,那么 是时候调用 WSASelect(),WSAEVENTSelect()(如果必须),但会超时.否则,即如果它返回一个正数,你应该只增加你的偏移量并将你的长度减少发送的数量,然后重复直到没有任何东西可以发送。

    3. 你的睡眠简直就是在浪费时间。

    但我会质疑整个方法。无论如何,在阻塞模式套接字上发送都是异步的。你目前的方法几乎没有什么收获。只需分块读取文件并以阻塞模式发送块。

    【讨论】:

    • @Æðelstan 他的意思是用 blocking 套接字发送数据不会等到数据被传输等等,直到数据被复制到你的 RAM 中的某个地方 =>在此期间,使用发送方站点上的非阻塞套接字从 HDD 读取不会获得任何东西,因为发送时间可以忽略不计。 ...接收具有阻塞/非阻塞的数据可能有显着的时间差异,但不会发送。阻塞发送真正阻塞[长时间]的唯一时间。是内核缓冲区已满且无法正确处理时;在这种情况下,无论如何连接都有问题
    • @Æðelstan 在同样的情况下,您的非阻塞代码也必须等待。
    • @Æðelstan 问题是你这样做了:int ret = send(socket, (char*)p, size, 0);。在此 send() 调用之后,您必须考虑 ret 可能小于 size 的事实。例如你要求它发送 1000 个字节,但也许 send() 只发送了 721 个字节。 (另外,删除你的睡眠电话,把它们放在那里是不对的。)
    • 这是一个“是”,也试图让你思考。
    • @Æðelstan 您正在使用 TCP。 TCP 会将数据分段为当时有意义的任何内容,这是网络行为方式、另一端的快/慢等的复杂组合。仅仅因为有 64kb 的缓冲区并不意味着总是有空间容纳 0 或 64kb 的数据。 TCP 可能决定发送大约 1460 个字节来填满一个以太网帧,或者它可能决定发送 155 个字节,因为这就是接收方目前的全部空间。然后你会收到一个写入事件,表明套接字缓冲区中有可用数据,可能只有几个字节。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-28
    • 1970-01-01
    • 1970-01-01
    • 2011-09-13
    相关资源
    最近更新 更多