udp穿透簡單講解和實現(Java)

  在上一小節中瞭解到了經過瀏覽器自帶的Webrtc功能來實現P2P視頻聊天。在HTML5尚未普及和制定Webrtc標準的前提下,若是要在手機裏進行視頻實時對話等包括其餘功能的話,仍是要本身實現,還比較好擴展。因此本次要了解一下udp進行穿透(打洞)。html

仍是進入正題吧,瞭解P2P。java

1. 原理bootstrap

  關於原理網上隨便就能夠找到好多資料了。大部分都是講解原理的,還配了圖,仍是不錯的。這裏不細說。瀏覽器

2. 代碼講解緩存

  本次使用Java語言。網絡框架使用Netty4, 其實這些都是次要的,原理看懂纔是關鍵。服務器

服務器代碼EchoServer.java網絡

 1 package com.jieli.nat.echo;
 2 
 3 import io.netty.bootstrap.Bootstrap;
 4 import io.netty.channel.ChannelOption;
 5 import io.netty.channel.EventLoopGroup;
 6 import io.netty.channel.nio.NioEventLoopGroup;
 7 import io.netty.channel.socket.nio.NioDatagramChannel;
 8 
 9 public class EchoServer {
10     
11     public static void main(String[] args) {
12         Bootstrap b = new Bootstrap();
13         EventLoopGroup group = new NioEventLoopGroup();
14         try {
15             b.group(group)
16              .channel(NioDatagramChannel.class)
17              .option(ChannelOption.SO_BROADCAST, true)
18              .handler(new EchoServerHandler());
19             
20             b.bind(7402).sync().channel().closeFuture().await();
21         } catch (Exception e) {
22             e.printStackTrace();
23         } finally{
24             group.shutdownGracefully();
25         }
26         
27     }
28 }

服務器代碼EchoServerHandler.java併發

 1 package com.jieli.nat.echo;
 2 
 3 import java.net.InetAddress;
 4 import java.net.InetSocketAddress;
 5 
 6 import io.netty.buffer.ByteBuf;
 7 import io.netty.buffer.Unpooled;
 8 import io.netty.channel.ChannelHandlerContext;
 9 import io.netty.channel.SimpleChannelInboundHandler;
10 import io.netty.channel.socket.DatagramPacket;
11 
12 public class EchoServerHandler extends SimpleChannelInboundHandler<DatagramPacket>{
13 
14     boolean flag = false;
15     InetSocketAddress addr1 = null;
16     InetSocketAddress addr2 = null;
17     /**
18      * channelRead0 是對每一個發送過來的UDP包進行處理
19      */
20     @Override
21     protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet)
22             throws Exception {
23         ByteBuf buf = (ByteBuf) packet.copy().content();
24         byte[] req = new byte[buf.readableBytes()];
25         buf.readBytes(req);
26         String str = new String(req, "UTF-8");
27         if(str.equalsIgnoreCase("L")){
28             //保存到addr1中 併發送addr2
29             addr1 = packet.sender();
30             System.out.println("L 命令, 保存到addr1中 ");
31         }else if(str.equalsIgnoreCase("R")){
32             //保存到addr2中 併發送addr1
33             addr2 = packet.sender();
34             System.out.println("R 命令, 保存到addr2中 ");
35         }else if(str.equalsIgnoreCase("M")){
36             //addr1 -> addr2
37             String remot = "A " + addr2.getAddress().toString().replace("/", "")
38                     +" "+addr2.getPort();
39             ctx.writeAndFlush(new DatagramPacket(
40                     Unpooled.copiedBuffer(remot.getBytes()), addr1));
41             //addr2 -> addr1
42             remot = "A " + addr1.getAddress().toString().replace("/", "")
43                     +" "+addr1.getPort();
44             ctx.writeAndFlush(new DatagramPacket(
45                     Unpooled.copiedBuffer(remot.getBytes()), addr2));
46             System.out.println("M 命令");
47         }
48         
49     }
50 
51     @Override
52     public void channelActive(ChannelHandlerContext ctx) throws Exception {
53         System.out.println("服務器啓動...");
54 
55         super.channelActive(ctx);
56     }
57 }

左邊客戶端EchoClient.java框架

 1 package com.jieli.nat.echo;
 2 
 3 import io.netty.bootstrap.Bootstrap;
 4 import io.netty.channel.ChannelOption;
 5 import io.netty.channel.EventLoopGroup;
 6 import io.netty.channel.nio.NioEventLoopGroup;
 7 import io.netty.channel.socket.nio.NioDatagramChannel;
 8 
 9 /**
10  * 模擬P2P客戶端
11  * @author 
12  *
13  */
14 public class EchoClient{
15     
16     public static void main(String[] args) {
17         int port = 7778;
18         if(args.length != 0){
19             port = Integer.parseInt(args[0]);
20         }
21         Bootstrap b = new Bootstrap();
22         EventLoopGroup group = new NioEventLoopGroup();
23         try {
24             b.group(group)
25              .channel(NioDatagramChannel.class)
26              .option(ChannelOption.SO_BROADCAST, true)
27              .handler(new EchoClientHandler());
28             
29             b.bind(port).sync().channel().closeFuture().await();
30         } catch (Exception e) {
31             e.printStackTrace();
32         } finally{
33             group.shutdownGracefully();
34         }
35     }
36 }

