使用 send() 和 FD_WRITE 的 Winsock

Winsock using send() and FD_WRITE

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

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

我尝试使用简单的 HTTP 客户端 Wget 来更好地了解发生了什么。据我所知,当我在使用 send() 后检查错误时检测到 WSAEWOULDBLOCK 错误时,数据流变薄了。看起来在这些发送过程中,并不是所有的数据都被发送了。

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

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);
  1. 如果不将返回值存储在变量中,就无法编写正确的非阻塞 send() 代码。它是实际发送的字节数。您不能假设整个缓冲区都是以非阻塞模式发送的。

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

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

但我会质疑整个方法。无论如何,在阻塞模式套接字上发送是异步的。您目前的方法几乎无济于事。只需分块读取文件并以阻塞模式发送块。

TransmitFile功能就是为您解决这个问题。它完全在内核模式下完成所有工作,因此它总是会击败手工制作的版本。

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

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

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

(无处不在的网络服务器有助于激励 OS 设计师解决这个问题。)