C#TCP打孔-客户端未连接

C#TCP打孔-客户端未连接,c#,tcp,tcpclient,tcplistener,hole-punching,C#,Tcp,Tcpclient,Tcplistener,Hole Punching,我一直在开发一个需要TCP打孔的P2P应用程序 我已经编写了大部分代码(我相信)来让客户端连接,不幸的是,有些事情我做得不对,因为它们无法连接 我提供了我正在使用的C#代码,它是一个C#控制台应用程序,服务器是用JavaScript编写的,使用NodeJS,我不会在这里包含代码,因为我没有任何问题,它只是用来向客户机发送彼此的详细信息 当一个IP被标记为x.x.x.x时,它是对输出的真实公共IP的替换 using System; using System.Text; using System.T

我一直在开发一个需要TCP打孔的P2P应用程序

我已经编写了大部分代码(我相信)来让客户端连接,不幸的是,有些事情我做得不对,因为它们无法连接

我提供了我正在使用的C#代码,它是一个C#控制台应用程序,服务器是用JavaScript编写的,使用NodeJS,我不会在这里包含代码,因为我没有任何问题,它只是用来向客户机发送彼此的详细信息

当一个IP被标记为x.x.x.x时,它是对输出的真实公共IP的替换

using System;
using System.Text;
using System.Threading.Tasks;
using System.Configuration;
using System.Net;
using System.Net.Sockets;
using System.Web.Script.Serialization;

namespace ScreenViewClientTest
{
    class Program
    {
        public static string server_ip;
        public static string server_port;

        public static bool im_a = false;
        public static bool im_b = false;

        public static bool clients_are_connected = false;

        public static void Main(string[] args)
        {
            server_ip = ConfigurationManager.AppSettings["server_ip"];
            server_port = ConfigurationManager.AppSettings["server_port"];

            Console.WriteLine("Connecting to " + server_ip + ":" + server_port + "...");

            TcpClient client = new TcpClient();
            client.Connect(server_ip, int.Parse(server_port));
            if (client.Connected)
            {
                Console.WriteLine("Connection to server was successful!.");
            }

            IPEndPoint local_endpoint = ((IPEndPoint)client.Client.LocalEndPoint);
            string local_ip = local_endpoint.Address.ToString();
            int local_port = local_endpoint.Port;
            client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);

            string message_to_server = "{ \"local_ip\": \"" + local_ip + "\", \"local_port\": " + local_port + "}";
            // send identity message to the server
            client.Client.Send(Encoding.ASCII.GetBytes(message_to_server));
            Console.WriteLine("Identity message (" + message_to_server + ") sent to server...");

            // listen/get a response from the server.
            NetworkStream stream = new NetworkStream(client.Client);
            byte[] bytes = new byte[100]; // we assume length won't be more than 100.
            int length = stream.Read(bytes, 0, 100);
            string initial_response_from_server = "";

            for (var i = 0; i < length; i++)
                initial_response_from_server += Convert.ToChar(bytes[i]);
            Console.WriteLine("Response from Server: " + initial_response_from_server);

            // we assume it's going to take a while for the second client to connect, that's why we don't 
            // have a fixed message length for the initial response. (when implemented this must be changed).

            // try to read the identity response from the server specifying which client I am, (=a= or =b=).
            // we know the response is going to have a fixed length (3).
            bytes = new byte[3];
            length = stream.Read(bytes, 0, 3);
            string identity_response = "My Client Identity is: ";

            for (var i = 0; i < length; i++)
                identity_response += Convert.ToChar(bytes[i]);
            Console.WriteLine(identity_response);

            if (identity_response.IndexOf("=a=") > -1)
            {
                im_a = true;
                Console.WriteLine("I'm A.");
            }
            else
            {
                if (identity_response.IndexOf("=b=") > -1)
                {
                    im_b = true;
                    Console.WriteLine("I'm B.");
                }
            }

            // if we've establibshed that we're a valid client.
            if (im_a || im_b)
            {
                // start listening for second client details
                // this should be changed to check first for a message length header using a fixed length.
                // second client details are returnes as a json string, so we need to desearialize it as an object.
                bytes = new byte[150];
                length = stream.Read(bytes, 0, 150);
                string second_client_details = "";

                for (var i = 0; i < length; i++)
                    second_client_details += Convert.ToChar(bytes[i]);
                Console.WriteLine("Identity of second Client is: " + second_client_details);

                // try and parse the data received from the server (should be the second client's nat address info).
                JavaScriptSerializer serializer = new JavaScriptSerializer();
                Address cliens_2_address = serializer.Deserialize<Address>(second_client_details);
                Console.WriteLine("Client 2 Local Address: " + cliens_2_address.local_ip + ":" + cliens_2_address.local_port.ToString());
                Console.WriteLine("Client 2 Remote Address: " + cliens_2_address.remote_ip + ":" + cliens_2_address.remote_port.ToString());

                // close the connection to the server so the local port can be used.
                if (client.Connected)
                    client.Close();

                // start the listener
                Task.Factory.StartNew(() => listen_on_local_port(local_port));
                // start sending requests to the second clients local endpoint.
                Task.Factory.StartNew(() => connect_to_client(cliens_2_address.local_ip, cliens_2_address.local_port, local_port));
                // start sending requests to the second clients remote endpoint.
                Task.Factory.StartNew(() => connect_to_client(cliens_2_address.remote_ip, cliens_2_address.remote_port, local_port));
                // run the tasks async
                Task.WaitAll();

            }
            // keeps the console window open.
            Console.Read();
        }