左邊客戶端EchoClientHandler.javasocket

 1 package com.jieli.nat.echo;
 2 
 3 import java.net.InetSocketAddress;
 4 import java.util.Vector;
 5 
 6 import io.netty.buffer.ByteBuf;
 7 import io.netty.buffer.Unpooled;
 8 import io.netty.channel.ChannelHandlerContext;
 9 import io.netty.channel.SimpleChannelInboundHandler;
10 import io.netty.channel.socket.DatagramPacket;
11 
12 //L
13 public class EchoClientHandler extends SimpleChannelInboundHandler<DatagramPacket>{
14     
15     @Override
16     protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet)
17             throws Exception {
18         //服務器推送對方IP和PORT
19         ByteBuf buf = (ByteBuf) packet.copy().content();
20         byte[] req = new byte[buf.readableBytes()];
21         buf.readBytes(req);
22         String str = new String(req, "UTF-8");
23         String[] list = str.split(" ");
24         //若是是A 則發送
25         if(list[0].equals("A")){
26             String ip = list[1];
27             String port = list[2];
28             ctx.writeAndFlush(new DatagramPacket(
29                     Unpooled.copiedBuffer("打洞信息".getBytes()), new InetSocketAddress(ip, Integer.parseInt(port))));
30             Thread.sleep(1000);
31             ctx.writeAndFlush(new DatagramPacket(
32                     Unpooled.copiedBuffer("P2P info..".getBytes()), new InetSocketAddress(ip, Integer.parseInt(port))));
33         }
34         System.out.println("接收到的信息:" + str);
35     }
36     
37     @Override
38     public void channelActive(ChannelHandlerContext ctx) throws Exception {
39         System.out.println("客戶端向服務器發送本身的IP和PORT");
40         ctx.writeAndFlush(new DatagramPacket(
41                 Unpooled.copiedBuffer("L".getBytes()), 
42                 new InetSocketAddress("183.1.1.1", 7402)));
43         super.channelActive(ctx);
44     }
45 }

右邊客戶端EchoClient2.java

 1 package com.jieli.nat.echo;
 2 
 3 import io.netty.bootstrap.Bootstrap;
 4 import io.netty.channel.ChannelOption;
 5 import io.netty.channel.EventLoopGroup;
 6 import io.netty.channel.nio.NioEventLoopGroup;
 7 import io.netty.channel.socket.nio.NioDatagramChannel;
 8 
 9 /**
10  * 模擬P2P客戶端
11  * @author 
12  *
13  */
14 public class EchoClient2{
15     
16     public static void main(String[] args) {
17         Bootstrap b = new Bootstrap();
18         EventLoopGroup group = new NioEventLoopGroup();
19         try {
20             b.group(group)
21              .channel(NioDatagramChannel.class)
22              .option(ChannelOption.SO_BROADCAST, true)
23              .handler(new EchoClientHandler2());
24             
25             b.bind(7779).sync().channel().closeFuture().await();
26         } catch (Exception e) {
27             e.printStackTrace();
28         } finally{
29             group.shutdownGracefully();
30         }
31     }
32 }
View Code

右邊客戶端EchoClientHandler2.java

 1 package com.jieli.nat.echo;
 2 
 3 import java.net.InetSocketAddress;
 4 import java.util.Vector;
 5 
 6 import io.netty.buffer.ByteBuf;
 7 import io.netty.buffer.Unpooled;
 8 import io.netty.channel.ChannelHandlerContext;
 9 import io.netty.channel.SimpleChannelInboundHandler;
10 import io.netty.channel.socket.DatagramPacket;
11 
12 public class EchoClientHandler2 extends SimpleChannelInboundHandler<DatagramPacket>{
13 
14     @Override
15     protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet)
16             throws Exception {
17         //服務器推送對方IP和PORT
18         ByteBuf buf = (ByteBuf) packet.copy().content();
19         byte[] req = new byte[buf.readableBytes()];
20         buf.readBytes(req);
21         String str = new String(req, "UTF-8");
22         String[] list = str.split(" ");
23         //若是是A 則發送
24         if(list[0].equals("A")){
25             String ip = list[1];
26             String port = list[2];
27             ctx.writeAndFlush(new DatagramPacket(
28                     Unpooled.copiedBuffer("打洞信息".getBytes()), new InetSocketAddress(ip, Integer.parseInt(port))));
29             Thread.sleep(1000);
30             ctx.writeAndFlush(new DatagramPacket(
31                     Unpooled.copiedBuffer("P2P info..".getBytes()), new InetSocketAddress(ip, Integer.parseInt(port))));
32         }
33         System.out.println("接收到的信息:" + str);
34     }
35     
36     @Override
37     public void channelActive(ChannelHandlerContext ctx) throws Exception {
38         System.out.println("客戶端向服務器發送本身的IP和PORT");
39         ctx.writeAndFlush(new DatagramPacket(
40                 Unpooled.copiedBuffer("R".getBytes()), 
41                 new InetSocketAddress("1831.1.1", 7402)));
42         super.channelActive(ctx);
43     }
44 }
View Code

 

