• C# UDP打洞


    下面是UDP打洞程序包的源码:

    //WellKnown公用库

    using System;

    using System.IO;

    using System.Runtime.Serialization.Formatters.Binary;

    using System.Net ;

    using System.Net .Sockets ;

    using System.Collections ;

     

     

     

    namespace P2PWellKnown

     

     

    {

     

           /// <summary>

           /// UDP用户登录事件委托

           /// </summary>

           /// <param name="sender">事件源对象</param>

           /// <param name="e">事件实体</param>

           public delegate void UdpUserLogInDelegate(object sender,                  UDPSockEventArgs e);

     

     

     

           /// <summary>

     

           /// 一般UDP消息事件委托

           /// </summary>

           /// <param name="sender">事件源对象</param>

           /// <param name="e">事件实体</param>

           public delegate void UdpMessageDelegate(object sender,                  UDPSockEventArgs e);

     

     

     

           /// <summary>

     

           /// 初始化一个新连接的事件委托

           /// </summary>

           /// <param name="sender">事件源对象</param>

           /// <param name="e">事件实体</param>

           public delegate void UdpNewConnectDelegate(object sender,                 UDPSockEventArgs e);

     

     

     

    /// <summary>

     

     

    /// P2P共享数据类

     

    /// </summary>

    public class P2PConsts

    {

         /// <summary>

         /// UDP服务器监听端口

         /// </summary>

         public const int UDP_SRV_PORT     = 2280;

     

     

     

         /// <summary>

     

         ///TCP服务器监听端口

         /// </summary>

         public const int TCP_SRV_PORT =2000;

    }

     

     

     

     

     

     

     

    /// <summary>

    /// FormatterHelper 序列化,反序列化消息的帮助类

     

    /// </summary>

    public class FormatterHelper

     

    {

          public static byte[] Serialize(object obj)

     

     

         {

     

     

     

          BinaryFormatter binaryF = new BinaryFormatter();

           MemoryStream ms = new MemoryStream(1024*10);

          binaryF.Serialize(ms, obj);

          ms.Seek(0, SeekOrigin.Begin);

     

     

          byte[] buffer = new byte[(int)ms.Length];

          ms.Read(buffer, 0, buffer.Length);

          ms.Close();

     

     

     

     

          return buffer;

         }

     

     

         public static object Deserialize(byte[] buffer)

     

     

         {

          BinaryFormatter binaryF = new BinaryFormatter();

          MemoryStream ms = new MemoryStream(buffer, 0, buffer.Length, false);

     

     

          object obj = binaryF.Deserialize(ms);

     

     

          ms.Close();

     

     

          return obj;

         }

    }

      

     

     

     

     

     

    /// <summary>

     

     

    /// 用于承载UDPSock信息的事件类

     

    /// </summary>

    public class UDPSockEventArgs:EventArgs

    {

         /// <summary>

         /// 要承载的消息

         /// </summary>

         private string m_strMsg;

         /// <summary>

     

         /// 用户信息

         /// </summary>

         private string m_strUserName;

               /// <summary>

     

               /// 触发该事件的公共终端

               /// </summary>

               private IPEndPoint m_EndPoint;

     

     

     

         /// <summary>

     

         /// 初始化UDPSock事件

         /// </summary>

         /// <param name="sMsg">用户发送的信息</param>

         public UDPSockEventArgs(string sMsg):base()

         {

          this.m_strMsg =sMsg;

         }

     

     

     

         /// <summary>

     

         /// 远端用户名

         /// </summary>

         public string RemoteUserName

         {

          get

          {

           return m_strUserName;

          }

          set

          {

           m_strUserName=value;

          }

         }

     

     

     

               /// <summary>

     

               /// 一般套接字消息

               /// </summary>

               public string SockMessage

               {

                   get

                   {

                       return m_strMsg;

                   }

                   set

                   {

                       m_strMsg = value;

                   }

               }

     

     

     

               /// <summary>

     

               /// 公共远端节点

               /// </summary>

               public IPEndPoint RemoteEndPoint

               {

                   get

                   {

                       return m_EndPoint;

                   }

                   set

                   {

                       m_EndPoint = value;

                   }

               }

         }

    }

     

     

     

     

     

    //UDPP2PSock.cs

     

     

    using System;

     

    using System.Collections.Generic;

    using System.Text;

    using System.Net;

    using System.Net.Sockets;

    using System.Threading;

     

     

     

    using P2PWellKnown;

     

     

     

     

     

     

    namespace UDPP2P

     

     

    {

     

           /// <summary>

           /// UDPP2P套接字管理类

           /// </summary>

           public class UDPP2PSock

           {

               /// <summary>

               /// 用户登录事件

               /// </summary>

               public event UdpUserLogInDelegate OnUserLogInU;

     

     

     

               /// <summary>

     

               /// 一般UDP消息事件

               /// </summary>

               public event UdpMessageDelegate OnSockMessageU;

     

     

     

               /// <summary>

     

               /// 初始化一个新连接事件

               /// </summary>

               public event UdpNewConnectDelegate OnNewConnectU;

     

     

     

               /// <summary>

     

               /// UDP服务器

               /// </summary>

               private UdpClient m_udpServer;

     

     

     

               /// <summary>

     

               /// UDP客户端

               /// </summary>

               private UdpClient m_udpClient;

     

     

     

               /// <summary>

     

               /// 服务器实际上在本地机器上监听的

               /// 端口,用于当一台计算机上同时启

               /// 动两个可两以上服务器进程时,标

               /// 识不同的服务器进程

               /// </summary>

               private int m_iMyServerPort;

     

     

     

               /// <summary>

     

               /// 客户端在本地机器上实际使用的端口,

               /// 用于当一台计算机上同时有两个或两

               /// 个以上客户端进程在运行时,标识不

               /// 同的客户端进程

               /// </summary>

               private int m_iMyClientPort;

     

     

     

               /// <summary>

     

               /// 标识是否已成功创服务器

               /// </summary>

               private bool m_bServerCreated;

     

     

     

               /// <summary>

     

               /// 标识是否已成功创建客户端

               /// </summary>

               private bool m_bClientCreated;

     

     

     

               /// <summary>

     

               /// 服务器使用的线程

               /// </summary>

               private Thread m_serverThread;

     

     

     

               /// <summary>

     

               /// 客户端使用的线程

               /// </summary>

               private Thread m_clientThread;

     

     

     

               /// <summary>

     

               /// 打洞线程

               /// </summary>

               //private Thread m_burrowThread;

     

     

     

               /// <summary>

     

               /// 远端节点

               /// </summary>

               private IPEndPoint m_remotePoint;

     

     

     

               /// <summary>

     

               /// 当前进程作为客户端的公共终端

               /// </summary>

               private string m_strMyPublicEndPoint;

     

     

     

               /// <summary>

     

               /// 当前进程作为客户端的私有终端

               /// </summary>

               private string m_strMyPrivateEndPoint;

     

     

     

               /// <summary>

     

               /// 用于接受信息的StringBuilder实例

               /// </summary>

               private StringBuilder m_sbResponse = new StringBuilder();

     

     

     

               /// <summary>

     

               /// P2P打洞时标识是否收到回应消息

               /// </summary>

               private bool m_bRecvAck=false ;

     

     

     

               /// <summary>

     

               /// 请求向其方向打洞的私有终端

               /// </summary>

               private IPEndPoint m_requestPrivateEndPoint;

     

     

     

               /// <summary>

     

               /// 请求向其方向打洞的公共终端

               /// </summary>

               private IPEndPoint m_requestPublicEndPoint;

     

     

     

               /// <summary>

     

               /// 打洞消息要发向的节点

               /// </summary>

               private ToEndPoint m_toEndPoint;

     

     

     

               /// <summary>

     

               /// 用于标识是否已经和请求客户端建立点对连接

               /// </summary>

               //private bool m_bHasConnected=false ;

     

     

     

               /// <summary>

     

               /// 创建服务器或客户端的最大尝试

               /// 次数,为(65536-60000),防止

               /// 因不能创建而限入死循环或使用

               /// 无效端口

               /// </summary>

               private const int MAX_CREATE_TRY = 5536;

     

     

     

               /// <summary>

     

               /// 打洞时尝试连接的最大尝试次数

               /// </summary>

               private const int MAX_CONNECT_TRY = 10;

     

     

     

     

     

     

     

               /// <summary>

     

               /// 构造函数,初始化UDPP2P实例

               /// </summary>

               public UDPP2PSock()

               {

                   m_iMyServerPort = P2PConsts.UDP_SRV_PORT;

                   m_iMyClientPort = 60000;

                   m_bClientCreated = false;

                   m_bServerCreated = false;

                   m_toEndPoint = new ToEndPoint();

                   m_serverThread = new Thread(new ThreadStart(RunUDPServer ));

                   m_clientThread = new Thread(new ThreadStart(RunUDPClient ));

                   //m_burrowThread = new Thread(new ThreadStart(BurrowProc));

               }

     

     

     

               /// <summary>

     

               /// 创建UDP服务器

               /// </summary>

               public void CreateUDPSever()

               {

                   int iTryNum=0;

     

     

     

                   //开始尝试创建服务器

     

                   while (!m_bServerCreated && iTryNum < MAX_CREATE_TRY)

                   {

                       try

                       {

                           m_udpServer = new UdpClient(m_iMyServerPort);

                           m_bServerCreated = true;

                       }

                       catch

                       {

                           m_iMyServerPort++;

                           iTryNum++;

                       }

                   }

     

     

     

                   //创建失败,抛出异常

     

                   if (!m_bServerCreated && iTryNum == MAX_CREATE_TRY)

                   {

                       throw new Exception ("创建服务器尝试失败!");

                   }

                   m_serverThread.Start();

               

               }

     

     

     

               /// <summary>

     

               /// 创建UDP客户端

               /// </summary>

               /// <param name="strServerIP">服务器IP</param>

               /// <param name="iServerPort">服务器端口</param>

               public void CreateUDPClient(string strServerIP,int iServerPort)

               {

                   int iTryNum = 0;

     

     

     

                   //开始尝试创建服务器

     

                   while (!m_bClientCreated     && iTryNum < MAX_CREATE_TRY)

                   {

                       try

                       {

                           m_udpClient     = new UdpClient(m_iMyClientPort );

                           m_bClientCreated     = true;

                           string strIPAddress =                            (System.Net.Dns.GetHostAddresses("localhost")[0]).                             ToString();

                           m_strMyPrivateEndPoint = strIPAddress + ":" +                            m_iMyClientPort.ToString();

                       }

                       catch

                       {

                           m_iMyClientPort ++;

                           iTryNum++;

                       }

                   }

     

     

     

                   //创建失败,抛出异常

     

                   if (!m_bClientCreated     && iTryNum == MAX_CREATE_TRY)

                   {

                       throw new Exception ("创建客户端尝试失败!");

                   }

     

     

     

                   IPEndPoint hostPoint =                     new IPEndPoint(IPAddress.Parse(strServerIP), iServerPort);

     

     

                   string strLocalIP =                    (System.Net.Dns.GetHostAddresses("localhost"))[0].ToString();

     

                   SendLocalPoint(strLocalIP, m_iMyClientPort, hostPoint);

                   m_clientThread .Start();

               }

     

     

     

     

     

     

     

               /// <summary>

     

               /// 运行UDP服务器

               /// </summary>

               private void RunUDPServer()

               {

                   while (true)

                   {

                       byte[] msgBuffer =m_udpServer .Receive(ref m_remotePoint);

                       m_sbResponse.Append(System.Text.Encoding.                           Default.GetString(msgBuffer));

                       CheckCommand();

                       Thread.Sleep(10);

                   }

               }

     

     

     

     

     

     

     

               /// <summary>

     

               /// 运行UDP客户端

               /// </summary>

               private void RunUDPClient()

               {

                   while (true)

                   {

                       byte[] msgBuffer = m_udpClient.Receive(ref m_remotePoint);

                       m_sbResponse.Append(System.Text.Encoding.                            Default.GetString(msgBuffer));

                       CheckCommand();

                       Thread.Sleep(10);

                   }

               }

     

     

     

     

     

     

     

               /// <summary>

     

               /// 销毁UDP服务器

               /// </summary>

               public void DisposeUDPServer()

               {

                   m_serverThread.Abort();

                   m_udpServer.Close();

               }

     

     

     

               /// <summary>

     

               /// 销毁UDP客房端

               /// </summary>

               public void DisposeUDPClient()

               {

                   m_clientThread.Abort();

                   m_udpClient.Close();

               }

     

     

     

               /// <summary>

     

               /// 发送消息

               /// </summary>

               /// <param name="strMsg">消息内容</param>

               /// <param name="REP">接收节点</param>

               public void SendData(string strMsg,IPEndPoint REP)

               {

                   byte[] byMsg =                    System.Text.Encoding.Default.GetBytes(strMsg.ToCharArray());

                   m_udpClient.Send(byMsg, byMsg.Length, REP);

               }

     

     

     

     

     

     

     

               /// <summary>

     

               /// 发送消息,服务器专用

               /// </summary>

               /// <param name="strMsg">消息内容</param>

               /// <param name="REP">接收节点</param>

               private void ServerSendData(string strMsg,IPEndPoint REP)

               {

                   byte[] byMsg =                    System.Text.Encoding.Default.GetBytes(strMsg.ToCharArray());

                   m_udpServer.Send(byMsg, byMsg.Length, REP);

               }

     

     

     

     

     

     

     

               /// <summary>

     

               /// 发送本地节点信息

               /// </summary>

               /// <param name="strLocalIP">本地IP</param>

               /// <param name="iLocalPort">本地端口</param>

               public void SendLocalPoint(string strLocalIP,                 int iLocalPort,IPEndPoint REP)

               {

                   string strLocalPoint = "\x01\x02" + strLocalIP + ":" +                          iLocalPort.ToString() + "\x02\x01";

                   SendData(strLocalPoint, REP);

               }

     

     

     

              /// <summary>

     

     

              /// 同时向指定的终端(包括公共终端和私有终端)打洞

     

              /// </summary>

              /// <param name="pubEndPoint">公共终端</param>

              /// <param name="prEndPoint">私有终端</param>

              /// <returns>打洞成功返回true,否则返回false</returns>

              public void StartBurrowTo(IPEndPoint pubEndPoint,                 IPEndPoint prEndPoint)

              {

                  Thread burrowThread = new Thread(new ThreadStart(BurrowProc));

                  m_toEndPoint.m_privateEndPoint = prEndPoint;

                  m_toEndPoint.m_publicEndPoint = pubEndPoint;

                  burrowThread.Start();

              }

     

     

     

            

     

     

              /// <summary>

     

              /// 打洞线程

              /// </summary>

              private void BurrowProc()

              {

                  IPEndPoint prEndPoint = m_toEndPoint.m_privateEndPoint;

                  IPEndPoint pubEndPoint = m_toEndPoint.m_publicEndPoint;

                  int j = 0;

                  for (int i = 0; i < MAX_CONNECT_TRY; i++)

                  {

                      SendData("\x01\x07\x07\x01", prEndPoint);

                      SendData("\x01\x07\x07\x01", pubEndPoint);

     

     

     

                      //等待接收线程标记修改

     

                      for (j = 0; j < MAX_CONNECT_TRY; j++)

                      {

                          if (m_bRecvAck)

                          {

                              m_bRecvAck = false;

                              SendData("\x01\x07\x07\x01", prEndPoint);

                              Thread.Sleep(50);

                              SendData("\x01\x07\x07\x01", pubEndPoint);

     

     

     

                              UDPSockEventArgs args = new UDPSockEventArgs("");

     

     

                              args.RemoteEndPoint = pubEndPoint;

     

                              if (OnNewConnectU != null)

                              {

                                  OnNewConnectU(this, args);

                              }

                              //Thread .Sleep (System .Threading.Timeout .Infinite );

                              return;

                          }

                          else

                          {

                              Thread.Sleep(100);

                          }

                      }

     

     

     

                      //如果没有收到目标主机的回应,表明本次打

     

                      //洞尝试失败,等待100毫秒后尝试下一次打洞

                      Thread.Sleep(100);

                  }

     

     

     

                  //MAX_CONNECT_TRY尝试都失败,表明打洞失败,抛出异常

     

                  //throw new Exception("打洞失败!");

                  System.Windows.Forms.MessageBox.Show("打洞失败!");////////////

              }

            

     

     

     

              /// <summary>

     

              /// 转发打洞请求消息,在服务器端使用

              /// </summary>

              /// <param name="strSrcPrEndpoint">请求转发的源私有终端</param>

              /// <param name="strSrcPubEndPoint">请求转发的源公共终端</param>

              /// <param name="REP">转发消息到达的目的终端</param>

              public void SendBurrowRequest(string strSrcPrEndpoint,                     string strSrcPubEndPoint,IPEndPoint REP)

              {

                  string strBurrowMsg = "\x04\x07" + strSrcPrEndpoint + " " +                     strSrcPubEndPoint + "\x07\x04";

                  ServerSendData(strBurrowMsg, REP);

              }

     

     

     

     

     

     

     

              /// <summary>

     

              /// 检查字符串中的命令

              /// </summary>

              private void CheckCommand()

              {

                  int nPos;

                  string strCmd = m_sbResponse.ToString();

     

     

     

                  //如果接收远端用户名

     

                  if ((nPos = strCmd.IndexOf("\x01\x02")) > -1)

                  {

                      ReceiveName(strCmd, nPos);

                   

                      //反馈公共终给端远端主机

                      string strPubEPMsg = "\x03\x07" + m_remotePoint.ToString() +                      "\x07\x03";

                      SendData(strPubEPMsg, m_remotePoint);

     

     

     

                      return;

     

     

                  }

     

     

     

     

                  //如果接收我的公共终端

     

                  if ((nPos = strCmd.IndexOf("\x03\x07")) > -1)

                  {

                      ReceiveMyPublicEndPoint(strCmd, nPos);

                      return;

                  }

     

     

     

                  //如果是打洞请求消息

     

                  if ((nPos = strCmd.IndexOf("\x04\x07")) > -1)

                  {

                      ReceiveAndSendAck(strCmd, nPos);

                      return;

                  }

     

     

     

                  //如果是打洞回应消息

     

                  if ((nPos =strCmd .IndexOf ("\x01\x07"))>-1)

                  {

                      m_bRecvAck = true;

                      int nPos2 = strCmd.IndexOf("\x07\x01");

                      if (nPos2 > -1)

                      {

                          m_sbResponse.Remove(nPos, nPos2 - nPos + 2);

                      }

     

     

     

                      /*

     

                      if (m_requestPublicEndPoint != null)

                      {

                          if (!m_bHasConnected)

                          {

                              m_bHasConnected = true;

                              UDPSockEventArgs args = new UDPSockEventArgs("");

                              args.RemoteEndPoint = m_requestPublicEndPoint;

                              if (OnNewConnectU != null)

                              {

                                  OnNewConnectU(this, args);

                              }

                              m_requestPublicEndPoint = null;

                          }

                      }*/

     

     

     

                      return;

     

     

                  }

     

     

     

     

                  //一般聊天消息

     

                  m_sbResponse.Remove(0, strCmd.Length);

                  RaiseMessageEvent(strCmd);

              }

     

     

     

     

     

     

     

              /// <summary>

     

              /// 接收远端用户名

              /// </summary>

              /// <param name="strCmd">包含用户名的控制信息</param>

              /// <param name="nPos"></param>

              private void ReceiveName(string strCmd, int nPos)

              {

                  int nPos2 = strCmd.IndexOf("\x02\x01");

                  if (nPos2 == -1)

                  {

                      return;

                  }

                  m_sbResponse.Remove(nPos, nPos2 - nPos + 2);

     

     

     

                  string strUserName = strCmd.Substring(nPos + 2, nPos2 -                     nPos - 2);

     

     

                  UDPSockEventArgs e = new UDPSockEventArgs("");

     

                  e.RemoteUserName = strUserName;

                  e.RemoteEndPoint = m_remotePoint;

     

     

     

                  //触发用户登录事件

     

                  if (OnUserLogInU != null)

                  {

                      OnUserLogInU(this, e);

                  }

              }

     

     

     

     

     

     

     

              /// <summary>

     

              /// 接收打洞请求的消息并发送回应

              /// </summary>

              /// <param name="strCmd"></param>

              /// <param name="nPos"></param>

              private void ReceiveAndSendAck(string strCmd, int nPos)

              {

                  int nPos2 = strCmd.IndexOf("\x07\x04");

                  if (nPos2 == -1)

                  {

                      return;

                  }

                  m_sbResponse.Remove(nPos, nPos2 - nPos + 2);

     

     

     

                  string strBurrowMsg = strCmd.Substring(nPos + 2, nPos2 -                     nPos - 2);

     

     

     

     

     

     

                  string[] strSrcPoint = strBurrowMsg.Split(' ');

     

     

     

     

     

     

                  //分析控制字符串包含的节点信息

     

                  string[] strPrEndPoint = strSrcPoint[0].Split(':');

                  string[] strPubEndPoint = strSrcPoint[1].Split(':');

                  m_requestPrivateEndPoint=                   new IPEndPoint(IPAddress.Parse(strPrEndPoint[0]),                   int.Parse(strPrEndPoint[1]));

                  m_requestPublicEndPoint    =                    new IPEndPoint(IPAddress.Parse(strPubEndPoint[0]),                   int.Parse(strPubEndPoint[1]));

     

     

     

                  //向请求打洞终端的方向打洞

     

                  StartBurrowTo(m_requestPublicEndPoint, m_requestPrivateEndPoint);

              }

     

     

     

     

     

     

     

              /// <summary>

     

              /// 接收我的公共终端

              /// </summary>

              /// <param name="strCmd">包含公共终端的控制信息</param>

              /// <param name="nPos">控制字符串的起始位置</param>

              private void ReceiveMyPublicEndPoint(string strCmd, int nPos)

              {

                  int nPos2 = strCmd.IndexOf("\x07\x03");

                  if (nPos2 == -1)

                  {

                      return;

                  }

                  m_sbResponse.Remove(nPos, nPos2 - nPos + 2);

     

     

     

                  m_strMyPublicEndPoint=strCmd.Substring(nPos + 2,                    nPos2 - nPos - 2);

     

     

              }

     

     

     

     

     

     

     

     

              /// <summary>

     

              /// 触发一般UDP消息事件

              /// </summary>

              /// <param name="strMsg">消息内容</param>

              private void RaiseMessageEvent(string strMsg)

              {

                  UDPSockEventArgs args = new UDPSockEventArgs("");

                  args.SockMessage = strMsg;

                  args.RemoteEndPoint = m_remotePoint;

                  if (OnSockMessageU != null)

                  {

                      OnSockMessageU(this, args);

                  }

              }

     

     

     

     

     

     

     

              /// <summary>

     

              /// 获取当前进程作为客户端的公共终端

              /// </summary>

              public string MyPublicEndPoint

              {

                  get

                  {

                      return m_strMyPublicEndPoint;

                  }

              }

     

     

     

     

     

     

     

              /// <summary>

     

              /// 获取当前进程作为客户端的私有终端

              /// </summary>

              public string MyPrivateEndPoint

              {

                  get

                  {

                      return m_strMyPrivateEndPoint;

                  }

              }

          }

     

     

     

       

     

     

          /// <summary>

     

          /// 保存打洞消息要发向的节点信息

          /// </summary>

          class ToEndPoint

          {

              /// <summary>

              /// 私有节点

              /// </summary>

              public IPEndPoint m_privateEndPoint;

     

     

     

              /// <summary>

     

              /// 公共节点

              /// </summary>

              public IPEndPoint m_publicEndPoint;

          }

     

     

     

    }

     

     

     

     

     

     

               关于如何使用上述程序包的一些说明:

     

               主要程序的初始化,参考代码如下:

                  //创建UDP服务器和客户端

                  try

                  {

                      string strServerIP="127.0.0.1"

                      udpSock = new UDPP2PSock();

                      udpSock.OnUserLogInU +=                          new UdpUserLogInDelegate(OnUserLogInU);

                      udpSock.OnNewConnectU +=                          new UdpNewConnectDelegate(OnNewConnectU);

                      udpSock.CreateUDPSever();

                      udpSock.CreateUDPClient(strServerIP, P2PConsts.UDP_SRV_PORT);

                  }

                  catch (Exception ex)

                  {

     

     

     

                  }

     

  • 相关阅读:
    理解 Java Thread ContextClassLoader(线程上下文类加载器)
    StringUtils工具类常用方法汇总2(截取、去除空白、包含、查询索引)
    StringUtils工具类常用方法汇总1(判空、转换、移除、替换、反转)
    数组去重(2)
    数组去重(1)
    查找数组中的最大值(最小值)及相对应的下标
    javascript 隐式转换 == 之 [ ]==![ ] 结果为true,而{ }==!{ } 结果为false
    圣杯布局(2)>>>>>
    圣杯布局(1)>>>>>
    二分查找里的upper bound与lower bound的实现与分析
  • 原文地址:https://www.cnblogs.com/allexw/p/2832336.html
Copyright © 2020-2023  润新知