        public static void listen_on_local_port(int local_port)
        {
            Console.WriteLine("Startint Listener...");
            // start listening on the local port used to connect to the server for messages from the other client.
            TcpListener server = null;
            try
            {
                // Set the TcpListener on port 13000.
                Int32 port = local_port;

                // TcpListener server = new TcpListener(port);
                server = new TcpListener(IPAddress.Parse("127.0.0.1"), port);

                // Start listening for client requests.
                server.Start();

                // Buffer for reading data
                Byte[] incoming_bytes = new Byte[256];
                String data = null;

                // Enter the listening loop.
                while (!clients_are_connected)
                {
                    // Perform a blocking call to accept requests.
                    // You could also user server.AcceptSocket() here.
                    TcpClient socket = server.AcceptTcpClient();
                    Console.WriteLine("Listening for connections on port: " + ((IPEndPoint)server.LocalEndpoint).Port.ToString() + "... ");
                    // does this really mean that someone connected?
                    if (socket.Connected && socket.Client.Connected)
                    {
                        Console.WriteLine("Someone connected to the socket!." + ((IPEndPoint)socket.Client.RemoteEndPoint).Address + ":" + ((IPEndPoint)socket.Client.RemoteEndPoint).Port.ToString());
                        clients_are_connected = true;
                    }

                    data = null;

                    // Get a stream object for reading and writing
                    NetworkStream net_stream = socket.GetStream();

                    int i;

                    // never seems to come threw to the other client.
                    string msg1 = "hello other person!!!!!!!!!!!!";
                    net_stream.Write(Encoding.ASCII.GetBytes(msg1), 0, msg1.Length);

                    // Loop to receive all the data sent by the client.
                    while ((i = net_stream.Read(incoming_bytes, 0, incoming_bytes.Length)) != 0)
                    {
                        // Translate data bytes to a ASCII string.
                        data = System.Text.Encoding.ASCII.GetString(incoming_bytes, 0, i);
                        Console.WriteLine("Received: {0}", data);

                        // Process the data sent by the client.
                        data = data.ToUpper();

                        byte[] msg = System.Text.Encoding.ASCII.GetBytes(data);

                        // Send back a response.
                        net_stream.Write(msg, 0, msg.Length);
                        Console.WriteLine("Sent: {0}", data);
                    }

                    // Shutdown and end connection
                    socket.Close();
                }
            }
            catch (SocketException e)
            {
                Console.WriteLine("SocketException: {0}", e);
            }
            finally
            {
                // Stop listening for new clients.
                server.Stop();
            }
        }

        public static void connect_to_client(string ip, int port, int local_port)
        {
            Console.WriteLine("Started trying to connect to Client: " + ip + ":" + port);
            // try to connect to the second client on it's public port and local ip
            while (!clients_are_connected)
            {
                TcpClient hole_punching_client = null;
                try
                {
                    // use local port used to connect to the server to connect yo the client.
                    IPEndPoint local = new IPEndPoint(IPAddress.Parse("127.0.0.1"), local_port);
                    hole_punching_client = new TcpClient("127.0.0.1", local_port);
                    hole_punching_client.Client.SetSocketOption(SocketOptionLevel.Socket,     SocketOptionName.ReuseAddress, true);
                    // when the below line is uncommented there's an error "An attempt was made to access a socket in a way forbidden by its access permissions"
                //hole_punching_client.Client.Bind(local);
                    //Console.WriteLine("Trying to connect to the second client at address: " + ip + ":" + port);
                    Console.WriteLine("Real Hole Punching Client Port is: " + ((IPEndPoint)hole_punching_client.Client.LocalEndPoint).Port.ToString());
                    // connect to the second client using the address provided as parameters.
                    hole_punching_client.Connect(ip, port);
                    if (hole_punching_client.Connected)
                    {
                        Console.WriteLine("Connection to the other client was successful!.");
                        clients_are_connected = true;
                    }
                }
                catch (SocketException se)
                {
                    Console.WriteLine("Socket Exception from Sender: " + se.Message);
                    System.Threading.Thread.Sleep(250);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Exception from Sender: " + ex.Message);
                }
                finally
                {
                    // program crashes when enabled.
                    //if (hole_punching_client.Connected)
                    //{
                    //    hole_punching_client.Close();
                    //}
                }
            }
        }
    }
}
客户B:

