我编写了一个Socket类,管理套接字文件描述符并处理使用RAII打开和关闭。该类还公开了面向连接的套接字(TCP)的标准套接字操作,如绑定、监听、接受、连接等。
阅读send和recv系统调用的手册后,我意识到需要在某种形式的循环内调用这些函数,以保证所有字节都成功发送/接收。
我的发送和接收API看起来类似于这样
void SendBytes(const std::vector<std::uint8_t>& bytes) const;
void SendStr(const std::string& str) const;
std::vector<std::uint8_t> ReceiveBytes() const;
std::string ReceiveStr() const;
对于发送功能,我决定在循环内使用阻塞的send
调用,如下所示(它是一个内部帮助函数,适用于std :: string和std :: vector)。
template<typename T>
void Send(const int fd, const T& bytes)
{
using ValueType = typename T::value_type;
using SizeType = typename T::size_type;
const ValueType *const data{bytes.data()};
SizeType bytesToSend{bytes.size()};
SizeType bytesSent{0};
while (bytesToSend > 0)
{
const ValueType *const buf{data + bytesSent};
const ssize_t retVal{send(fd, buf, bytesToSend, 0)};
if (retVal < 0)
{
throw ch::NetworkError{"Failed to send."};
}
const SizeType sent{static_cast<SizeType>(retVal)};
bytesSent += sent;
bytesToSend -= sent;
}
}
这似乎很好地运作,并保证了一旦成员函数返回且没有抛出异常,所有字节都已发送。然而,当我开始实现接收功能时,遇到了问题。在我的第一次尝试中,我在循环内使用了一个阻塞的
recv
调用,并在 recv
返回 0 表示底层 TCP 连接已关闭时退出循环。template<typename T>
T Receive(const int fd)
{
using SizeType = typename T::size_type;
using ValueType = typename T::value_type;
T result;
const SizeType bufSize{1024};
ValueType buf[bufSize];
while (true)
{
const ssize_t retVal{recv(fd, buf, bufSize, 0)};
if (retVal < 0)
{
throw ch::NetworkError{"Failed to receive."};
}
if (retVal == 0)
{
break; /* Connection is closed. */
}
const SizeType offset{static_cast<SizeType>(retVal)};
result.insert(std::end(result), buf, buf + offset);
}
return result;
}
只要发送方在发送完所有字节后关闭连接,这段代码就可以正常工作。但是,当使用例如Chrome请求网页时,情况并非如此。连接保持打开状态,我的接收成员函数在接收请求中的所有字节后会被阻塞在recv系统调用上。我通过使用setsockopt设置recv调用的超时来解决了这个问题。基本上,一旦超时到期,我就返回到目前为止接收到的所有字节。这感觉像一个非常不优雅的解决方案,我不认为这是Web服务器在现实中处理此问题的方式。
那么,接下来是我的问题。
Web服务器如何知道HTTP请求已完全接收?
HTTP 1.1中的GET请求似乎不包括Content-Length头。请参见this link。
GET
请求并没有任何数据,它只有(可选的)头部字段和一个明确定义的终止。 - Some programmer dude