You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Socket read()函数在隧道处理中不返回0的循环退出条件问题

代理服务器CONNECT隧道的无限循环退出问题

我正在为代理服务器实现CONNECT方法对应的隧道功能,当前代码可成功加载HTTPS网站,但存在无限while循环无法退出的问题,原因是Socket read()函数有时始终不返回0。

相关代码如下:

当端口为443时的处理逻辑:

if(port == 443){ // https
    strcpy(buffer, "HTTP/1.0 200 Connection established\r\n\r\n");
    write(client, buffer, strlen(buffer));
    tunnel(server, client, buffer);
    return;
}

tunnel()函数实现:

void tunnel(int server, int client, char* buffer){
    int servercon, clientcon;
    int x;
    x = fcntl(server, F_GETFL, 0);
    fcntl(server, F_SETFL, x|O_NONBLOCK);
    x = fcntl(client, F_GETFL, 0);
    fcntl(client, F_SETFL, x|O_NONBLOCK);
    while(true){
        cout << "test" << endl;
        clientcon = read(client, buffer, BUFSIZE);
        write(server, buffer, clientcon);
        servercon = read(server, buffer, BUFSIZE);
        write(client, buffer, servercon);
        if(???) // 此处需设置退出条件
            break;
    }
    close(client);
    close(server);
}

我尝试过几种退出条件:

  • servercon == 0(有时read()永远不返回0)
  • servercon == -1(会出现安全连接失败)
  • 添加计时器变量i,当i<100000时运行(有效但效率极低)

另外必须将Socket设置为非阻塞模式,否则程序会在read()处永久停滞,虽原因不明但该设置解决了停滞问题。

请问应如何设置正确的退出条件?


这个问题的核心是你没正确处理非阻塞Socket的read()返回值,以及没有考虑双向连接的关闭状态。非阻塞模式下read()的返回值有多种情况,不能只判断0或-1,得结合errno来分析:

先明确非阻塞read()的返回规则

  • 返回值 > 0:成功读取了对应字节数的数据
  • 返回值 = 0:对端已经关闭了连接(FIN包已收到)
  • 返回值 = -1:此时要检查errno:
    • EAGAIN或EWOULDBLOCK:当前没有数据可读,不是错误,应该跳过继续循环
    • 其他错误码(比如ECONNRESET、EPIPE):连接出现异常,需要退出循环

正确的退出条件逻辑

你需要同时检查客户端和服务端两个Socket的状态,并且处理read()的错误情况。另外,你现在的代码里直接把read()的返回值传给write(),如果read()返回-1(比如EAGAIN),write()会写入-1字节,这是错误的,得先过滤这种情况。

修改后的tunnel()函数逻辑大概是这样:

#include <errno.h>
#include <unistd.h>

void tunnel(int server, int client, char* buffer){
    int servercon, clientcon;
    int x;
    bool client_closed = false;
    bool server_closed = false;

    x = fcntl(server, F_GETFL, 0);
    fcntl(server, F_SETFL, x|O_NONBLOCK);
    x = fcntl(client, F_GETFL, 0);
    fcntl(client, F_SETFL, x|O_NONBLOCK);

    while(true){
        // 处理客户端到服务端的数据
        if(!client_closed){
            clientcon = read(client, buffer, BUFSIZE);
            if(clientcon > 0){
                // 成功读取,写入服务端
                write(server, buffer, clientcon);
            } else if(clientcon == 0){
                // 客户端主动关闭连接
                client_closed = true;
                // 给服务端发FIN,关闭写端
                shutdown(server, SHUT_WR);
            } else {
                // 读取错误,判断是否是无数据
                if(errno != EAGAIN && errno != EWOULDBLOCK){
                    // 真正的错误,退出
                    client_closed = true;
                    shutdown(server, SHUT_WR);
                }
            }
        }

        // 处理服务端到客户端的数据
        if(!server_closed){
            servercon = read(server, buffer, BUFSIZE);
            if(servercon > 0){
                // 成功读取,写入客户端
                write(client, buffer, servercon);
            } else if(servercon == 0){
                // 服务端主动关闭连接
                server_closed = true;
                // 给客户端发FIN,关闭写端
                shutdown(client, SHUT_WR);
            } else {
                // 读取错误,判断是否是无数据
                if(errno != EAGAIN && errno != EWOULDBLOCK){
                    // 真正的错误,退出
                    server_closed = true;
                    shutdown(client, SHUT_WR);
                }
            }
        }

        // 退出条件:两端都关闭,完成双向连接的终止
        if(client_closed && server_closed){
            break;
        }

        // 可选:添加短暂休眠,避免空转占用CPU(比计数器高效多了)
        usleep(1000); // 休眠1毫秒
    }

    close(client);
    close(server);
}

关键说明

  1. 分开关闭状态跟踪:分别记录客户端和服务端的连接是否关闭,因为可能一端先关闭,另一端还会发送剩余数据,不能直接退出。
  2. 处理EAGAIN/EWOULDBLOCK:这两个错误码表示当前没有数据可读,不是连接故障,所以不能直接退出,应该继续循环等待。
  3. 调用shutdown():当一端关闭时,要给另一端发送FIN包,告诉对方“我不再发数据了”,这样对方的read()会返回0,完成双向关闭的流程。
  4. 避免空转CPU:添加usleep(1000)可以让循环在没有数据的时候休眠,不会占用过多CPU资源,比你之前的计数器方法高效太多。

另外,你之前的代码里直接把read()的返回值传给write(),如果read()返回-1(比如EAGAIN),write()会尝试写入-1字节,这是无效的甚至会触发错误,所以一定要先判断read()的返回值是否大于0再调用write()。


内容的提问来源于stack exchange,提问作者James

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:48:25