Connecting to x.x.x.x:1994...
Connection to server was successful!.
Identity message ({ "local_ip": "10.0.0.4", "local_port": 49754}) sent to server...
Response from Server: Welcome new client!, your remote address is: ::ffff:x.x.x.x:1024.
My Client Identity is: =b=
I'm B.
Identity of second Client is {"local_ip":"192.168.1.137","local_port":52974,"remote_ip":"::ffff:x.x.x.x","remote_port":52974}
Client 2 Local Address: 192.168.1.137:52974
Client 2 Remote Address: ::ffff:x.x.x.x:52974
Starting Listener...
Started trying to connect to Client: 192.168.1.137:52974
Listening for connections on port: 49754...
Someone connected to the socket!.127.0.0.1:49755
Real Hole Punching Client Port is: 49755
Socket Exception from Sender: A connect request was made on an already connected socket
Started trying to connect to Client: ::ffff:x.x.x.x:52974
我首先搞不清楚的是“在已连接的套接字上发出了连接请求”例外

我认为另一个问题可能是将侦听器和客户端绑定到同一个本地端口

需要做的是,在关闭与服务器的连接后,我需要监听与服务器的连接中使用的本地端口(因为这是其他客户端将获得的),同时我还需要尝试连接到其他客户端公共和私有端点,请求来自同一本地端口(我正在收听)

我认为我做得不对,当我尝试绑定时,代码中出现了另一个错误(注释)

在进一步检查之后,我注意到虽然我应该从本地端口发出请求,但请求实际上来自不同的端口

用于连接到服务器的客户端A的本地端口是52974,侦听器实际上使用该端口(客户端A输出行14),但用于尝试连接到另一个客户端的端口在一种情况下是53025(行13),在一种情况下是53024(行17)。(请记住,我们正在尝试连接到第二个本地客户端“and”远程端点,因此需要2次尝试)

您可以在客户端B的输出中看到相同的内容

另一件让我困惑的事情是客户端A输出的第15行和客户端B输出的第13行,它表示有人成功地连接到了两个侦听器(在两个客户端上!)这没有任何意义,因为一旦有人连接到侦听器,就建立了P2P连接,而不需要另一个客户端继续侦听(我使用布尔值clients\u are\u connected来验证这一点)

更重要的是,如果连接远程ip的客户端是一个没有意义的本地ip地址,那么连接是否可能来自同一个客户端?我尝试注释试图连接到另一个客户端的本地端点的代码,因此它只尝试连接到其他客户端的远程端点,但输出是w同样的

如果有任何见解,我将不胜感激


谢谢

我想也许我应该将用于连接到服务器或侦听器中使用的TCP客户端传递到connect_to_Client()方法,这会将其绑定到同一端口吗?如果我理解正确,您将有两个客户端连接到一台服务器。然后服务器共享连接详细信息(IP和端口)在两个相互连接的客户端中,客户端会尝试相互连接?客户端1是否连接到客户端2,反之亦然?@支付服务器用于在两个客户端之间交换地址信息,因为它们都连接到服务器,一旦每个客户端都有其他信息,客户端就会尝试相互连接。确定吗所以客户端使用的是C端的东西?上面的C代码是实际的客户端。唯一缺少的是服务器代码和一个定义地址对象的类,该对象只有4个属性(本地ip、本地端口、远程ip、远程端口),该对象用于将服务器返回的json与其他客户端信息反序列化。我想也许我应该将用于连接到服务器或侦听器中使用的TCP客户端传递到connect_to_Client()方法,是否将其绑定到同一端口?如果我理解正确,您将有两个客户端连接到一个服务器。然后服务器共享连接详细信息(IP和端口)在两个相互连接的客户端中,客户端会尝试相互连接?客户端1是否连接到客户端2,反之亦然?@支付服务器用于在两个客户端之间交换地址信息,因为它们都连接到服务器,一旦每个客户端都有其他信息,客户端就会尝试相互连接。确定吗所以客户端使用的是C端的东西?上面的C端代码是实际的客户端。唯一缺少的是服务器代码和一个类,该类定义了一个地址对象,它只有4个属性(本地ip、本地端口、远程ip、远程端口),该对象用于将服务器返回的json与其他客户端信息反序列化。
Connecting to x.x.x.x:1994...
Connection to server was successful!.
Identity message ({ "local_ip": "10.0.0.4", "local_port": 49754}) sent to server...
Response from Server: Welcome new client!, your remote address is: ::ffff:x.x.x.x:1024.
My Client Identity is: =b=
I'm B.
Identity of second Client is {"local_ip":"192.168.1.137","local_port":52974,"remote_ip":"::ffff:x.x.x.x","remote_port":52974}
Client 2 Local Address: 192.168.1.137:52974
Client 2 Remote Address: ::ffff:x.x.x.x:52974
Starting Listener...
Started trying to connect to Client: 192.168.1.137:52974
Listening for connections on port: 49754...
Someone connected to the socket!.127.0.0.1:49755
Real Hole Punching Client Port is: 49755
Socket Exception from Sender: A connect request was made on an already connected socket
Started trying to connect to Client: ::ffff:x.x.x.x:52974