3. 實驗環境模擬

實驗環境:1臺本地主機L,裏面安裝虛擬機L,地址192.168.182.129. 經過路由器183.1.1.54上網。 1臺服務器主機S,服務器地址183.1.1.52:7402, 同時服務器裏安裝虛擬機R,地址10.0.2.15 .因爲外網地址只有兩個,因此這能這樣測試。經過虛擬機也是能夠模擬出測試環境的。  圖示以下:

三臺測試機ip以下

 

三臺測試機器分別啓動

而後經過第三方工具發送一個M指定到服務器

通常路由器的緩存會保存一小段時間,具體跟路由器有關。

關於Client R會少接收到一個"打洞消息"這個信息。不是由於UDP的丟包,是Client L 發送的打洞命令。簡單說一下。一開始ClientL發送一個UDP到Server,此時ClientL的路由器會保留這樣的一條記錄(ClientL:IP:Port->Server:IP:Port) 因此Server:IP:Port發送過來的信息,ClientL路由器沒有進行攔截,因此能夠接收穫得。可是ClientR:IP:Port發送過來的消息在ClientL的路由器上是沒有這一條記錄的,因此會被拒絕。此時ClientL主動發送一條打洞消息(ClientL:IP:Port->ClientR:IP:Port), 使ClientL路由器保存一條記錄。使ClientR能夠經過指定的IP:Port發送信息過來。不過ClientL的這條打洞信息就不必定能準確的發送到ClientR。緣由就是,同理,ClientR路由器上沒有ClientL的記錄。

因爲ClientL ClientR路由器上都沒有雙方的IP:port,因此經過這樣的打洞過程。

我以爲我這樣描述仍是比較難懂的。若是還不瞭解,請另外參考其餘網上資料。

還有一個就是搭建這樣的測試環境仍是比較麻煩的。注意若是你只有一臺電腦,而後搭建成下面這種測試環境,通常是不行的。由於ClientL和ClientR是經過183.1.1.52路由器進行數據的P2P傳輸,通常路由器會拒絕掉這種迴路的UDP包。

這個時候就要進行內網的穿透了。這個就要像我上一篇博客裏面的Webrtc是如何通訊同樣的了,要經過信令來交換雙方信息。

就是發送包括本身內網的全部IP,支持TCPUDP等其餘信息封裝成信令發送到服務器而後轉發到另外一端的客戶端。使客戶端能夠對多個IP:Port進行嘗試性鏈接。這個具體的就不展開了。

4.多說兩句

  最近智能傢俱比較火,其中有一類網絡攝像頭。也是咱們公司準備作的一款產品。我簡單作了一下前期的預研。目前的一些傳統的行業所作的網絡攝像頭,大部分是基於局域網的網絡攝像頭,就是隻能在自家的路由器上經過手機查看。這類產品,我以爲很難進入普通家庭,由於普通家庭也就那麼不到100平方的房子,這種網絡攝像頭的就體現不是很好了。與普通的監控就是解決了佈線的問題了。其餘到沒有什麼提高。

  還有一類是互聯網行業作的網絡攝像頭。小米、360、百度等都有作這類型的網絡攝像頭。這類型的公司靠本身強大的雲存儲能力來實現。對這幾個產品作了簡單的瞭解,它們是支持本地存儲,同時複製一份到雲盤上。而後移動端(手機)是經過訪問雲盤裏面的視頻來實現監控的。這樣雖然有一小段時間的延時,可是這樣的效果仍是不錯的。你想,在監控某個地方,攝像頭區域通常畫面是不會發生太大的變化的,一個小時裏面也就那麼幾個畫面是要看到的。假使一段15分鐘的視頻,通過分析,獲得下面這樣。

  而後拖動到高亮的滑動條,高亮的地方,表示視頻畫面變更較大。這樣就比較有針對性,也方便了客戶了。還有重要的一點放在網盤,隨時隨地能夠查看。可是也有一點就是隱私問題比較麻煩。其餘的還有不少就不展開說明了。

  做爲一個小廠,同時做爲一名小兵,暫時還不知道公司要作哪一種類型的,上級只是讓我瞭解點對點穿透。我猜應該是在家裏有個攝像頭監控,數據保存在本地。網絡部分是移動端發起鏈接到攝像頭,實行點對點的實時監控和讀取攝像頭本地存儲的視頻回放,全程只是通過服務器進行命令控制。視頻走P2P(走不通應該是轉發,這個還不知道。會不會提示當前網絡不支持這種提示啊?期待!!畢竟若是轉發視頻的話很麻煩,很佔資源),視頻保存本地。我猜目前公司應該是作成這個樣子的。(公司非互聯網公司,沒有那麼好的*aaS平臺)

 

參考資料:

 

本文地址: http://www.cnblogs.com/wunaozai/p/5545150.html 

相關文章
相關標籤/搜索