开发C# Torrent客户端时Peer握手异常问题求助
C# BitTorrent Peer握手异常问题的解决方案
核心问题分析
你的代码存在四个关键错误,直接导致握手响应异常:
- pstrlen字段格式错误:BitTorrent协议规定pstrlen是单个字节,表示后续协议字符串的长度(十进制19),但你将其定义为字符串
"19",转成字节后是两个字节,完全破坏了握手消息的起始格式,Peer无法正确解析请求。 - 保留字段生成冗余:用
Encoding.ASCII.GetBytes处理包含'\x00'的字符数组,虽然结果可能正确,但更高效安全的方式是直接创建8个0字节的数组,避免编码转换的潜在风险。 - 重复发送握手消息:收发逻辑中同时调用
stream.Write和listener.Client.Send,导致同一条握手消息被发送两次,Peer收到重复请求后会返回异常响应甚至断开连接。 - 响应处理方式错误:握手响应是二进制数据,其中info_hash和peer_id包含非ASCII字节,直接用
Encoding.ASCII.GetString转换会导致字节丢失或乱码,无法正确验证响应格式。
修正后的代码
握手消息生成类修正
public class PeerHandShake { // pstrlen是单个字节的数值19,而非字符串 public const byte ProtocolStringLength = 19; public const string ProtocolString = "BitTorrent protocol"; public byte[] InfoHash { get; set; } public string PeerId { get; set; } public PeerHandShake(string peerId, byte[] infoHash) { PeerId = peerId; InfoHash = infoHash; } // 严格遵循协议格式:<pstrlen><pstr><reserved><info_hash><peer_id> public byte[] GenerateHandShakeMessage() { var handshakeMessage = new List<byte>(); // 添加单个字节的pstrlen handshakeMessage.Add(ProtocolStringLength); // 添加协议字符串的ASCII字节 handshakeMessage.AddRange(Encoding.ASCII.GetBytes(ProtocolString)); // 8字节保留字段,全部为0 handshakeMessage.AddRange(new byte[8]); // 添加info_hash二进制字节 handshakeMessage.AddRange(InfoHash); // 添加peer_id的ASCII字节 handshakeMessage.AddRange(Encoding.ASCII.GetBytes(PeerId)); return handshakeMessage.ToArray(); } }
收发逻辑修正
public static void SendRequest(string endpoint, int port, byte[] message) { try { // 使用using自动释放资源 using var tcpClient = new TcpClient(); tcpClient.Connect(endpoint, port); using var stream = tcpClient.GetStream(); // 仅发送一次握手消息 stream.Write(message, 0, message.Length); // 握手响应固定长度为68字节(1+19+8+20+20) var responseBuffer = new byte[68]; int totalBytesRead = 0; // 确保读取完整的响应,避免部分读取导致的格式混乱 while (totalBytesRead < responseBuffer.Length) { int bytesRead = stream.Read(responseBuffer, totalBytesRead, responseBuffer.Length - totalBytesRead); if (bytesRead == 0) { throw new IOException("Peer closed connection prematurely"); } totalBytesRead += bytesRead; } // 验证响应的pstrlen是否正确 if (responseBuffer[0] != 19) { throw new InvalidDataException($"Invalid pstrlen: {responseBuffer[0]}"); } // 验证协议字符串是否匹配 string responseProtocol = Encoding.ASCII.GetString(responseBuffer, 1, 19); if (responseProtocol != ProtocolString) { throw new InvalidDataException($"Unsupported protocol: {responseProtocol}"); } // 提取并验证info_hash是否匹配 byte[] responseInfoHash = responseBuffer.Skip(28).Take(20).ToArray(); if (!responseInfoHash.SequenceEqual(message.Skip(28).Take(20).ToArray())) { throw new InvalidDataException("Info hash mismatch"); } // 提取Peer ID string responsePeerId = Encoding.ASCII.GetString(responseBuffer, 48, 20); Console.WriteLine($"Handshake succeeded! Peer ID: {responsePeerId}"); } catch (Exception e) { Console.WriteLine($"Handshake failed: {e.Message}"); } }
额外注意事项
- 确保Peer ID符合BitTorrent规范(通常是20字节的ASCII字符串,比如
"-UT3410-xxxxxxxxxxxx")。 - 使用
using语句自动释放TcpClient和Stream资源,避免连接泄漏。 - 读取响应时要循环读取,确保获取完整的68字节握手响应,因为
stream.Read可能只返回部分数据。
内容的提问来源于stack exchange,提问作者Roman Kostyk
相关产品推荐
相关产品推荐

