在上一小節中瞭解到了經過瀏覽器自帶的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 }
右邊客戶端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 }
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