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

Python Socket IPv6转IPv4转发工具连接延迟及请求丢失问题求助

解决方案:IPv6/IPv4转发程序异常排查与修复

我来帮你排查并解决这个IPv6/IPv4转发程序的问题,先拆解下两个异常的原因,再给出修复后的代码和详细说明:

问题1:部分请求未被捕获(如style.css加载卡住)

核心原因:你的程序每个TCP连接仅处理一次HTTP请求响应,但现代浏览器默认使用HTTP/1.1的持久连接(Keep-Alive)——也就是同一个TCP连接会连续发送多个请求(比如页面HTML请求后,紧接着发送style.css、图片等资源请求)。而你的handle_request函数在处理完第一个响应后就关闭了IPv4侧的socket并return,导致后续请求在同一个连接中无法被处理,浏览器一直等待响应,控制台也不会有新连接记录(因为没新建连接)。

问题2:Web界面操作延迟严重

延迟主要来自三个方面:

  1. 请求接收不完整:你只用一次return_address.recv(2048)获取请求,当HTTP请求长度超过2048字节时,会导致请求截断,程序需要等待超时或出错,造成延迟。
  2. 冗余的发送循环:你在发送响应时,每次recv(2048)后又手动循环分块发送,但socket.send本身会尽可能发送数据(除非缓冲区满),这个循环反而增加了额外开销。
  3. Host头替换不健壮:你的正则\\[[^]]*]:7250只能匹配带方括号的IPv6地址+端口格式,若请求中的Host头省略端口或格式稍有变化,替换就会失败,导致后端服务器无法正确处理请求。

修复后的完整代码

import socket
import re
from threading import Thread

# 配置参数,根据实际情况修改
IPV6 = "::"  # 你的IPv6监听地址,::表示监听所有IPv6接口
TARGET_IPV4 = "127.0.0.1"
TARGET_PORT = 7245
LISTEN_PORT = 7250

def handle_request(connected_socket):
    connected_socket.settimeout(10)  # 设置socket超时,避免无限阻塞
    ipv4side = None
    try:
        # 连接到本地IPv4服务器,设置超时
        ipv4side = socket.create_connection((TARGET_IPV4, TARGET_PORT), timeout=10)
        ipv4side.settimeout(10)

        # 完整接收HTTP请求(处理持久连接与请求体)
        request_data = b""
        while True:
            chunk = connected_socket.recv(4096)  # 用更大的缓冲区减少循环次数
            if not chunk:
                break
            request_data += chunk
            # 判断请求头是否结束(HTTP请求头以\r\n\r\n为标志)
            if b"\r\n\r\n" in request_data:
                # 检查是否有Content-Length,确保接收完整的请求体
                if b"Content-Length:" in request_data:
                    content_length_match = re.search(rb"Content-Length: (\d+)", request_data)
                    if content_length_match:
                        content_length = int(content_length_match.group(1))
                        body_start = request_data.find(b"\r\n\r\n") + 4
                        # 继续接收直到请求体完整
                        while len(request_data) - body_start < content_length:
                            chunk = connected_socket.recv(4096)
                            if not chunk:
                                break
                            request_data += chunk
                break

        if not request_data:
            return

        # 健壮替换请求中的Host头与请求行地址
        request_str = request_data.decode('latin-1')  # 用latin-1兼容所有字节,避免utf-8解码错误
        # 替换Host头字段
        request_str = re.sub(r"Host: .*\r\n", f"Host: {TARGET_IPV4}:{TARGET_PORT}\r\n", request_str, flags=re.IGNORECASE)
        # 替换请求行中的绝对URL(如果存在)
        request_str = re.sub(r"GET http://\[[^\]]*]:%d/" % LISTEN_PORT, f"GET http://{TARGET_IPV4}:{TARGET_PORT}/", request_str)
        request_str = re.sub(r"POST http://\[[^\]]*]:%d/" % LISTEN_PORT, f"POST http://{TARGET_IPV4}:{TARGET_PORT}/", request_str)
        request_data = request_str.encode('latin-1')

        # 发送完整请求到IPv4服务器,sendall自动处理所有数据
        ipv4side.sendall(request_data)

        # 转发响应到IPv6客户端,支持持久连接
        while True:
            response_chunk = ipv4side.recv(4096)
            if not response_chunk:
                break
            # 发送响应到客户端
            connected_socket.sendall(response_chunk)
            # 若响应包含Connection: close,则处理完后关闭连接
            if b"Connection: close\r\n" in response_chunk.lower():
                break

    except socket.timeout:
        print("Socket超时,终止请求处理")
    except Exception as e:
        print(f"处理请求时出错: {str(e)}")
    finally:
        # 确保所有socket都被关闭
        if ipv4side:
            ipv4side.close()
        connected_socket.close()

def main():
    ipv6side = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
    ipv6side.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 允许端口复用,避免重启时端口占用
    ipv6side.bind((IPV6, LISTEN_PORT))
    ipv6side.listen(50)  # 增大监听队列,处理更多并发连接
    ipv6side.settimeout(60)  # 调整超时时间,减少不必要的打印
    print(f"IPv6代理已启动,监听地址: [{IPV6}]:{LISTEN_PORT}")
    while True:
        try:
            connected_socket, addr = ipv6side.accept()
            print(f"新连接来自: [{addr[0]}]:{addr[1]}")
            # 使用守护线程,程序退出时自动结束所有线程
            Thread(target=handle_request, args=(connected_socket,), daemon=True).start()
        except socket.timeout:
            continue  # 超时后继续等待新连接,无需打印
        except KeyboardInterrupt:
            print("正在关闭代理...")
            break
    ipv6side.close()

if __name__ == "__main__":
    main()

关键修改说明

  1. 支持HTTP持久连接:

    • 处理响应时循环接收并转发数据,直到IPv4服务器关闭连接或响应包含Connection: close头,确保同一个TCP连接能处理多个请求。
    • 完整接收HTTP请求(包括请求头和请求体),避免请求截断导致的异常。
  2. 性能优化:

    • 使用recv(4096)增大缓冲区,减少循环次数提升效率。
    • 用sendall()替代手动循环发送,它会自动处理所有数据的发送,简化代码并减少开销。
  3. 健壮的请求修改:

    • 直接匹配替换Host:头字段,不管原Host是IPv6地址还是其他格式,确保后端服务器正确识别请求。
    • 替换请求行中的绝对URL,避免后端收到错误的目标地址。
  4. 稳定性与错误处理:

    • 给所有socket设置超时时间,避免无限阻塞导致的延迟或僵死。
    • 使用守护线程,确保程序退出时所有线程自动结束。
    • 添加SO_REUSEADDR选项,避免程序重启时出现端口占用错误。
    • 增大监听队列长度,处理更多并发连接。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 20:17:46