返回博客

通过代理的WebSocket:WS和WSS连接的完整代理指南

WebSocket连接与普通HTTP请求的工作方式不同,标准代理通常会中断它们。我们来探讨如何正确代理WS和WSS流量而不丢失连接。

📅2026年8月7日
```html

WebSocket不是普通的HTTP请求。在初始的“握手”(handshake)之后,连接保持打开状态,数据以连续流的方式双向传输。这就是为什么标准HTTP代理经常中断WS连接或根本无法处理它们。在本文中,我们将探讨如何正确代理WebSocket流量:哪些代理适合,如何配置它们,以及常见的错误。

WebSocket是如何工作的,以及为什么它对代理来说很复杂

要理解这个问题,需要了解其机制。WebSocket连接开始时是一个普通的HTTP请求——客户端发送头部 Upgrade: websocketConnection: Upgrade。服务器以101 Switching Protocols的代码响应——从这一刻起,连接不再是HTTP。它变成了一个持续的双向通道,数据以帧的形式传输。

仅能转发请求和响应的标准HTTP/1.1代理面临一个问题:它不知道在响应 101 之后该如何处理连接。许多代理服务器在此时简单地关闭连接或返回502 Bad Gateway错误。其他代理保持连接,但无法正确传递WebSocket帧,导致数据中断或失真。

以下是WebSocket与普通HTTP在代理方面的关键区别:

参数 HTTP WebSocket
连接类型 请求 → 响应 → 关闭 持续的,双向的
生命周期 秒(一个请求) 分钟,小时,天
数据发起者 仅客户端 客户端和服务器
握手后的协议 HTTP 自定义(RFC 6455)
端口 80, 443 80(WS),443(WSS)

正是由于握手后协议的变化,大多数简单的代理解决方案无法处理WebSocket。要么使用明确支持隧道化的代理,要么使用SOCKS5——一种在更低级别工作并且不解析流量内容的协议。

哪些类型的代理支持WebSocket

并非所有代理对WebSocket都同样有用。我们来分析每种类型:

代理类型 支持WS 机制 复杂性
HTTP代理 ⚠️ 部分支持 通过CONNECT隧道 中等
HTTPS代理 ✅ 是 CONNECT + TLS 中等
SOCKS4 ⚠️ 有限支持 无auth的TCP隧道
SOCKS5 ✅ 完全支持 透明的TCP/UDP
透明代理 ❌ 否 仅HTTP

总结:对于WebSocket任务,最佳选择是SOCKS5。该协议在传输层工作,简单地隧道化TCP连接,而不深入流量内容。它不关心内部是HTTP、WebSocket、SSH还是其他内容。HTTP代理也可以通过WS工作,但仅通过CONNECT方法——这里有一些细节,我们将在下面讨论。

CONNECT方法:HTTP代理如何隧道化WebSocket

HTTP方法 CONNECT 是一种特殊机制,允许HTTP代理创建到目标服务器的“盲隧道”。代理不分析隧道内的流量,而是简单地转发字节。这就是HTTPS通过HTTP代理的工作方式——也是代理WebSocket的方式。

过程如下:

  1. 客户端向代理发送请求:CONNECT example.com:443 HTTP/1.1
  2. 代理与 example.com:443 建立TCP连接
  3. 代理回复客户端:200 Connection Established
  4. 从这一刻起,代理只是将字节来回传递——不进行解析
  5. 客户端通过隧道直接与服务器执行TLS握手
  6. 然后——在TLS上进行WebSocket握手

关键限制:CONNECT方法通常仅允许用于443端口。如果您的WebSocket服务器在非标准端口(例如8080或9000)上运行,代理可能会拒绝连接。在这种情况下,SOCKS5更为合适——它没有端口限制。

还需要注意的是,一些企业HTTP代理(例如,标准配置的Squid)明确阻止某些端口的CONNECT方法或要求身份验证。如果您使用商业代理提供商,他们中的大多数都支持无条件的CONNECT。

# 通过curl的CONNECT请求示例(用于检查)
curl -v -x http://proxy_host:proxy_port \
  --proxytunnel \
  https://echo.websocket.org

# 如果代理支持CONNECT——您将看到:
# * CONNECT tunnel established, response 200

SOCKS5和WebSocket:为什么这是最佳选择

SOCKS5是一个在TCP/UDP连接级别的代理协议。与HTTP代理不同,SOCKS5对内部的应用协议一无所知。它只是创建客户端和目标服务器之间的隧道,仅此而已。这使得它在多个方面成为WebSocket的理想选择:

  • 没有协议限制:SOCKS5隧道化任何TCP流量,包括WS、WSS、SSH、FTP等。
  • 没有端口限制:与任何端口一起工作,不仅限于443或80
  • 协议切换时没有中断:代理不会“看到”从HTTP到WebSocket的过渡
  • 支持身份验证:SOCKS5支持用户名/密码,这对商业代理很方便
  • 支持UDP:如果您的应用使用WebRTC或UDP与WS一起使用——SOCKS5可以处理

几乎所有现代WebSocket库都支持SOCKS5,或者直接支持,或者通过附加包。下面我们将讨论Python和Node.js的具体示例。

💡 何时选择SOCKS5,何时选择HTTP CONNECT?

如果:非标准端口,需要UDP支持,想要最少的设置,请使用SOCKS5
如果:代理提供商不支持SOCKS5,或者您通过企业代理工作,请使用HTTP CONNECT

代码示例:通过Python的代理使用WebSocket

我们来看几个Python的场景。Python中最流行的WebSocket库是 websocketswebsocket-client

选项1:通过HTTP代理的websocket-client

import websocket

# HTTP代理设置
proxy_host = "proxy.example.com"
proxy_port = 8080
proxy_user = "username"
proxy_pass = "password"

ws = websocket.WebSocket()

ws.connect(
    "wss://echo.websocket.org",
    http_proxy_host=proxy_host,
    http_proxy_port=proxy_port,
    http_proxy_auth=(proxy_user, proxy_pass),
    proxy_type="http"  # 或 "socks5"
)

ws.send("Hello, WebSocket!")
result = ws.recv()
print(f"收到:{result}")

ws.close()

选项2:通过SOCKS5的websocket-client

import websocket

# SOCKS5需要安装包:pip install PySocks
ws = websocket.WebSocket()

ws.connect(
    "wss://echo.websocket.org",
    http_proxy_host="socks5_proxy.example.com",
    http_proxy_port=1080,
    http_proxy_auth=("username", "password"),
    proxy_type="socks5"
)

ws.send("测试消息")
print(ws.recv())
ws.close()

选项3:通过SOCKS5的websockets库(asyncio)

websockets(asyncio)库没有内置的代理支持,因此我们使用 python-socks 来创建隧道:

# pip install websockets python-socks[asyncio]
import asyncio
import websockets
from python_socks.async_.asyncio import Proxy

async def connect_via_socks5():
    proxy = Proxy.from_url("socks5://username:[email protected]:1080")
    
    # 通过代理创建TCP连接
    sock = await proxy.connect(
        dest_host="echo.websocket.org",
        dest_port=443
    )
    
    # 将套接字传递给websockets
    async with websockets.connect(
        "wss://echo.websocket.org",
        sock=sock
    ) as ws:
        await ws.send("通过SOCKS5发送问候!")
        response = await ws.recv()
        print(f"响应:{response}")

asyncio.run(connect_via_socks5())

选项4:通过PySocks的全局补丁

如果您希望将整个Python应用程序的流量通过SOCKS5传递而无需更改每个调用——请使用 socks.setdefaultproxy()

# pip install PySocks
import socks
import socket
import websocket

# 全局补丁socket
socks.set_default_proxy(
    socks.SOCKS5,
    "proxy.example.com",
    1080,
    username="user",
    password="pass"
)
socket.socket = socks.socksocket

# 现在所有连接都通过SOCKS5
ws = websocket.WebSocket()
ws.connect("wss://echo.websocket.org")
ws.send("全局SOCKS5代理!")
print(ws.recv())
ws.close()

代码示例:通过Node.js的代理使用WebSocket

在Node.js生态系统中,最流行的WebSocket库是 ws。通过HTTP/SOCKS5进行代理使用 https-proxy-agentsocks-proxy-agent 包。

选项1:通过HTTP CONNECT代理的WSS

// npm install ws https-proxy-agent
const WebSocket = require('ws');
const { HttpsProxyAgent } = require('https-proxy-agent');

const proxyUrl = 'http://username:[email protected]:8080';
const agent = new HttpsProxyAgent(proxyUrl);

const ws = new WebSocket('wss://echo.websocket.org', { agent });

ws.on('open', () => {
  console.log('通过HTTP代理建立了连接');
  ws.send('来自Node.js的问候!');
});

ws.on('message', (data) => {
  console.log(`收到:${data}`);
  ws.close();
});

ws.on('error', (err) => {
  console.error('错误:', err.message);
});

选项2:通过SOCKS5的WSS

// npm install ws socks-proxy-agent
const WebSocket = require('ws');
const { SocksProxyAgent } = require('socks-proxy-agent');

const proxyUrl = 'socks5://username:[email protected]:1080';
const agent = new SocksProxyAgent(proxyUrl);

const ws = new WebSocket('wss://echo.websocket.org', { agent });

ws.on('open', () => {
  console.log('通过SOCKS5建立了连接');
  ws.send(JSON.stringify({ type: 'ping', data: 'test' }));
});

ws.on('message', (data) => {
  console.log('响应:', data.toString());
});

ws.on('close', (code, reason) => {
  console.log(`关闭:${code} - ${reason}`);
});

选项3:通过HTTP代理手动进行WS(无TLS)

对于通过HTTP代理的未加密WS(端口80),需要手动发送CONNECT请求,因为标准代理通常仅处理HTTPS:

const net = require('net');
const WebSocket = require('ws');

function createTunnel(proxyHost, proxyPort, targetHost, targetPort) {
  return new Promise((resolve, reject) => {
    const socket = net.connect(proxyPort, proxyHost, () => {
      const connectReq = 
        `CONNECT ${targetHost}:${targetPort} HTTP/1.1\r\n` +
        `Host: ${targetHost}:${targetPort}\r\n` +
        `Proxy-Authorization: Basic ${Buffer.from('user:pass').toString('base64')}\r\n` +
        `\r\n`;
      
      socket.write(connectReq);
    });

    socket.once('data', (data) => {
      if (data.toString().includes('200')) {
        resolve(socket);
      } else {
        reject(new Error(`代理拒绝CONNECT:${data.toString()}`));
      }
    });

    socket.on('error', reject);
  });
}

async function main() {
  const socket = await createTunnel(
    'proxy.example.com', 8080,
    'echo.websocket.org', 80
  );

  const ws = new WebSocket('ws://echo.websocket.org', { socket });
  
  ws.on('open', () => {
    ws.send('手动CONNECT隧道!');
  });

  ws.on('message', (data) => {
    console.log('收到:', data.toString());
    ws.close();
  });
}

main().catch(console.error);

WSS(WebSocket Secure):使用TLS代理的特点

WSS是TLS上的WebSocket(类似于HTTP的HTTPS)。通过SOCKS5或HTTP CONNECT代理WSS时,有一个重要的细节:TLS加密是在客户端和最终服务器之间建立的,而不是在客户端和代理之间。这意味着:

  • 代理服务器无法看到 WSS流量的内容——仅能看到目标的IP地址和端口
  • 服务器证书由客户端直接验证
  • 代理无法“替换”或“拦截”数据,除非安装自己的CA证书

从安全的角度来看,这是个好消息。但在设置时也有实际的细节:

通过代理验证证书

有时在使用企业代理(进行SSL检查)时,您可能会收到证书验证错误。在这种情况下,代理会用自己的证书替换服务器证书。要在这种环境中工作,需要将代理的CA证书添加到受信任的证书中:

# Python:传递企业代理的CA证书
import ssl
import websocket

ssl_context = ssl.create_default_context()
ssl_context.load_verify_locations("/path/to/corporate-ca.crt")

ws = websocket.WebSocket(sslopt={"context": ssl_context})
ws.connect(
    "wss://internal.example.com",
    http_proxy_host="corp-proxy.company.com",
    http_proxy_port=8080,
    proxy_type="http"
)

# 在测试环境中(不用于生产环境!)可以禁用验证:
ws_test = websocket.WebSocket(sslopt={"cert_reqs": ssl.CERT_NONE})
ws_test.connect("wss://test.example.com", http_proxy_host="proxy", http_proxy_port=8080)

⚠️ 重要

禁用TLS证书验证(CERT_NONE)仅在测试环境中是可接受的。在生产环境中,这会导致MITM(中间人攻击)漏洞。

通过代理的SNI(服务器名称指示)

使用SOCKS5时,客户端可以自行解析DNS或将其委托给代理。socks5h(带主机名解析的SOCKS5)意味着DNS请求在代理服务器上执行。这对于WSS很重要,因为TLS握手中的SNI头必须与主机名匹配:

# socks5  — DNS在本地(客户端)解析
# socks5h — DNS在代理服务器上解析(推荐用于匿名性)

from python_socks.async_.asyncio import Proxy

# 通过代理解析DNS(推荐):
proxy = Proxy.from_url("socks5h://user:[email protected]:1080")

# 本地DNS:
proxy = Proxy.from_url("socks5://user:[email protected]:1080")

常见错误及其解决方法

我们收集了在代理WebSocket时最常见的问题及其解决方案:

错误1:407代理身份验证失败

代理要求身份验证,但您没有提供凭据或提供了错误的凭据。

# ❌ 错误 — 没有授权
ws.connect("wss://example.com", http_proxy_host="proxy.example.com", http_proxy_port=8080)

# ✅ 正确 — 提供用户名和密码
ws.connect(
    "wss://example.com",
    http_proxy_host="proxy.example.com",
    http_proxy_port=8080,
    http_proxy_auth=("username", "password")
)

错误2:Connection reset by peer / 502 Bad Gateway

代理不支持WebSocket或CONNECT方法。解决方案:切换到SOCKS5或检查您的提供商是否支持WebSocket流量。

错误3:连接在30-60秒后中断

许多代理服务器在超时后关闭“非活动”TCP连接。如果没有数据交换,WebSocket连接可能看起来是非活动的。解决方案是启用ping/pong:

# Python — 每20秒启用keepalive ping
import websocket
import threading

def run():
    ws = websocket.WebSocketApp(
        "wss://echo.websocket.org",
        on_message=lambda ws, msg: print(msg),
        on_error=lambda ws, err: print(f"错误:{err}"),
        on_close=lambda ws, c, m: print("关闭")
    )
    ws.run_forever(
        ping_interval=20,   # 每20秒发送ping
        ping_timeout=10,    # 最多等待10秒的pong
        http_proxy_host="proxy.example.com",
        http_proxy_port=8080,
        proxy_type="socks5"
    )

thread = threading.Thread(target=run)
thread.start()

错误4:SSL: CERTIFICATE_VERIFY_FAILED

通常在使用企业代理进行SSL检查时发生。解决方案:将代理的CA证书添加到受信任的证书中(见上文)或使用SOCKS5而不是HTTP代理——SOCKS5不会进行SSL检查。

错误5:握手状态403 Forbidden

目标服务器阻止了连接。原因:代理的IP被列入黑名单,缺少必要的头部(Origin,User-Agent),或服务器阻止来自数据中心的流量。解决方案:使用 住宅代理,这些代理具有真实的家庭用户IP——它们更难被阻止。

错误6:[Errno 111] Connection refused

代理服务器不可用:主机/端口错误,或代理未启动。在测试WebSocket之前,请检查连接数据和代理的可用性。

选择哪种类型的代理用于WebSocket任务

选择代理类型取决于具体任务。以下是实用指南:

任务 推荐类型 原因
通过WS解析(交易所、金融数据) 数据中心代理 高速,低延迟,稳定连接
绕过WebSocket服务的封锁 住宅代理 真实IP,最低的封锁风险
使用WS的移动应用(与移动API交互) 移动代理 移动运营商的IP——服务的高信任度
对WS服务器进行负载测试 数据中心代理 便宜,快速,多个并发连接
WS地理定位测试 住宅代理 丰富的国家和城市选择

WebSocket的代理重要参数

在选择WebSocket任务的代理提供商时,请注意以下参数:

  • 支持SOCKS5:确保提供商提供SOCKS5,而不仅仅是HTTP
  • 会话持续时间(session duration):对于WebSocket,重要的是“粘性”会话——一个IP长时间使用。轮换代理会在每次更换IP时中断连接
  • 连接超时:代理应支持长寿命的TCP连接(从几分钟到几小时)
  • 带宽:对于流式WebSocket(视频,交易数据),高带宽且无限制非常重要
  • 延迟(latency):对于金融应用和交易机器人,最低延迟至关重要——选择靠近目标服务的代理

💡 检查WebSocket代理提供商的支持

在购买WebSocket任务的代理之前,通过免费的回声服务器测试它们: wss://echo.websocket.orgwss://ws.postman-echo.com/raw。 如果连接建立并返回消息——代理与WebSocket正常工作。

为WebSocket设置粘性会话

大多数住宅代理提供商默认使用IP轮换。对于WebSocket来说,这是不可接受的——每次更换IP都会导致连接中断。确保您使用的是粘性会话模式(固定IP)。通常通过代理的特殊URL格式来实现:

# 粘性会话格式示例(取决于提供商):
# 轮换(不适合WebSocket):
socks5://user:[email protected]:1080

# 粘性会话(适合WebSocket):
socks5://user-session-abc123:[email protected]:1080

# 或通过国家和会话参数:
socks5://user-country-us-session-12345:[email protected]:1080

结论

WebSocket不仅仅是“长连接的HTTP”。它是一个独立的协议,在代理时需要特别的处理。本文的主要结论:

  • SOCKS5是WebSocket的最佳选择:在传输层工作,不解析协议,支持任何端口
  • 通过CONNECT的HTTP代理也可以工作,但在端口和SSL检查方面存在限制
  • 粘性会话是必需的:轮换代理在每次更换IP时会中断WebSocket连接
  • Ping/pong keepalive是防止代理超时中断连接的必要措施
  • 通过代理的WSS是安全的:TLS加密直接在客户端和服务器之间建立,代理无法看到内容

如果您正在开发一个通过代理使用WebSocket的应用程序——从SOCKS5和粘性会话开始。这将为您节省数小时的调试时间。对于需要高速和稳定连接的任务(交易机器人、数据流),低延迟的 数据中心代理 是绝佳选择。如果目标服务积极阻止数据中心IP——考虑使用 住宅代理:它们具有真实的家庭用户IP,即使在长时间的WebSocket会话中也更难被阻止。

```