C#通过Socket经代理发送HTTP POST请求收到乱码如何解决
问题原因
你收到§♥♥ ☻☻2这类乱码的核心原因是:你通过CONNECT方法连接的是目标站点的443 HTTPS端口,代理返回200代表TCP隧道打通后,后续传输的所有内容都是TLS加密的二进制数据,你直接发送明文HTTP请求、直接用ASCII解码收到的原始字节,拿到的是TLS握手帧的二进制内容,自然是乱码。
除此之外你的代码还有两个会导致异常的逻辑问题:
- TCP是面向流的协议,单次
Receive调用最多读取指定长度的字节,无法保证一次读到完整报文,你固定读256字节的写法,很容易出现读半包、漏读数据的问题 - 没有区分隧道建立阶段和HTTPS传输阶段的流量处理逻辑,把TLS密文当成明文HTTP解析
修复步骤
- 代理返回CONNECT 200响应后,不要直接在原始Socket上发送HTTP请求,先基于现有Socket创建SSL流,完成和目标站点的TLS握手
- 所有HTTP请求、响应的收发都走完成握手的SSL流,不要直接操作原始Socket传输应用层内容
- 调整接收逻辑,循环读取数据直到识别到HTTP响应头结束标记
\r\n\r\n,再根据响应头里的Content-Length或Transfer-Encoding字段读取完整响应体,不要单次读取固定长度就终止
修正后参考代码
using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Net.Sockets; public static void Post(string host, string proxyip, int proxyport, string httpRequest) { try { string connectRequest = $"CONNECT {host}:443 HTTP/1.1\r\nHost: {host}:443\r\n\r\n"; Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint proxy = new IPEndPoint(IPAddress.Parse(proxyip), proxyport); s.ReceiveTimeout = 5000; s.SendTimeout = 5000; s.Connect(proxy); // 发送CONNECT隧道建立请求 Byte[] connectReqBytes = Encoding.ASCII.GetBytes(connectRequest); s.Send(connectReqBytes); // 循环读取CONNECT响应直到拿到完整响应头 StringBuilder connectRespBuilder = new StringBuilder(); Byte[] recvBuffer = new Byte[1024]; while (true) { int recvLen = s.Receive(recvBuffer); connectRespBuilder.Append(Encoding.ASCII.GetString(recvBuffer, 0, recvLen)); string connectResp = connectRespBuilder.ToString(); if (connectResp.Contains("\r\n\r\n")) { if (!connectResp.StartsWith("HTTP/1.1 200")) { Console.WriteLine($"代理隧道建立失败: {connectResp}"); return; } break; } } Console.WriteLine("代理隧道建立成功,开始TLS握手"); // 基于现有Socket创建SSL流完成TLS握手 using SslStream sslStream = new SslStream(new NetworkStream(s, ownsSocket: true), false, (sender, cert, chain, errors) => true); sslStream.AuthenticateAsClient(host); // 通过SSL流发送构造好的HTTP POST请求 Byte[] reqBytes = Encoding.ASCII.GetBytes(httpRequest); sslStream.Write(reqBytes); sslStream.Flush(); // 读取HTTP响应 StringBuilder respBuilder = new StringBuilder(); recvBuffer = new Byte[1024]; int readLen; while ((readLen = sslStream.Read(recvBuffer, 0, recvBuffer.Length)) > 0) { respBuilder.Append(Encoding.UTF8.GetString(recvBuffer, 0, readLen)); // 简化逻辑:读到响应头结束标记先停止,生产环境需根据Content-Length/分块编码规则判断响应是否完整 if (respBuilder.ToString().Contains("\r\n\r\n")) { break; } } Console.WriteLine("收到响应:"); Console.WriteLine(respBuilder.ToString()); } catch (Exception e) { Console.WriteLine(e); } }
注意事项
- 示例代码里的证书验证回调直接返回
true是测试环境写法,生产环境请去掉该逻辑,正常校验服务端证书避免中间人攻击 - 示例里的响应读取逻辑做了简化,如果你需要读取完整响应(尤其是带大响应体的接口),需要解析响应头的
Content-Length字段,循环读取对应长度的内容;如果是Transfer-Encoding: chunked的分块响应,需要按照分块编码规则解析,不能读到\r\n\r\n就停止 - 构造POST请求的时候记得带上
Content-Length请求头,值和请求体的字节长度一致,否则服务端会一直等待请求体导致超时
内容的提问来源于stack exchange,提问作者Hikmet
相关产品推荐
相关产品推荐

