因為當前 IPV4地址的缺乏 ,nat、防火牆的中介設備和不對稱尋址建立起來的 p2p通信機制造成了地址訪問的問題。
在 internet最初體系結構中,每個節點都有全球唯一的 ip地址,能夠直接通信。可是隨着節點的增多, ip地址使用緊張,他們需要中介設備如 nat連在一起。
私有網絡中的節點可以直接連接到相同私有網絡中的其他節點,也可以連接到全局地址空間中擁有全球唯一 ip地址的節點。。然而 nat通常只允許臨時的向外連接申請,對於向內的申請會拒絕。這就造成了在 natA內網中的節點 A連接 natB內網中的節點 B時連接申請報到 natB時就被阻止了。此時我們需要的就是穿越技術。。。
總體來說穿越技術是利用一個公共服務器中轉,使節點 A、 B都連接到中轉服務器 S之后,通過 S中轉 A發送到 B的數據報或者是中轉連接申請,,使 A、 B對於 natA和 natB來說都是向外申請。。。
1、 中轉數據報: A、 B都先向外與服務器 S建立接連,然后通過 S中轉 A、 B之間的數據報。。
2、 反向連接:當 A、 B都與 S建立了連接,並且只有一個節點在 nat之后(假設 A在 natA之后)。。當 B向 A申請連接時,申請背 natA拒絕。 B可以向 S提出申請要與 A建立連接,然后 S向 A發出指令,通知 A主動向 B申請建立連接。。
【 UDP打洞】
1、 A、 B在同一個 nat之后:
用戶 A讓 S做介紹人來與 B建立對話
(1) A向 S發送一個消息請求與 B建立連接
(2) S使用 B的公共終端( 155.99.25.11: 62005)和私有終端( 10.1.1.3)響應 A
(3) 同時 S也想 B發送 A的公共終端( 155.99.25.11: 62000)和私有終端( 10.0.0.1),但是發送到公共終端的消息不一定能達到 B取決於 NAT是否支持“發夾”轉化(回環轉化)
(4) 如果 nat支持發夾轉化的話,應用程序就可以免除私有和共有終端都要試圖連接的復雜性。。
2、不同 NAT后面的節點
(1) 注冊, A、 B都想服務器 S注冊 natA安排了 62000端口用作 A和 S對話使用, natB安排了 31000端口用作 B和 S對話使用, A向 S的注冊消息中報告了自己的私有終端 10.0.0.1: 4321這種情況下 A的公共終端是 155.99.25.11: 62000,同理 B的私有終端 10.1.1.3: 4321和公共終端 136.76.29.7: 31000
(2) A發送請求消息到 S,請求與 B建立連接,作為響應 S向 A發送了 B的私有終端和公共終端也向 B發送了 A的私有和公共終端。
(3) 既然 A、 B處在不同的子網中,那么 A、 B的私有終端是不能公共路由的,發送的消息肯能會發到自己子網中的 ip中(應為不同子網中的私有 ip可以相同)
(4) 當從 A發向 B的第一個消息到達 natA時, natA注意到這是一個新的外出會話, natA看到源地址是子網中地址,而目的地址是外網地址,所以 natA將從私有終端 10.0.0.1: 4321的外出會話轉化到對應公共終端 155.99.25.11: 62000,這樣 A的第一個到 B的公共終端的外出會話消息就在 natA上“打了一個洞”。新的 UDP會話由 A的私有網絡上的終端 10.0.0.1: 4321/138.76.29.7: 31000和 internet上的公共終端 155.99.25.11: 62000/138.76.29.7:31000標識,同理 B也建立了對 A的私有、公共連接標識。
(5) 如果 A發向 B的公共終端的消息在 B發向 A的第一個消息穿過 B自己的 natB之前到達了 natB的話, natB會認為 A的內入消息是禁止的,丟棄 B的請求消息,但是 B的請求消息在 natB上為 A打了一洞,此時洞雙向打開,通信可以進行下去了。。。
3、多級 NAT后面的節點:
( 1) A、 B都建立與 S的向外連接
( 2)最終連接目的:
Aà B 10.0.0.1à 10.0.1.2:55000
Bà A 10.0.0.3à 10.0.1.1:45000
( 3)但是在此時 A、 B無法知道偽公眾終端 10.0.1.2:55000和 10.0.1.1:45000。 S只看到了 155.99.25.11: 52000和 155.99.25.11: 62005。
( 4)此時相應的 A、 B也只知道 155.99.25.11: 52000和 155.99.25.11: 62005
( 5)只能依賴 natC的發夾轉化。
當 A-à B,即 10.0.0.1—>155.99.25.11: 62005時 natA將數據報中源地址 10.0.0.1轉化為 10.0.1.1然后發送到 natC,當 natC發現目的地址 ip是 155.99.25.11是自己轉化過的 ip后, natC就會轉化數據報中的源地址和目的地址,再發送到私有網絡中。 155.99.25.11: 62000--à 10.0.1.2: 55000
( 6)當數據報到 B私有網絡時,同樣方法進行轉化。。
另外,局域網內數據共享,可以采用組播方式。
UDPServer.java:
- package org.iaiai.test;
- import java.net.DatagramPacket;
- import java.net.DatagramSocket;
- import java.net.InetAddress;
- /**
- *
- * <br/>
- * Title: UDPServer.java<br/>
- * E-Mail: 176291935@qq.com<br/>
- * QQ: 176291935<br/>
- * Http: iaiai.iteye.com<br/>
- * Create time: 2013-1-29 上午11:11:56<br/>
- * <br/>
- * @author 丸子
- * @version 0.0.1
- */
- public class UDPServer {
- public static void main(String[] args) {
- try {
- DatagramSocket server = new DatagramSocket(2008);
- byte[] buf = new byte[1024];
- DatagramPacket packet = new DatagramPacket(buf, buf.length);
- String sendMessage132 = "";
- String sendMessage129 = "";
- int port132 = 0;
- int port129 = 0;
- InetAddress address132 = null;
- InetAddress address129 = null;
- for (;;) {
- server.receive(packet);
- String receiveMessage = new String(packet.getData(), 0, packet.getLength());
- System.out.println(receiveMessage);
- //接收到clientA
- if (receiveMessage.contains("132")) {
- port132 = packet.getPort();
- address132 = packet.getAddress();
- sendMessage132 = "host:" + address132.getHostAddress() + ",port:" + port132;
- }
- //接收到clientB
- if (receiveMessage.contains("129")) {
- port129 = packet.getPort();
- address129 = packet.getAddress();
- sendMessage129 = "host:" + address129.getHostAddress() + ",port:" + port129;
- }
- //兩個都接收到后分別A、B址地交換互發
- if (!sendMessage132.equals("") && !sendMessage129.equals("")) {
- send132(sendMessage129, port132, address132, server);
- send129(sendMessage132, port129, address129, server);
- sendMessage132 = "";
- sendMessage129 = "";
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- private static void send129(String sendMessage132, int port132, InetAddress address132, DatagramSocket server) {
- try {
- byte[] sendBuf = sendMessage132.getBytes();
- DatagramPacket sendPacket = new DatagramPacket(sendBuf, sendBuf.length, address132, port132);
- server.send(sendPacket);
- System.out.println("消息發送成功!");
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- private static void send132(String sendMessage129, int port129, InetAddress address129, DatagramSocket server) {
- try {
- byte[] sendBuf = sendMessage129.getBytes();
- DatagramPacket sendPacket = new DatagramPacket(sendBuf, sendBuf.length, address129, port129);
- server.send(sendPacket);
- System.out.println("消息發送成功!");
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
UDPClientA.java:
- package org.iaiai.test;
- import java.net.DatagramPacket;
- import java.net.DatagramSocket;
- import java.net.InetAddress;
- import java.net.InetSocketAddress;
- import java.net.SocketAddress;
- /**
- *
- * <br/>
- * Title: UDPClientA.java<br/>
- * E-Mail: 176291935@qq.com<br/>
- * QQ: 176291935<br/>
- * Http: iaiai.iteye.com<br/>
- * Create time: 2013-1-29 上午11:11:56<br/>
- * <br/>
- * @author 丸子
- * @version 0.0.1
- */
- public class UDPClientA {
- public static void main(String[] args) {
- try {
- // 向server發起請求
- SocketAddress target = new InetSocketAddress("10.1.11.137", 2008);
- DatagramSocket client = new DatagramSocket();
- String message = "I am UPDClinetA 192.168.85.132";
- byte[] sendbuf = message.getBytes();
- DatagramPacket pack = new DatagramPacket(sendbuf, sendbuf.length, target);
- client.send(pack);
- // 接收請求的回復,可能不是server回復的,有可能來自UPDClientB的請求內
- receive(client);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- //接收請求內容
- private static void receive(DatagramSocket client) {
- try {
- for (;;) {
- byte[] buf = new byte[1024];
- DatagramPacket packet = new DatagramPacket(buf, buf.length);
- client.receive(packet);
- String receiveMessage = new String(packet.getData(), 0, packet.getLength());
- System.out.println(receiveMessage);
- int port = packet.getPort();
- InetAddress address = packet.getAddress();
- String reportMessage = "tks";
- //獲取接收到請問內容后並取到地址與端口,然后用獲取到地址與端口回復內容
- sendMessaage(reportMessage, port, address, client);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- //回復內容
- private static void sendMessaage(String reportMessage, int port, InetAddress address, DatagramSocket client) {
- try {
- byte[] sendBuf = reportMessage.getBytes();
- DatagramPacket sendPacket = new DatagramPacket(sendBuf, sendBuf.length, address, port);
- client.send(sendPacket);
- System.out.println("消息發送成功!");
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
UDPClientB.java
- package org.iaiai.test;
- import java.net.DatagramPacket;
- import java.net.DatagramSocket;
- import java.net.InetAddress;
- import java.net.InetSocketAddress;
- import java.net.SocketAddress;
- /**
- *
- * <br/>
- * Title: UDPClientB.java<br/>
- * E-Mail: 176291935@qq.com<br/>
- * QQ: 176291935<br/>
- * Http: iaiai.iteye.com<br/>
- * Create time: 2013-1-29 上午11:11:56<br/>
- * <br/>
- * @author 丸子
- * @version 0.0.1
- */
- public class UDPClientB {
- public static void main(String[] args) {
- try {
- //向server發起請求
- SocketAddress target = new InetSocketAddress("10.1.11.137", 2008);
- DatagramSocket client = new DatagramSocket();
- String message = "I am UDPClientB 192.168.85.129";
- byte[] sendbuf = message.getBytes();
- DatagramPacket pack = new DatagramPacket(sendbuf, sendbuf.length, target);
- client.send(pack);
- //接收server的回復內容
- byte[] buf = new byte[1024];
- DatagramPacket recpack = new DatagramPacket(buf, buf.length);
- client.receive(recpack);
- //處理server回復的內容,然后向內容中的地址與端口發起請求(打洞)
- String receiveMessage = new String(recpack.getData(), 0, recpack.getLength());
- String[] params = receiveMessage.split(",");
- String host = params[0].substring(5);
- String port = params[1].substring(5);
- System.out.println(host + ":" + port);
- sendMessage(host, port, client);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- //向UPDClientA發起請求(在NAT上打孔)
- private static void sendMessage(String host, String port, DatagramSocket client) {
- try {
- SocketAddress target = new InetSocketAddress(host, Integer.parseInt(port));
- for (;;) {
- String message = "I am master 192.168.85.129 count test";
- byte[] sendbuf = message.getBytes();
- DatagramPacket pack = new DatagramPacket(sendbuf, sendbuf.length, target);
- client.send(pack);
- //接收UDPClientA回復的內容
- receive(client);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- //收到UDPClientA的回復內容,穿透已完成
- private static void receive(DatagramSocket client) {
- try {
- for (;;) {
- //將接收到的內容打印
- byte[] buf = new byte[1024];
- DatagramPacket recpack = new DatagramPacket(buf, buf.length);
- client.receive(recpack);
- String receiveMessage = new String(recpack.getData(), 0, recpack.getLength());
- System.out.println(receiveMessage);
- //記得重新收地址與端口,然后在以新地址發送內容到UPDClientA,就這樣互發就可以了。
- int port = recpack.getPort();
- InetAddress address = recpack.getAddress();
- String reportMessage = "I am master 192.168.85.129 count test";
- //發送消息
- sendMessage(reportMessage, port, address, client);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- private static void sendMessage(String reportMessage, int port, InetAddress address, DatagramSocket client) {
- try {
- byte[] sendBuf = reportMessage.getBytes();
- DatagramPacket sendPacket = new DatagramPacket(sendBuf, sendBuf.length, address, port);
- client.send(sendPacket);
- System.out.println("send success");
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }