You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在POCO的HTTPRequestHandler中检测客户端断开导致的流关闭?

如何在POCO的HTTPRequestHandler中检测客户端断开导致的流关闭?

我之前做MJPEG流推送的时候也碰到过一模一样的问题——用POCO的HTTP服务器发送multipart/x-mixed-replace格式的图片流,客户端关闭窗口后服务器还在傻循环发数据,完全不知道连接已经断了。后来研究了POCO的流处理机制,找到了两个在POCO框架内就能解决的办法,不用引入第三方库,也不用额外开端口。

核心原理

POCO的HTTPServerResponse::send()返回的std::ostream是和底层TCP连接绑定的。当客户端断开连接时,后续的写入操作会失败,流会自动设置对应的错误状态位(failbitbadbit)。我们只需要检测这个状态,就能知道什么时候该停止发送了。

方法一:检查流的状态位

这种方式比较直观,每次写入后检查流是否还处于可用状态,同时记得刷新流——因为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) {
        // 捕获到异常,说明客户端断开或流出现错误,直接退出即可
        // 这里可以根据需求添加日志记录
    }
}

注意事项

  1. 必须调用flush():这是关键!如果不刷新,缓冲的数据不会被发送到网络,即使连接断了,流也不会立刻反馈错误。
  2. 帧率控制:建议添加小延迟,避免无限制发送导致CPU或网络资源占用过高,同时也符合MJPEG流的常规帧率(比如10帧/秒就加100ms延迟)。
  3. Content-Length准确性:你关闭了分块编码,所以要确保每个图片部分的Content-Length和实际图片字节数完全一致,否则客户端可能解析出错。

备注:内容来源于stack exchange,提问作者Daniel

火山引擎 最新活动