c#用TCP/IP协议打孔NAT网络

Posted

技术标签:

【中文标题】c#用TCP/IP协议打孔NAT网络【英文标题】:c # punch NAT network with TCP / IP protocol 【发布时间】:2020-04-26 11:44:10 【问题描述】:

打通 UDP 网络可以正常工作。但是,说到TCP,可能我写错了,但我不是初学者,或者我不明白。

当然,我会缩短一点,我们假设我们已经有了一些微不足道的东西,比如连接到外部服务器 :) 线程之间的数据包同步以及它们的创建、排队、序列化、发送原始字节等等我跳过了,因为它是关于创建 TCP 套接字的元素,而不是什么有效。

我们将为服务器使用 TcpListener 类。

public void InitializeServer(IPAddress address, int port)
        
            try
            
                // 127.0.0.1 accept only local connections, 0.0.0.0 is open for whole internet connections

                listener = new TcpListener(address, port);
                socket = listener.Server;

                // Enable NAT Translation
                listener.AllowNatTraversal(true);

                // Start listening for example 10 client requests.
                listener.Start(listenQueue);

                Debug.Log($"[Lsocket.LocalEndPoint]Server start... ", EDebugLvl.Log);

                OnServerInitialize(true);

                // Enter the listening loop.
                StartListener();
            
            catch (SocketException e)
            
                Debug.LogError($"SocketException: e", EDebugLvl.Error);
                OnServerInitialize(false);
            
        

我们开始倾听

private void StartListener()
        
            Debug.Log("\nWaiting for a connection... ");
            listener.BeginAcceptTcpClient(AcceptCallback, listener);
        

服务器收到连接后,我们创建一个新的socket

private void AcceptCallback(IAsyncResult ar)
        
            TcpListener server = (TcpListener)ar.AsyncState;
            TcpClient newClient = null;

            try
            
                newClient = server.EndAcceptTcpClient(ar);
            
            catch (Exception e)
            
                Debug.LogError(e.ToString());
            

            if (newClient != null && newClient.Connected)
            

                //...

                client.StartRead();
            

            //Loop
            StartListener();
        

我们在客户端新建一个socket并尝试建立连接

public void Connect(IPEndPoint remote, IPEndPoint bind = null, bool reuseAddress = false)
        
            if (bind == null)
            
                client = new TcpClient();
            
            else
            
                client = new TcpClient(bind);
            

            socket = client.Client;

            if (reuseAddress)
            
                socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, reuseAddress);
                //It throws me a error SocketOption so im comment this.
                //socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseUnicastPort, reuseAddress);
            

            client.BeginConnect(remote.Address, remote.Port, ConnectCallback, null);
        

连接正常,数据传输没有任何问题。

不幸的是,正如我们所知,我们必须启动一个新的套接字,监听与连接服务器时创建的地址和端口相同的地址和端口。我为每个客户都这样做。

public void StartHost(Client server)
        
            if (server != null && server.socket.Connected)
            
                IPEndPoint localHost = (IPEndPoint)server.socket.LocalEndPoint;
                InitializeHost(localHost.Address, localHost.Port);
            
        
public void InitializeHost(IPAddress address, int port, bool reuse = false)
        
            try
            
                listener = new TcpListener(address, port);
                socket = listener.Server;

                if (reuse)
                
                    socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
                    socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseUnicastPort, true);
                

                // Enable NAT Translation
                listener.AllowNatTraversal(true);

                // Start listening for example 10 client requests.
                listener.Start(listenQueue);

                Debug.Log($"\n[Lsocket.LocalEndPoint]Host start... ", EDebugLvl.Log);

                OnServerInitialize(true);

                // Enter the listening loop.
                StartListener();
            
            catch (SocketException e)
            
                Debug.LogError($"SocketException: e", EDebugLvl.Error);
                OnServerInitialize(false);
            
        
private void StartListener()
        
            Debug.Log("\nWaiting for a connection... ");
            listener.BeginAcceptTcpClient(AcceptCallback, listener);
        
private void AcceptCallback(IAsyncResult ar)
        
            TcpListener server = (TcpListener)ar.AsyncState;
            TcpClient newClient = null;

            try
            
                newClient = server.EndAcceptTcpClient(ar);
            
            catch (Exception e)
            
                Console.WriteLine(e.ToString());
            

            if (newClient != null && newClient.Connected)
            

                //...

                client.StartRead();
            

            //Loop
            StartListener();
        

