/// <summary> /// 解读一条用户连接消息。 /// </summary> /// <param name="message">待解读的消息。</param> /// <param name="infoHash">解读出的 InfoHash。</param> /// <param name="bitTorrentClientPort">解读出的用户监听端口。</param> /// <exception cref="System.ArgumentException">解读的不是用户连接消息时发生。</exception> public static void GetPeerMessageContent(KMessage message, out InfoHash infoHash, out ushort bitTorrentClientPort) { //if (message.Content.Data.Length != 22) //{ // throw new ArgumentException("要解读的不是一条用户连接消息。"); //} //infoHash = InfoHash.FromByteArray(message.Content.Data.Take(20).ToArray()); //bitTorrentClientPort = BitConverter.ToUInt16(message.Content.Data, 20); BEncodedDictionary dictionary; try { dictionary = BEncodedValue.Decode <BEncodedDictionary>(message.Content.Data); infoHash = InfoHash.FromByteArray((dictionary["infohash"] as BEncodedString).TextBytes); bitTorrentClientPort = (ushort)((dictionary["bt client port"] as BEncodedNumber).Number); } catch (Exception) { throw new ArgumentException("附带数据不是用户连接消息数据。"); } // 这里遇到了一个奇怪的问题,就是编解码的时候 #warning 系统的编解码和网络的编解码字节序可能不同! // BitConverter 的编解码和网络的编解码顺序可能不同 // 但是奇怪的是,即使这样,KMessage 还能正常解码,地址大多数是正常的,但是端口大多数是不正常的 // 即使是端口,也只是低2字节被颠倒了,高2字节都为零所以无法验证 // 所以目前的临时方法是直接将低2字节颠倒回来,而且只保留低2字节 // 再次测试后发现似乎是 μTorrent 自己的诈和……(以前)第一次收到的是错误的(后来在调试的时候,第一次好像也对了),接下来收到的都是正确的…… //var b1 = (byte)((bitTorrentClientPort & 0x0000ff00) >> 8); //var b2 = (byte)(bitTorrentClientPort & 0x000000ff); //bitTorrentClientPort = (int)(((int)b2 << 8) + b1); }
//public static bool TryRead(this Stream stream, byte[] buffer, int offset, int count, out int bytesRead) //{ // bool successful = true; // int read = 0; // try // { // read = stream.Read(buffer, offset, count); // } // catch (Exception) // { // successful = false; // } // bytesRead = read; // return successful; //} //public static bool TryWrite(this Stream stream, byte[] buffer, int offset, int count) //{ // bool successful = true; // try // { // stream.Write(buffer, offset, count); // } // catch (Exception) // { // successful = false; // } // return successful; //} /// <summary> /// 从指定的 <see cref="System.IO.Stream"/> 中读取一条消息。 /// </summary> /// <param name="stream">要读取的 <see cref="System.IO.Stream"/>。</param> /// <param name="message">读取出的消息。如果返回值不是 <see cref="Kei.KNetwork.MessageIOErrorCode.NoError"/>,则该值无效。</param> /// <returns>一个 <see cref="Kei.KNetwork.MessageIOCode"/> 值,表示读取的结果。</returns> public static MessageIOErrorCode ReadMessage(this Stream stream, out KMessage message) { byte[] buffer; KMessage km = new KMessage(); // Check magic try { buffer = new byte[8]; stream.Read(buffer, 0, 8); km.MagicNumber = BitConverter.ToUInt64(buffer, 0); if (km.MagicNumber != KMessage.Magic) { message = km; return(MessageIOErrorCode.WrongMagicNumber); } } catch (Exception) { message = km; return(MessageIOErrorCode.ReadMagicNumberFailed); } // Read header try { var headerSize = Marshal.SizeOf(typeof(KMessageHeader)); buffer = new byte[headerSize]; stream.Read(buffer, 0, headerSize); km.Header = KMessageHeader.FromByteArray(buffer); } catch (Exception) { message = km; return(MessageIOErrorCode.ReadHeaderFailed); } // Read content try { KMessageContent content = new KMessageContent(); buffer = new byte[4]; stream.Read(buffer, 0, 4); content.DataLength = BitConverter.ToUInt32(buffer, 0); content.Data = new byte[content.DataLength]; if (content.DataLength > 0) { stream.Read(content.Data, 0, (int)content.DataLength); } km.Content = content; } catch (Exception) { message = km; return(MessageIOErrorCode.ReadContentFailed); } message = km; return(MessageIOErrorCode.NoError); }
/// <summary> /// 使用指定的参数创建一个新的 <see cref="Kei.KNetwork.HandleMessageArgs"/> 实例。 /// </summary> /// <param name="stream">网络传输使用的 <see cref="System.IO.Stream"/>。</param> /// <param name="message">收到的消息。</param> /// <param name="endPoint">通信中的源端点。</param> internal HandleMessageArgs(Stream stream, KMessage message, IPEndPoint endPoint, ushort realPort) { _stream = stream; _message = message; _endPoint = endPoint; RealPort = realPort; }
/// <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> /// 生成一个 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> /// 生成一个 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> /// 向指定的 <see cref="System.Net.Sockets.Socket"/> 中写入一条消息。 /// </summary> /// <param name="socket">要进行写入的 <see cref="System.Net.Sockets.Socket"/>。</param> /// <param name="message">要写入的消息。</param> /// <returns>一个 <see cref="Kei.KNetwork.MessageIOErrorCode"/> 值,表示写入的结果。</returns> public static MessageIOErrorCode WriteMessage(this Socket socket, KMessage message) { var data = message.ToByteArray(); try { socket.Send(data); return(MessageIOErrorCode.NoError); } catch (Exception) { return(MessageIOErrorCode.WriteFailed); } }
/// <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> /// 生成一个 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> /// 向指定的 <see cref="System.IO.Stream"/> 中写入一条消息。 /// </summary> /// <param name="stream">要进行写入的 <see cref="System.IO.Stream"/>。</param> /// <param name="message">要写入的消息。</param> /// <returns>一个 <see cref="Kei.KNetwork.MessageIOErrorCode"/> 值,表示写入的结果。</returns> public static MessageIOErrorCode WriteMessage(this Stream stream, KMessage message) { var data = message.ToByteArray(); try { stream.Write(data, 0, data.Length); stream.Flush(); return(MessageIOErrorCode.NoError); } catch (Exception) { return(MessageIOErrorCode.WriteFailed); } }
/// <summary> /// 判断一个 List<KHandledMessage> 内是否含有以指定 <see cref="Kei.KNetwork.KMessages"/> 区分的 <see cref="Kei.KNetwork.KHandledMessage"/>。 /// </summary> /// <param name="list">要判断的 List<KHandledMessage>。</param> /// <param name="message">作为指定键的 <see cref="Kei.KNetwork.KMessage"/>。</param> /// <returns>一个 <see cref="System.Boolean"/>,指示是否找到了符合条件的 <see cref="Kei.KNetwork.KHandledMessage"/>。</returns> public static bool Contains(this List <KHandledMessage> list, KMessage message) { if (list == null) { return(false); } bool contains = false; lock (list) { foreach (var item in list) { if (item.Message.Equals(message)) { contains = true; break; } } } return(contains); }
/// <summary> /// 从一个 <see cref="Kei.KNetwork.KMessage"/> 创建一个新的实例,并指定为此时为处理时刻,使用默认生存时间。 /// </summary> /// <param name="message">处理的基础 <see cref="Kei.KNetwork.KMessage"/></param> public KHandledMessage(KMessage message) : this(message, DateTime.Now) { }
/// <summary> /// 向连接列表中的所有端点发送消息(即广播)。 /// <para>注意,本方法执行中会为 ConnectionList 加锁。</para> /// </summary> /// <param name="message">要广播的消息。</param> /// <returns>一个 <see cref="System.Threading.WaitHandle"/>,通过此对象可以对发送操作进行等待。</returns> private WaitHandle BroadcastMessage(KMessage message) { string tmpLog; tmpLog = "KClient::BroadcastMessage(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 { lock (ConnectionList) { foreach (var item in ConnectionList) { if (item.State != ConnectionState.RemovePending) { TcpClient tcpClient = new TcpClient(); var remoteEndPoint = item.ClientLocation.GetEndPoint(); 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); item.ResetTimesTried(); var bs = tcpClient.GetStream(); { Logger.Log("广播消息。"); // 本次调用是因为收到了 TrackerComm 事件的消息 // 发生事件前 TrackerServer 的 Myself 就已经设置完成了,所以这里不用担心 bs.WriteMessage(message); } tcpClient.Close(); } else { Logger.Log("连接超时。"); // 设置/移除出列表 item.IncreaseTimesTriedAndCheck(); } } } } Logger.Log("清扫连接列表并重设等待事件。"); SweepConnectionList(); ev.Set(); ev.Dispose(); } catch (Exception ex) { Logger.Log(ex.Message + Environment.NewLine + ex.StackTrace); } }); thread.IsBackground = true; thread.Start(); return(ev); }
/// <summary> /// 从一个 <see cref="Kei.KNetwork.KMessage"/> 创建一个新的实例,并设置处理时刻与生存时间。 /// </summary> /// <param name="message">处理的基础 <see cref="Kei.KNetwork.KMessage"/>。</param> /// <param name="lifeStart">消息的处理时刻。</param> /// <param name="lifeLength">消息的生存时间。</param> public KHandledMessage(KMessage message, DateTime lifeStart, TimeSpan lifeLength) { _message = message; _lifeStart = lifeStart; _lifeLength = lifeLength; }
/// <summary> /// 从一个 <see cref="Kei.KNetwork.KMessage"/> 创建一个新的实例,设置处理时刻,并使用默认生存时间。 /// </summary> /// <param name="message">处理的基础 <see cref="Kei.KNetwork.KMessage"/>。</param> /// <param name="lifeStart">消息的处理时刻。</param> public KHandledMessage(KMessage message, DateTime lifeStart) : this(message, lifeStart, DefaultLifeLength) { }
/// <summary> /// 在指定的 List<KHandledMessage> 中寻找以指定 <see cref="Kei.KNetwork.KMessage"/> 区分的 <see cref="Kei.KNetwork.KHandledMessage"/>,并返回查找结果。 /// </summary> /// <param name="list">要在其中查找的 List<KHandledMessage>。</param> /// <param name="message">作为指定键的 <see cref="Kei.KNetwork.KMessage"/>。</param> /// <param name="index">输出一个 <see cref="System.Int32"/>。如果找到了,这个值是找到的项的索引;如果未找到,则该值无效。</param> /// <returns>一个 <see cref="Kei.KNetwork.KHandledMessage"/>,指示找到的项;或者 null,表示没找到。</returns> public static KHandledMessage FindHandledMessage(this List <KHandledMessage> list, KMessage message, out int index) { if (list == null) { index = 0; return(null); } KHandledMessage khm = null; int i = 0; index = 0; lock (list) { foreach (var item in list) { if (item.Message.Equals(message)) { index = i; khm = item; break; } i++; } } return(khm); }
/// <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); }