zoukankan      html  css  js  c++  java
  • 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)

                  {

     

     

     

                  }

     

  • 相关阅读:
    LeetCode 1110. Delete Nodes And Return Forest
    LeetCode 473. Matchsticks to Square
    LeetCode 886. Possible Bipartition
    LeetCode 737. Sentence Similarity II
    LeetCode 734. Sentence Similarity
    LeetCode 491. Increasing Subsequences
    LeetCode 1020. Number of Enclaves
    LeetCode 531. Lonely Pixel I
    LeetCode 1091. Shortest Path in Binary Matrix
    LeetCode 590. N-ary Tree Postorder Traversal
  • 原文地址:https://www.cnblogs.com/allexw/p/2832336.html
Copyright © 2011-2022 走看看