C#代码实现TCP穿透(打洞)

内网之间实现TCP通讯需要用到内网穿透技术,具体原理网上都有,参考:

https://blog.csdn.net/leisure512/article/details/4900191

https://blog.csdn.net/aaron133/article/details/79206257

TCP穿透成功的条件需要两边网络都是锥形NAT(或者至少一端网络是锥形NAT),具体可以参考

https://blog.csdn.net/h_armony/article/details/45167975

里面有给出各种NAT说明:

有公网IP的宽带:比如联通的ADSL,这类宽带会给每个用户分配一个公网IP,所以其NAT类型取决于用户所选用的路由器,大部分家用路由器都是端口限制锥型NAT;

无公网IP的宽带:比如宽带通,这类宽带给用户分配的是局域网IP,连接公网的NAT是运营商的,一般都是对称型NAT;

移动互联网:跟“无公网IP的宽带”类似,分配给手机的是局域网IP,出口基本都是对称型NAT;

大公司路由器:大部分都把路由器配置成对称型NAT。

这边使用VS2010 C#实现:

服务端代码:

 static void Main(string[] args)
        {
            int port = 555;
            IPEndPoint ipe = new IPEndPoint(IPAddress.Any, port); 
            Socket sSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            sSocket.Bind(ipe);
            sSocket.Listen(100);
            Console.WriteLine("监听已经打开,请等待");
             
            while (true)
            {
                Socket serverSocket1 = sSocket.Accept();
                Console.WriteLine("连接已经建立");
                string recStr = "";
                byte[] recByte = new byte[4096];
                int bytes = serverSocket1.Receive(recByte);
                IPEndPoint ep1 = (IPEndPoint)serverSocket1.RemoteEndPoint;
                Console.WriteLine(" from {0}", ep1.ToString()); 
                recStr = Encoding.ASCII.GetString(recByte, 0, bytes);
                Console.WriteLine("客户端1:{0}", recStr);

                Socket serverSocket2 = sSocket.Accept();
                bytes = serverSocket2.Receive(recByte);
                IPEndPoint ep2 = (IPEndPoint)serverSocket2.RemoteEndPoint;
                Console.WriteLine(" from {0}", ep2.ToString());
                recStr = Encoding.ASCII.GetString(recByte, 0, bytes);
                Console.WriteLine("客户端2:{0}", recStr);


                byte[] sendByte =Encoding.ASCII.GetBytes(ep1.ToString() + ":" + ep2.ToString());  
                serverSocket1.Send(sendByte, sendByte.Length, 0);

                sendByte = Encoding.ASCII.GetBytes(ep2.ToString() + ":" + ep1.ToString());  
                serverSocket2.Send(sendByte, sendByte.Length, 0);

                serverSocket1.Close();
                serverSocket2.Close();
            } 
              
        }

功能:两边客户端连接服务器后将映射的外网IP和端口号传给双方。

客户端代码

static void Main(string[] args)
{ 
string host = "115.21.X.X";//服务端IP地址
            int port = 555;
            Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//设置端口可复用
            clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            //连接服务端
            clientSocket.Connect(host, port);
            Console.WriteLine("Connect:" + host + "  " + port);

            string data = "hello,Server!";
            clientSocket.Send(Encoding.ASCII.GetBytes(data));
            Console.WriteLine("Send:" + data);
            byte[] recBytes = new byte[100];
            //获取到双方的ip及端口号
            int bytes = clientSocket.Receive(recBytes, recBytes.Length, 0);
           string result = Encoding.ASCII.GetString(recBytes, 0, bytes);
           Console.WriteLine("Recv:" +result);
           clientSocket.Close();

           string[] ips = result.Split(':'); 
           int myPort = Convert.ToInt32(ips[1]);
           string otherIp = ips[2];
           int otherPort = Convert.ToInt32(ips[3]);


           Socket mySocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
           mySocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            //绑定到之前连通过的端口号
           IPEndPoint ipe = new IPEndPoint(IPAddress.Any, Convert.ToInt32(myPort));
           mySocket.Bind(ipe);
            //尝试5次连接
           for (int j = 0; j < 5; j++)
           {
               try
               {
                   mySocket.Connect(otherIp, otherPort);
                   Console.WriteLine("Connect:成功{0},{1}", otherIp,otherPort);
                   break;
               }
               catch (Exception)
               {
                   Console.WriteLine("Connect:失败");
                    // otherPort++;//如果是对称NAT,则有可能客户端的端口号已经改变,正常有规律的应该是顺序加1,可以尝试+1再试(我使用手机热点连接的时候端口号就变成+1的了)除非是碰到随机端口,那就不行了。
               }

           }
           while (true)
           {
               mySocket.Send(Encoding.ASCII.GetBytes("hello,the other client!"));

               byte[] recv = new byte[4096];
               int len = mySocket.Receive(recv, recv.Length, 0);
               result = Encoding.ASCII.GetString(recv, 0, len);
               Console.WriteLine("recv :" + result);

               Thread.Sleep(1000); 
           }
}

另一边客户端也一样。连接服务器后,可以绑定之前的端口号复用,但如果碰到一端是对称NAT时每次使用端口号会不一样时,这样就得通过预测下次可能使用的端口号来连通。如:使用手机热点网络连接服务器时,获取到它使用的端口号是56324,等到下一次客户端互相连接使用56325才连上。

 

你可能感兴趣的:(C#代码实现TCP穿透(打洞))