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

开发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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 11:20:27