Winsock using send() and FD_WRITE

Winsock using send() and FD_WRITE

本文关键字:FD WRITE and using send Winsock      更新时间:2023-10-16

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

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

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

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

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 transferrnBytes read=" << totalSIZE;
}
Sleep(2000);
closeSocket(socket);
  1. 如果不将返回值存储在变量中,则无法编写正确的非阻塞send()代码。它是实际发送的字节数。你不能假设整个缓冲区是在非阻塞模式下发送的

  2. 如果send()返回-1与WSAGetLastError() == WSAEWOULDBLOCK或任何它是,然后是调用WSASelect(),WSAEVENTSelect()的时间,如果你必须,但有一个超时。否则,也就是说,如果它返回一个正计数,你应该只推进你的偏移量并减少你的长度发送的数量,并重复,直到没有任何东西可以发送。

  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

(无处不在的web服务器帮助激励操作系统设计师解决这个问题)