/// <summary> /// 尝试将一个端点(对应一个客户端)加入连接列表,并向该端点发送一条消息。 /// </summary> /// <param name="endPoint">目标端点。</param> /// <param name="message">要发送的消息。</param> /// <returns>一个 <see cref="System.Threading.WaitHandle"/>,通过此对象可以对发送操作进行等待。</returns> private WaitHandle SendMessage(KEndPoint endPoint, KMessage message) { string tmpLog; tmpLog = "KClient::SendMessage(KEndPoint, KMesage)"; tmpLog += Environment.NewLine + "消息代码: " + message.Header.Code.ToString(); tmpLog += Environment.NewLine + "消息唯一编码: " + message.Header.MessageID.ToString(); tmpLog += Environment.NewLine + "消息来源: " + message.Header.SourceEndPoint.ToString(); Logger.Log(tmpLog); AutoResetEvent ev = new AutoResetEvent(false); Thread thread = new Thread(delegate() { try { TcpClient tcpClient = new TcpClient(); var remoteEndPoint = endPoint.GetEndPoint(); Logger.Log("KClient::SendMessage(KEndPoint, KMessage) 工作线程。"); var connListItem = AddToConnectionList(endPoint); if (connListItem == null) { return; } Logger.Log("尝试连接端点: " + remoteEndPoint.ToString()); var iar = tcpClient.BeginConnect(remoteEndPoint.Address, remoteEndPoint.Port, null, null); if (!iar.IsCompleted) { iar.AsyncWaitHandle.WaitOne(SendTimeout); } if (iar.IsCompleted) { Logger.Log("连接成功。"); // 未超时 tcpClient.EndConnect(iar); connListItem.ResetTimesTried(); var bs = tcpClient.GetStream(); { Logger.Log("发送消息。"); bs.WriteMessage(message); } tcpClient.Close(); } else { Logger.Log("连接失败。"); // 设置/移除出列表 connListItem.IncreaseTimesTriedAndCheck(); } ev.Set(); ev.Dispose(); } catch (Exception ex) { Logger.Log(ex.Message + Environment.NewLine + ex.StackTrace); } }); thread.IsBackground = true; thread.Start(); return(ev); }
/// <summary> /// 处理 GotPeer 消息。 /// </summary> /// <param name="args">处理时所需要的信息。</param> private void HandleGotPeer(HandleMessageArgs args) { Logger.Log("收到消息: 在其他客户端上找到种子。"); ushort bitTorrentClientPort; InfoHash infoHash; Logger.Log("解码用户及 infohash 数据。"); MessageFactory.GetPeerMessageContent(args.Message, out infoHash, out bitTorrentClientPort); Logger.Log("Infohash: " + infoHash.ToHexString() + Environment.NewLine + "用户端口: " + bitTorrentClientPort.ToString()); List <Peer> peerList; if (!TrackerServer.Seeds.TryGetValue(infoHash, out peerList)) { // 这是本客户端发出的信息,那么本客户端就要接收并处理 peerList = new List <Peer>(8); lock (TrackerServer.Seeds) { TrackerServer.Seeds.Add(infoHash, peerList); } } { Logger.Log("处理GOTPEER。添加对方地址。"); KEndPoint ep = args.Message.Header.SourceEndPoint; ep.SetPort(bitTorrentClientPort); Peer peer = Peer.Create(ep); if (!peerList.Contains(peer)) { lock (peerList) { peerList.Add(peer); } } } }
/// <summary> /// 处理 PeerExitNetwork 消息。 /// </summary> /// <param name="args">处理时所需要的信息。</param> private void HandlePeerExitNetwork(HandleMessageArgs args) { Logger.Log("收到消息: 用户退出。"); ushort bitTorrentClientPort; InfoHash infoHash; Logger.Log("解码用户及 infohash 数据。"); MessageFactory.GetPeerMessageContent(args.Message, out infoHash, out bitTorrentClientPort); Logger.Log("Infohash: " + infoHash.ToHexString() + Environment.NewLine + "用户端口: " + bitTorrentClientPort.ToString()); List <Peer> peerList; if (TrackerServer.Seeds.TryGetValue(infoHash, out peerList)) { Logger.Log("在本机发现该种子。移出列表。"); KEndPoint ep = args.Message.Header.SourceEndPoint; ep.SetPort(bitTorrentClientPort); Peer peer = Peer.Create(ep); lock (peerList) { peerList.Remove(peer); EventHelper.RaiseEvent(ConnectionListChanged, this, EventArgs.Empty); } } Logger.Log("转发消息。"); // 转发 BroadcastMessage(args.Message); }
/// <summary> /// 生成一个 ReportAlive 消息。 /// </summary> /// <param name="localEP">消息源的端点。</param> /// <returns>创建的 ReportAlive 消息。</returns> public static KMessage ReportAlive(KEndPoint localEP) { var message = KMessage.CreateEmptyMessage(); message.Header.Code = KMessageCode.ReportAlive; message.Header.SourceEndPoint = localEP; message.Header.MessageID = GetMessageHash(); return(message); }
/// <summary> /// 生成一个 ClientExitNetwork 消息。 /// </summary> /// <param name="localEP">消息源的端点。</param> /// <returns>创建的 ClientExitNetwork 消息。</returns> public static KMessage ClientExitNetwork(KEndPoint localEP) { var message = KMessage.CreateEmptyMessage(); message.Header.Code = KMessageCode.ClientExitNetwork; message.Header.SourceEndPoint = localEP; message.Header.MessageID = GetMessageHash(); return(message); }
/// <summary> /// 设置本客户端的端点为新的端点。用于自我端点确认。 /// </summary> /// <param name="ep">要被设置为的端点。</param> /// <exception cref="System.ArgumentNullException">ep 为 null 时发生。</exception> private void SetLocalEndPoint(IPEndPoint ep) { if (ep == null) { throw new ArgumentNullException("ep"); } _localEndPoint = ep; _localKEndPoint = KEndPoint.FromEndPoint(_localEndPoint); }
/// <summary> /// 生成一个 GotPeer 消息。 /// </summary> /// <param name="localEP">消息源的端点。</param> /// <param name="infoHash">用户所持有的 InfoHash。</param> /// <param name="bitTorrentClientPort">用户所监听的端口(即 BitTorrent 客户端的监听端口)。</param> /// <returns>创建的 GotPeer 消息。</returns> public static KMessage GotPeer(KEndPoint localEP, InfoHash infoHash, ushort bitTorrentClientPort) { var message = KMessage.CreateEmptyMessage(); message.Header.Code = KMessageCode.GotPeer; message.Header.SourceEndPoint = localEP; message.Header.MessageID = GetMessageHash(); BEncodedDictionary data = new BEncodedDictionary(); data.Add("infohash", infoHash.ToByteArray()); data.Add("bt client port", bitTorrentClientPort); message.Content = KMessageContent.FromByteArray(data.Encode()); return(message); }
/// <summary> /// 生成一个 ClientEnterNetwork 消息。 /// </summary> /// <param name="localEP">消息源的端点。</param> /// <param name="realPort">非零表示这是接入点要连接接入点,该端口是本机正在监听的端口;零表示只是普通用户连接接入点。</param> /// <returns>创建的 ClientEnterNetwork 消息。</returns> public static KMessage ClientEnterNetwork(KEndPoint localEP, ushort realPort) { var message = KMessage.CreateEmptyMessage(); message.Header.Code = KMessageCode.ClientEnterNetwork; message.Header.SourceEndPoint = localEP; message.Header.MessageID = GetMessageHash(); BEncodedDictionary data = new BEncodedDictionary(); data.Add("message handled", 0); data.Add("real port", realPort); message.Content = KMessageContent.FromByteArray(data.Encode()); return(message); }
/// <summary> /// 从一个字节数组中读取 <see cref="Kei.KNetwork.KMessageHeader"/> 所需的信息,并创建相应的 <see cref="Kei.KNetwork.KMessageHeader"/>。 /// </summary> /// <param name="data">包含所需信息的字节数组。注意长度必须等于某个版本的 <see cref="Kei.KNetwork.KMessageHeader"/> 的长度。</param> /// <returns>创建的 <see cref="Kei.KNetwork.KMessageHeader"/>。</returns> public static KMessageHeader FromByteArray(byte[] data) { KMessageHeader header; // +4 header.HeaderLength = BitConverter.ToUInt32(data, 0); // +2 header.HeaderVersion = BitConverter.ToUInt16(data, 4); // +8 header.MessageID = BitConverter.ToUInt64(data, 6); // +4 header.Code = (KMessageCode)BitConverter.ToInt32(data, 14); // +? header.SourceEndPoint = KEndPoint.FromByteArray(data.Skip(18).Take(6).ToArray()); return(header); }
/// <summary> /// 判断一个 List<ConnectionListItem> 内是否含有以指定 <see cref="Kei.KEndPoint"/> 区分的 <see cref="Kei.KNetwork.ConnectionListItem"/>。 /// </summary> /// <param name="list">要判断的 List<ConnectionListItem>。</param> /// <param name="endPoint">作为指定键的 <see cref="Kei.KEndPoint"/>。</param> /// <returns>一个 <see cref="System.Boolean"/>,指示是否找到了符合条件的 <see cref="Kei.KNetwork.ConnectionListItem"/>。</returns> public static bool Contains(this List <ConnectionListItem> list, KEndPoint endPoint) { if (list == null) { return(false); } bool contains = false; lock (list) { foreach (var item in list) { if (item.ClientLocation.Equals(endPoint)) { contains = true; break; } } } return(contains); }
/// <summary> /// 将一个端点(对应一个客户端)添加至连接列表。如果列表中已有此端点,则返回对应的连接列表项。 /// <para>注意,本方法执行中会为 ConnectionList 加锁。</para> /// </summary> /// <param name="endPoint">要添加的客户端的端点。</param> /// <returns>一个 <see cref="Kei.KNetwork.ConnectionListItem"/>,表示添加或者找到的连接列表项。</returns> private ConnectionListItem AddToConnectionList(KEndPoint endPoint) { if (endPoint.Equals(LocalKEndPoint)) { Logger.Log("KClient::AddToConnectionList(KEndPoint): 不能加入自己"); return(null); } Logger.Log("KClient::AddToConnectionList(KEndPoint)" + Environment.NewLine + "待加入的端点: " + endPoint.ToString()); int index; var connItem = ConnectionList.FindConnectionListItem(endPoint, out index); if (connItem == null) { Logger.Log("在列表中未发现,即将加入。"); connItem = new ConnectionListItem(endPoint); lock (ConnectionList) { ConnectionList.Add(connItem); EventHelper.RaiseEvent(ConnectionListChanged, this, EventArgs.Empty); } } return(connItem); }
/// <summary> /// 处理 ClientExitNetwork 消息。 /// </summary> /// <param name="args">处理时所需要的信息。</param> private void HandleClientExitNetwork(HandleMessageArgs args) { Logger.Log("收到消息: 客户端离开分布网络。"); KEndPoint remoteEndPoint = args.Message.Header.SourceEndPoint; // 只是简单的移除 int clItemIndex; ConnectionListItem item = ConnectionList.FindConnectionListItem(remoteEndPoint, out clItemIndex); if (item != null) { Logger.Log("找到项目并移除。"); lock (ConnectionList) { // 一样,应该不用担心前面的项被移动到后面去导致索引错误的事情吧 ConnectionList.RemoveAt(clItemIndex); EventHelper.RaiseEvent(ConnectionListChanged, this, EventArgs.Empty); } } Logger.Log("转发消息。"); // 转发 BroadcastMessage(args.Message); }
/// <summary> /// 处理 PeerEnterNetwork 消息。 /// </summary> /// <param name="args">处理时所需要的信息。</param> private void HandlePeerEnterNetwork(HandleMessageArgs args) { Logger.Log("收到消息: 用户加入。"); ushort bitTorrentClientPort; InfoHash infoHash; Logger.Log("解码用户及 infohash 数据。"); MessageFactory.GetPeerMessageContent(args.Message, out infoHash, out bitTorrentClientPort); Logger.Log("Infohash: " + infoHash.ToHexString() + Environment.NewLine + "用户端口: " + bitTorrentClientPort.ToString()); List <Peer> peerList; if (TrackerServer.Seeds.TryGetValue(infoHash, out peerList)) { Logger.Log("在本机发现该种子。加入列表。"); KEndPoint remoteClientEP = args.Message.Header.SourceEndPoint; KEndPoint remoteBTEP = remoteClientEP; remoteBTEP.SetPort(bitTorrentClientPort); Peer peer = Peer.Create(remoteBTEP); if (!peerList.Contains(peer)) { lock (peerList) { peerList.Add(peer); } } // 同时报告信息源,我这里有种子 Logger.Log("报告消息源在这里找到种子。"); // 此时 ep 的 Port 已经被修改,所以不能直接向 ep 所表示的端点发送,之前写错了…… // 所以后面索性改了名称,remoteClientEP 和 remoteBTEP SendMessage(remoteClientEP, MessageFactory.GotPeer(LocalKEndPoint, infoHash, (ushort)TrackerServer.Myself.EndPoint.GetPortNumber())); } Logger.Log("转发消息。"); // 转发 BroadcastMessage(args.Message); }
/// <summary> /// 使用指定的目标端点、项状态和已尝试次数创建一个新的 ConnectionListItem。 /// </summary> /// <param name="clientLocation">指定的目标端点。</param> /// <param name="state">指定的项状态。</param> /// <param name="timesTried">指定的已尝试次数。</param> private ConnectionListItem(KEndPoint clientLocation, ConnectionState state, uint timesTried) { _clientLocation = clientLocation; _state = state; _timesTried = timesTried; }
/// <summary> /// 在指定的 List<ConnectionListItem> 中寻找以指定 <see cref="Kei.KEndPoint"/> 区分的 <see cref="Kei.KNetwork.ConnectionListItem"/>,并返回查找结果。 /// </summary> /// <param name="list">要在其中查找的 List<ConnectionListItem>。</param> /// <param name="endPoint">作为指定键的 <see cref="Kei.KEndPoint"/>。</param> /// <param name="index">输出一个 <see cref="System.Int32"/>。如果找到了,这个值是找到的项的索引;如果未找到,则该值无效。</param> /// <returns>一个 <see cref="Kei.KNetwork.ConnectionListItem"/>,指示找到的项;或者 null,表示没找到。</returns> public static ConnectionListItem FindConnectionListItem(this List <ConnectionListItem> list, KEndPoint endPoint, out int index) { if (list == null) { index = 0; return(null); } ConnectionListItem cli = null; int i = 0; index = 0; lock (list) { foreach (var item in list) { if (item.ClientLocation.Equals(endPoint)) { index = i; cli = item; break; } i++; } } return(cli); }
/// <summary> /// 消息处理的核心分发过程。 /// </summary> /// <param name="args">处理时需要的信息。</param> private void HandleMessage(HandleMessageArgs args) { Logger.Log("KClient::HandleMessage() from " + args.Message.Header.SourceEndPoint.ToString() + " with code " + args.Message.Header.Code.ToString()); // 确保不是自己发出的消息 if (!args.Message.Header.SourceEndPoint.Equals(_localKEndPoint)) { KHandledMessage handledMessage; int handledMessageIndex; handledMessage = HandledMessages.FindHandledMessage(args.Message, out handledMessageIndex); string tmpLog; tmpLog = "消息代码: " + args.Message.Header.Code.ToString(); tmpLog += Environment.NewLine + "消息唯一编码: " + args.Message.Header.MessageID.ToString(); tmpLog += Environment.NewLine + "消息来源: " + args.Message.Header.SourceEndPoint.ToString(); Logger.Log(tmpLog); // 若未处理过该消息 if (handledMessage == null) { Logger.Log("未处理过该消息。"); switch (args.Message.Header.Code) { case KMessageCode.ReportAlive: if (!args.Message.Header.SourceEndPoint.AddressEquals(IPAddress.Loopback)) { HandleReportAlive(args); AddToConnectionList(args.Message.Header.SourceEndPoint); } break; case KMessageCode.ClientEnterNetwork: HandleClientEnterNetwork(args); if (args.RealPort == 0) { // 这条消息是用来连接到接入点的,此时对方还未初始化完毕,所以不用判断源端点的事情 AddToConnectionList(KEndPoint.FromEndPoint(args.EndPoint)); } else { // 这是接入点连接到接入点的请求 // 此时地址是真实的,监听端口由 args.RealPort 反映 var kep = KEndPoint.FromEndPoint(args.EndPoint); kep.SetPort(args.RealPort); AddToConnectionList(kep); } break; case KMessageCode.ClientExitNetwork: if (!args.Message.Header.SourceEndPoint.AddressEquals(IPAddress.Loopback)) { HandleClientExitNetwork(args); // 既然退出了为什么还要保留这个项 //AddToConnectionList(args.Message.Header.SourceEndPoint); } break; case KMessageCode.PeerEnterNetwork: if (!args.Message.Header.SourceEndPoint.AddressEquals(IPAddress.Loopback)) { HandlePeerEnterNetwork(args); AddToConnectionList(args.Message.Header.SourceEndPoint); } break; case KMessageCode.PeerExitNetwork: if (!args.Message.Header.SourceEndPoint.AddressEquals(IPAddress.Loopback)) { HandlePeerExitNetwork(args); AddToConnectionList(args.Message.Header.SourceEndPoint); } break; case KMessageCode.GotPeer: if (!args.Message.Header.SourceEndPoint.AddressEquals(IPAddress.Loopback)) { HandleGotPeer(args); AddToConnectionList(args.Message.Header.SourceEndPoint); } break; default: return; } Logger.Log("将消息加入“已处理”列表。"); // 对于收到的信息,都加入“已处理消息”列表 lock (HandledMessages) { HandledMessages.Add(new KHandledMessage(args.Message)); } } else { Logger.Log("更新消息生命周期。"); // 更新生命周期,并将其移动到列表最后(因为是“刚刚加入”的) handledMessage.LifeStart = DateTime.Now; lock (HandledMessages) { // 访问这个过程的只有一个线程,所以不用担心说前面某一项先被移动到后面去了导致索引错误 HandledMessages.RemoveAt(handledMessageIndex); HandledMessages.Add(handledMessage); } Logger.Log("清理“已处理”列表。"); SweepHandledMessages(); } } }
/// <summary> /// 连接到指定的接入端点,并广播“加入分布网络”消息。 /// <para>注意,本方法执行中会为 ConnectionList 加锁。</para> /// </summary> /// <param name="pointInsertion">要连接的端点。</param> /// <param name="realPort">非零表示这是接入点要连接接入点,该端口是本机正在监听的端口;零表示只是普通用户连接接入点。</param> /// <param name="workerThread">返回发送用的 <see cref="System.Threading.Thread"/>。</param> /// <returns>一个 <see cref="System.Threading.WaitHandle"/>,通过此对象可以对发送操作进行等待。</returns> public WaitHandle EnterNetwork(IPEndPoint pointInsertion, ushort realPort, out Thread workerThread) { Logger.Log("KClient::EnterNetwork(IPEndPoint)" + Environment.NewLine + "接入点: " + pointInsertion.ToString()); AutoResetEvent ev = new AutoResetEvent(false); Thread thread = new Thread(delegate() { try { // 第一次发送信息,要使用以后用来接收信息的端口,这样才能让接入点知道你映射后的接收端口 //TcpClient client = new TcpClient(); // 那么就不能采取常规的方式,而应该用 TcpListener 的 socket 发出去 //TcpClient client = new TcpClient(new IPEndPoint(IPAddress.Loopback, LocalEndPoint.Port)); Socket enterSocket; if (realPort == 0) { // 普通用户 enterSocket = _listener.Server; if (!enterSocket.IsBound) { enterSocket.Bind(new IPEndPoint(IPAddress.Any, ((IPEndPoint)_listener.LocalEndpoint).Port)); } } else { enterSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); } //Socket enterSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //Socket enterSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP); //enterSocket.Bind(new IPEndPoint(IPAddress.Loopback, LocalEndPoint.Port)); Logger.Log("尝试连接端点: " + pointInsertion.ToString()); //var iar = client.BeginConnect(pointInsertion.Address, pointInsertion.Port, null, null); var iar = enterSocket.BeginConnect(pointInsertion, null, null); if (!iar.IsCompleted) { iar.AsyncWaitHandle.WaitOne(SendTimeout); } if (!iar.IsCompleted) { Logger.Log("连接超时。"); // 未成功连接的话,直接返回 //return false; ev.Set(); ev.Dispose(); return; // 这里好像无法直接返回连接状态 // 但是应该可以判断连接列表是否为空来确定是否成功,非空则成功,否则失败 } //client.EndConnect(iar); enterSocket.EndConnect(iar); Logger.Log("socket 本地端点: " + enterSocket.LocalEndPoint.ToString() + " 远程端点: " + enterSocket.RemoteEndPoint.ToString()); // 为了不干扰连接列表的报告,先发送信息 Logger.Log("连接成功,发送自己进入的消息。"); int origPortNumber; string origAddress; //var bs = client.GetStream(); { // 写入 //bs.WriteMessage(MessageFactory.ClientEnterNetwork(LocalKEndPoint)); enterSocket.WriteMessage(MessageFactory.ClientEnterNetwork(LocalKEndPoint, realPort)); // 接下来接收接入点返回的当前连接信息 // 先是读取列表数据大小 Logger.Log("读取返回数据: 数据长度"); //int dataLength = bs.ReadInt32(); int dataLength = enterSocket.ReadInt32(); Logger.Log("长度: " + dataLength.ToString()); // 然后读列表 var dataBuffer = new byte[1024]; var data = new byte[dataLength]; int readLength = 0; int totalReadLength = 0; // 确保全部读取 while (totalReadLength < dataLength) { //readLength = bs.Read(dataBuffer, 0, dataBuffer.Length); readLength = enterSocket.Receive(dataBuffer); if (readLength > 0) { Array.Copy(dataBuffer, 0, data, totalReadLength, readLength); totalReadLength += readLength; } } Logger.Log("数据读取完毕。准备解码。"); origPortNumber = LocalEndPoint.Port; origAddress = LocalEndPoint.Address.ToString(); // 解码并将自己的连接列表和用户列表同步为接入点的数据 DecodeTargetInformation(data); Logger.Log("他人眼中的我: " + LocalEndPoint.ToString()); StringBuilder sb = new StringBuilder(100); sb.AppendLine("刚刚收到的连接列表如下。"); lock (ConnectionList) { foreach (var item in ConnectionList) { sb.AppendLine(item.ToString()); } } sb.AppendLine("刚刚收到的用户列表如下。"); lock (TrackerServer.Seeds) { foreach (var item in TrackerServer.Seeds) { sb.AppendLine("[" + item.Key.ToHexString() + "]"); foreach (var peer in item.Value) { sb.AppendLine(peer.EndPoint.ToString()); } } } Logger.Log(sb.ToString()); } Logger.Log("添加对方持有的连接信息结束,将对方加入自己的连接列表。"); // 然后将对方加入自己的连接列表 try { AddToConnectionList(KEndPoint.FromEndPoint(pointInsertion)); } catch (Exception) { } // 不需要 //if (ConnectionList.Count > 0) //{ // Logger.Log("尝试广播消息,让连接列表中的客户端报告他们存活。"); // // 要其他人报告他们存活 // // 能发送等价于存活,所以并不期望回音,只是测试能连接而已 // BroadcastMessage(MessageFactory.ReportAlive(LocalKEndPoint)); //} //client.Close(); enterSocket.Disconnect(false); enterSocket.Close(); _listener.Stop(); // 说到底,要 EnterNetwork(),肯定是普通客户端,那么就请求端口映射吧 //if (origPortNumber != LocalEndPoint.Port) { if (PortMapping.CanUsePortMapping) { try { if (origPortNumber == LocalEndPoint.Port) { Thread.Sleep(TimeSpan.FromSeconds(0.8)); } //PortMapping.AddPortMapping((ushort)origPortNumber, (ushort)LocalEndPoint.Port, origAddress); PortMapping.AddPortMapping((ushort)origPortNumber, (ushort)LocalEndPoint.Port); } catch (Exception ex) { System.Diagnostics.Debug.Print("AddPortMapping: " + ex.Message); Logger.Log("AddPortMapping: " + ex.Message); } } } ev.Set(); ev.Dispose(); //return true; } catch (Exception ex) { Logger.Log(ex.Message + Environment.NewLine + ex.StackTrace); } }); thread.IsBackground = true; thread.Start(); workerThread = thread; return(ev); }
/// <summary> /// 解码连接到接入点时收到的字节数组,并应用这些信息。 /// </summary> /// <param name="data">收到的数据。</param> ///// <param name="sendPeerEnter">解码过程中是否应该广播 PeerEnterNetwork 消息。</param> /// <exception cref="System.FormatException">解码失败时发生。</exception> /// <remarks> /// 需要发送 PeerEnterNetwork 消息的情况会发生于:A开启客户端和μT,B开启客户端和μT,A(用户列表非空)再尝试连接B,此时如果B并没有保存全网的用户列表,那么A就要广播 PeerEnterNetwork。 /// </remarks> private void DecodeTargetInformation(byte[] data) { // 如果对方发过来的是空,那么就肯定不会有数据啦 if (data.Length > 0) { BEncodedDictionary dictionary = BEncodedDictionary.Decode(data) as BEncodedDictionary; if (dictionary == null) { throw new FormatException("无法解码。"); } BEncodedList connList = dictionary["connections"] as BEncodedList; BEncodedDictionary peersDict = dictionary["peers"] as BEncodedDictionary; // 规范 v1.2 // 先确认自己,同时 if ... 是兼容老版的通信 if (dictionary.ContainsKey("your endpoint")) { BEncodedDictionary yourEndPoint = dictionary["your endpoint"] as BEncodedDictionary; var ip = new IPAddress((yourEndPoint["ip"] as BEncodedString).TextBytes); var port = BitConverter.ToUInt16((yourEndPoint["port"] as BEncodedString).TextBytes, 0); // 分别设置 KClient、TrackerServer 和 BT 客户端的自己 SetLocalEndPoint(new IPEndPoint(ip, port)); TrackerServer.SetLocalEndPoint(new IPEndPoint(ip, TrackerServer.LocalEndPoint.Port)); TrackerServer.SetMyself(new IPEndPoint(ip, TrackerServer.Myself.EndPoint.GetPortNumber())); this.FreeToGo = true; TrackerServer.FreeToGo = true; } // ... lock (ConnectionList) { foreach (var item in connList) { var d = item as BEncodedDictionary; KEndPoint kep = KEndPoint.Empty; kep.SetAddress((d["ip"] as BEncodedString).TextBytes); kep.SetPort((int)BitConverter.ToUInt16((d["port"] as BEncodedString).TextBytes, 0)); try { AddToConnectionList(kep); } catch (Exception) { } } } // 如果已经有用户登记了,那么应该广播 if (TrackerServer.Seeds.Count > 0) { lock (TrackerServer.Seeds) { foreach (var kv in TrackerServer.Seeds) { // 广播消息 BroadcastMyselfAddAsPeer(kv.Key); } } } lock (TrackerServer.Seeds) { foreach (var kv in peersDict) { InfoHash infoHash = InfoHash.FromByteArray(kv.Key.TextBytes); List <Peer> peers = new List <Peer>((kv.Value as BEncodedList).Count); foreach (var item in (kv.Value as BEncodedList)) { var d = item as BEncodedDictionary; KEndPoint kep = KEndPoint.Empty; kep.SetAddress((d["ip"] as BEncodedString).TextBytes); kep.SetPort((int)BitConverter.ToUInt16((d["port"] as BEncodedString).TextBytes, 0)); Peer peer = Peer.Create(kep); peers.Add(peer); } try { TrackerServer.Seeds.Add(infoHash, peers); } catch (Exception) { } } } } else { Logger.Log("待解码的数据为空,这意味着对方客户端目前持有的连接列表和用户列表为空。"); } }
/// <summary> /// 设定程序认为的 BitTorrent 客户端眼中的“自己”是什么。 /// </summary> /// <param name="endPoint">本地的 BitTorrent 客户端使用的端点,包括本地局域网地址(不是本地环回地址)及其监听端口。</param> public void SetMyself(IPEndPoint endPoint, string peerID = null) { _myself = Peer.Create(KEndPoint.FromEndPoint(endPoint), peerID); }
/// <summary> /// 使用指定的目标端点创建一个新的 ConnectionListItem。 /// </summary> /// <param name="clientLocation">指定的目标端点。</param> public ConnectionListItem(KEndPoint clientLocation) : this(clientLocation, ConnectionState.Active, 0) { }
/// <summary> /// 处理 ClientEnterNetwork 消息。 /// </summary> /// <param name="args">处理时需要的信息。</param> private void HandleClientEnterNetwork(HandleMessageArgs args) { Logger.Log("收到消息: 客户端加入分布网络。"); KEndPoint remoteEndPoint = args.Message.Header.SourceEndPoint; int isMessageHandled = 0; ushort realPort = 0; if (args.Message.Content.Data.Length > 0) { BEncodedDictionary dict; dict = BEncodedValue.Decode <BEncodedDictionary>(args.Message.Content.Data); isMessageHandled = (int)((dict["message handled"] as BEncodedNumber).Number); realPort = (ushort)((dict["real port"] as BEncodedNumber).Number); } string enterArgsRecord = "ClientEnterNetwork Args:"; enterArgsRecord += Environment.NewLine + "isMessageHandled: " + isMessageHandled.ToString(); enterArgsRecord += Environment.NewLine + "realPort: " + realPort.ToString(); Logger.Log(enterArgsRecord); args.RealPort = realPort; if (!ConnectionList.Contains(remoteEndPoint)) { if (isMessageHandled == 0) { Logger.Log("本客户端是第一个收到这条进入消息的客户端。" + Environment.NewLine + "本机的连接列表如下。"); StringBuilder sb = new StringBuilder(100); lock (ConnectionList) { foreach (var item in ConnectionList) { sb.AppendLine(item.ToString()); } } Logger.Log(sb.ToString()); Logger.Log("将当前连接信息编码并发送。我眼中的对方: " + args.EndPoint.ToString()); // 将自己的连接列表和用户列表编码,准备发送到连接来的客户端 var data = EncodeTargetInformation(args.EndPoint); // 然后要修正 remoteEndPoint,因为加入消息必定是 127.0.0.1:ep 形式,所以要修正为实际的 ep remoteEndPoint = KEndPoint.FromEndPoint(args.EndPoint); try { // 先返回接下来的字节大小 args.Stream.WriteInt32(data.Length); // 然后一次性发送 args.Stream.Write(data, 0, data.Length); args.Stream.Flush(); } catch (Exception ex) { // 首次通信就失败了…… Logger.Log(ex.Message); Logger.Log(ex.StackTrace); } } } KMessage message = args.Message; if (isMessageHandled == 0) { // 注意:虽然 KMessage 是一个值类型,但是其中含有引用类型(数组),这里修改了这个引用类型 Logger.Log("设置该消息为处理过。"); message.Content.Data = BitConverter.GetBytes((int)1); } else { // 本客户端不是第一个处理的,那就报告存活吧 SendMessage(remoteEndPoint, MessageFactory.ReportAlive(LocalKEndPoint)); } Logger.Log("转发消息。"); // 转发 BroadcastMessage(message); }