所以,当他们到处写...客户端“B”向服务器发送一个数据包想要建立连接时,服务器向客户端“A”发送有关客户端“B”的信息,反之亦然

然后他们都尝试连接新的套接字?没问题...

public void Connect(IPEndPoint remote, IPEndPoint bind = null, bool reuseAddress = false)
        
            if (bind == null)
            
                client = new TcpClient();
            
            else
            
                client = new TcpClient(bind);
            

            socket = client.Client;

            if (reuseAddress)
            
                socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, reuseAddress);
                //socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseUnicastPort, reuseAddress);
            

            client.BeginConnect(remote.Address, remote.Port, ConnectCallback, null);
        
private void ConnectCallback(IAsyncResult ar)
        
            try
            
                client.EndConnect(ar);
            
            catch (Exception e)
            
                Debug.LogError(e.ToString(), EDebugLvl.ConnectionError);
            
            if (client.Connected)
            
                Debug.Log($"[Psocket.RemoteEndPoint, Lsocket.LocalEndPoint]Connected", EDebugLvl.ConnectionLog);
                stream = new NetworkStream(socket, FileAccess.ReadWrite, true);
                StartRead();
            
            ConnectedComplete(this, socket.Connected);
        

无论我尝试多少次,连接都会被拒绝......地址到处都匹配但它不起作用,所以在这种情况下我没有什么可写的,特别是因为 UDP 对我有用。

我写的内容只适用于同一个 NAT 网络。不幸的是,我注意到在同一个 NAT 上创建了两个连接。一种是尝试将新的套接字 A 连接到 B 的结果,另一种是接收到从 B 到 A 的新连接的结果,因此每个客户端都有一个由本地地址连接的不必要的套接字。所以所有的 NAT TCP / IP Punch NAT 都对我不起作用。我实际上可以使用 UDP,但我真的需要 TCP。我在空闲时间已经坐了几个月,但我无法从代码中找到一个例子,而不是有很多理论。 8年积累了很多知识,2年开始用socket写应用,终于要打网了。

为什么我不使用现成的解决方案?我需要我自己的,它只使用 UDP 和 TCP 完全开放,因为一些目标设备只支持这些协议。我也使用了 Socket 类,但是这个没有给我一个工作副本。

也许有人能帮助我,对此我将不胜感激,当然这篇文章也会帮助其他人理解这一点。

问候 屋大维

【问题讨论】:

您假设两个 NAT 将重用相同的外部端口。你肯定知道吗?根据我的经验,这并不常见。一个或两个 NAT 也完全有可能防止打孔。 我知道端口的末端可能会改变 XXXX 1-9 但它并没有随着我改变它仍然是相同的至少我没有注意到任何东西。 90% 的路由器支持打孔,或者至少他们写:D 我假设如果我管理 UDP,那么为什么我不能通过 TCP 写错东西。因为如果它不起作用并且我不能使用 UPnP。那么如何获得稳定的连接呢?好的,我可以使用将接收和发送数据包的网关,但我真的只有一种解决方案可以将 10 个用户连接到 NAT 网络后面的另一台主机? :// 你知道你的服务器看到的端口没有改变,但是你怎么知道你的NAT没有为不同的地址使用不同的端口呢?更多详情请见***.com/questions/2443471/…;您不太可能取得任何成功,这是一种在 90 年代 / 2000 年代初期的某些时候才勉强可行的技术,并且随着 NAT 技术的进步,它变得越来越不可靠。 UPnP 现在得到广泛支持,而且几乎总是更好的选择。 【参考方案1】:

NAT 打孔仅适用于 UDP,即使这样也是一种 hack。

NAT 防火墙实现将在看到初始 SYN 数据包离开网络时开始跟踪 TCP 流。要捕获传入的 TCP 流,您需要在路由器上创建传入规则,这是无法解决的。如果路由器支持 UPnP,您可以要求它为您动态创建该规则。

由于 UDP 没有等效的 SYN 数据包,路由器将开始跟踪任何传出数据包的流。这就是 NAT 打孔有效的原因。如果两个端点都在 NAT 之后,并且只是假设链接可以工作。两者都可以开始互相发送UDP数据包。路由器将添加连接状态,并将传入的数据包映射到每个端点。

【讨论】:

以上是关于c#用TCP/IP协议打孔NAT网络的主要内容,如果未能解决你的问题,请参考以下文章

tcp和ip有啥区别?

TCP/IP协议和OSI七层模型说明

TCP/IP协议简介

网络协议和管理

tcp/ip是指啥?有啥用?上哪可以设置?

TCP/IP协议和HTTP协议