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