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才连上。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值