?內(nèi)網(wǎng)之間實(shí)現(xiàn)TCP通訊需要用到內(nèi)網(wǎng)穿透技術(shù),具體原理網(wǎng)上都有,參考:
https://blog.csdn.net/leisure512/article/details/4900191
https://blog.csdn.net/aaron133/article/details/79206257
TCP穿透成功的條件需要兩邊網(wǎng)絡(luò)都是錐形NAT(或者至少一端網(wǎng)絡(luò)是錐形NAT),具體可以參考
https://blog.csdn.net/h_armony/article/details/45167975
里面有給出各種NAT說明:
有公網(wǎng)IP的寬帶:比如聯(lián)通的ADSL,這類寬帶會給每個(gè)用戶分配一個(gè)公網(wǎng)IP,所以其NAT類型取決于用戶所選用的路由器,大部分家用路由器都是端口限制錐型NAT;
無公網(wǎng)IP的寬帶:比如寬帶通,這類寬帶給用戶分配的是局域網(wǎng)IP,連接公網(wǎng)的NAT是運(yùn)營商的,一般都是對稱型NAT;
移動互聯(lián)網(wǎng):跟“無公網(wǎng)IP的寬帶”類似,分配給手機(jī)的是局域網(wǎng)IP,出口基本都是對稱型NAT;
大公司路由器:大部分都把路由器配置成對稱型NAT。
這邊使用VS2010 C#實(shí)現(xiàn):
服務(wù)端代碼:
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(“監(jiān)聽已經(jīng)打開,請等待”);
while (true)
{
Socket serverSocket1 = sSocket.Accept();
Console.WriteLine('連接已經(jīng)建立');
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();
}
}
功能:兩邊客戶端連接服務(wù)器后將映射的外網(wǎng)IP和端口號傳給雙方。
客戶端代碼
static void Main(string[] args) { string host = “115.21.X.X”;//服務(wù)端IP地址 int port = 555; Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //設(shè)置端口可復(fù)用 clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); //連接服務(wù)端 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,則有可能客戶端的端口號已經(jīng)改變,正常有規(guī)律的應(yīng)該是順序加1,可以嘗試+1再試(我使用手機(jī)熱點(diǎn)連接的時(shí)候端口號就變成+1的了)除非是碰到隨機(jī)端口,那就不行了。
}
}
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);
}
}
另一邊客戶端也一樣。連接服務(wù)器后,可以綁定之前的端口號復(fù)用,但如果碰到一端是對稱NAT時(shí)每次使用端口號會不一樣時(shí),這樣就得通過預(yù)測下次可能使用的端口號來連通。如:使用手機(jī)熱點(diǎn)網(wǎng)絡(luò)連接服務(wù)器時(shí),獲取到它使用的端口號是56324,等到下一次客戶端互相連接使用56325才連上。 ?
|