是否有确定的方法来确定在asio框架中是否关闭了TCP连接的另一端而没有发送任何数据?
对服务器进程使用Boost.asio,如果客户端在服务器响应请求之前超时或以其他方式断开连接,则服务器在完成请求并生成响应发送之前不会找到它,当发送立即生成连接中止错误时。
对于一些长时间运行的请求,这可能会导致客户端一次又一次地取消和重试,堆积了并行运行的同一请求的许多实例,这使得它们需要更长时间并且“滚雪球”成雪崩而使服务器无法使用。基本上反复击中F5是一种拒绝服务攻击。
不幸的是,在请求完成之前我无法开始发送响应,因此“结果”输出结果不是一个选项,我需要能够在请求处理期间检查关键点并停止处理如果客户放弃了。
答案 0 :(得分:2)
此问题的关键是避免在接收处理程序中执行请求处理。以前,我做的是这样的事情:
async_receive(..., recv_handler)
void recv_handler(error) {
if (!error) {
parse input
process input
async_send(response, ...)
相反,适当的模式更像是这样:
async_receive(..., recv_handler)
void async_recv(error) {
if (error) {
canceled_flag = true;
} else {
// start a processing event
if (request_in_progress) {
capture input from input buffer
io_service.post(process_input)
}
// post another read request
async_receive(..., recv_handler)
}
}
void process_input() {
while (!done && !canceled_flag) {
process input
}
async_send(response, ...)
}
显然我遗漏了很多细节,但重要的是将处理作为单独的“事件”发布在io_service线程池中,以便可以同时运行额外的接收。这允许在处理过程中接收“连接中止”消息。但请注意,这意味着两个线程必须相互通信,需要某种同步,并且正在处理的输入必须与接收调用所在的输入缓冲区分开保存,因为更多数据可能到达另外的阅读电话。
编辑:
我还应该注意,如果在处理过程中收到更多数据,您可能不希望启动另一个异步处理调用。以后的处理可能会先完成,结果可能会无序地发送给客户端。除非你使用UDP,否则这可能是一个严重错误。
这是一些伪代码:
async_read (=> read_complete)
read_complete
store new data in queue
if not currently processing
if a full request is in the queue
async_process (=> process_complete)
else ignore data for now
async_read (=> read_complete)
async_process (=> process_complete)
process data
process_complete
async_write_result (=> write_complete)
write_complete
if a full request is in the queue
async_process (=> process_complete)
因此,如果在请求正在进行时收到数据,则它会排队但不会被处理。一旦处理完成并发送结果,我们就可以使用之前收到的数据再次开始处理。
通过允许在写入前一个请求的结果时进行处理,可以进一步优化这一点,但这需要更加小心,以确保结果的编写顺序与接收请求的顺序相同。 / p>
答案 1 :(得分:1)
如果连接经历了有序关闭,即客户端在套接字上调用close
或shutdown
,那么您可以从套接字读取一个非阻塞的一字节来确定它是否是仍然连接:
int ret = recv(sockfd, buf, 1, MSG_DONTWAIT | MSG_PEEK);
errno == EAGAIN
MSG_PEEK
标志将数据保留在套接字缓冲区中。ret
将等于0表示正常关闭连接。现在这种技术并不是完整的证据,但只要来自客户端的FIN
数据包到达,它就会在很长一段时间内起作用。
你应该能够调整它以便与Boost.Asio一起使用,只要它允许你将套接字标志传递给它的recv
函数。