如何在POCO的HTTPRequestHandler中检测客户端断开导致的流关闭?
如何在POCO的HTTPRequestHandler中检测客户端断开导致的流关闭?
我之前做MJPEG流推送的时候也碰到过一模一样的问题——用POCO的HTTP服务器发送multipart/x-mixed-replace格式的图片流,客户端关闭窗口后服务器还在傻循环发数据,完全不知道连接已经断了。后来研究了POCO的流处理机制,找到了两个在POCO框架内就能解决的办法,不用引入第三方库,也不用额外开端口。
核心原理
POCO的HTTPServerResponse::send()返回的std::ostream是和底层TCP连接绑定的。当客户端断开连接时,后续的写入操作会失败,流会自动设置对应的错误状态位(failbit或badbit)。我们只需要检测这个状态,就能知道什么时候该停止发送了。
方法一:检查流的状态位
这种方式比较直观,每次写入后检查流是否还处于可用状态,同时记得刷新流——因为POCO的流默认带缓冲,不刷新的话,写入操作只是把数据放到内存里,不会触发实际的网络发送,自然检测不到连接断开。
修改后的代码如下:
void handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response) override { std::ifstream imgFile("../../assets/imgs/random.jpg"); std::stringstream ss{}; ss << imgFile.rdbuf(); std::string buf = ss.str(); std::string boundary = "--BOUNDARY--"; response.setVersion(request.getVersion()); response.setStatus(Poco::Net::HTTPServerResponse::HTTP_OK); response.setChunkedTransferEncoding(false); response.setKeepAlive(false); response.setContentType("multipart/x-mixed-replace; boundary=" + boundary); response.set("Access-Control-Allow-Origin", "*"); response.set("Connection", "Close"); response.set("Cache-Control", "no-cache, no-store, must-revalidate, pre-check=0, post-check=0, max-age=0, false"); response.set("Pragma", "no-cache"); std::ostream& ostr = response.send(); // 明确设置流仅通过状态位反馈错误,不抛出异常(默认也是这个行为) ostr.exceptions(std::ostream::goodbit); while (ostr.good()) { ostr << boundary << "\r\n"; ostr << "Content-Type: image/jpeg\r\n" << "Content-Length: " << buf.length() << "\r\n\r\n"; ostr << buf; ostr << "\r\n"; // 强制刷新缓冲区,触发实际网络写入,才能检测连接状态 ostr.flush(); // 可选:添加延迟控制帧率,避免发送过快 // Poco::Thread::sleep(100); } // 退出循环说明流已失效,客户端大概率断开了连接 }
方法二:让流抛出异常
如果你更习惯用异常处理,可以给流设置异常掩码,让它在写入失败时直接抛出异常,我们捕获异常后退出循环即可:
void handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response) override { std::ifstream imgFile("../../assets/imgs/random.jpg"); std::stringstream ss{}; ss << imgFile.rdbuf(); std::string buf = ss.str(); std::string boundary = "--BOUNDARY--"; response.setVersion(request.getVersion()); response.setStatus(Poco::Net::HTTPServerResponse::HTTP_OK); response.setChunkedTransferEncoding(false); response.setKeepAlive(false); response.setContentType("multipart/x-mixed-replace; boundary=" + boundary); response.set("Access-Control-Allow-Origin", "*"); response.set("Connection", "Close"); response.set("Cache-Control", "no-cache, no-store, must-revalidate, pre-check=0, post-check=0, max-age=0, false"); response.set("Pragma", "no-cache"); std::ostream& ostr = response.send(); // 设置流在出现严重错误(badbit)或写入失败(failbit)时抛出异常 ostr.exceptions(std::ostream::badbit | std::ostream::failbit); try { while (true) { ostr << boundary << "\r\n"; ostr << "Content-Type: image/jpeg\r\n" << "Content-Length: " << buf.length() << "\r\n\r\n"; ostr << buf; ostr << "\r\n"; ostr.flush(); // 可选:添加延迟控制帧率 // Poco::Thread::sleep(100); } } catch (const std::ios_base::failure& e) { // 捕获到异常,说明客户端断开或流出现错误,直接退出即可 // 这里可以根据需求添加日志记录 } }
注意事项
- 必须调用flush():这是关键!如果不刷新,缓冲的数据不会被发送到网络,即使连接断了,流也不会立刻反馈错误。
- 帧率控制:建议添加小延迟,避免无限制发送导致CPU或网络资源占用过高,同时也符合MJPEG流的常规帧率(比如10帧/秒就加100ms延迟)。
- Content-Length准确性:你关闭了分块编码,所以要确保每个图片部分的
Content-Length和实际图片字节数完全一致,否则客户端可能解析出错。
备注:内容来源于stack exchange,提问作者Daniel




