udp穿透简单讲解和实现(Java)
在上一小节中了解到了通过浏览器自带的Webrtc功能来实现P2P视频聊天。在HTML5还没有普及和制定Webrtc标准的前提下,如果要在手机里进行视频实时对话等包括其他功能的话,还是要自己实现,还比较好扩展。所以本次要了解一下udp进行穿透(打洞)。
还是进入正题吧,了解P2P。
1. 原理
关于原理网上随便就可以找到好多资料了。大部分都是讲解原理的,还配了图,还是不错的。这里不细说。
2. 代码讲解
本次使用Java语言。网络框架使用Netty4, 其实这些都是次要的,原理看懂才是关键。
服务器代码EchoServer.java
package com.jieli.nat.echo; import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel; public class EchoServer { public static void main(String[] args) {
Bootstrap b = new Bootstrap();
EventLoopGroup group = new NioEventLoopGroup();
try {
b.group(group)
.channel(NioDatagramChannel.class)
.option(ChannelOption.SO_BROADCAST, true)
.handler(new EchoServerHandler()); b.bind(7402).sync().channel().closeFuture().await();
} catch (Exception e) {
e.printStackTrace();
} finally{
group.shutdownGracefully();
} }
}
服务器代码EchoServerHandler.java
package com.jieli.nat.echo; import java.net.InetAddress;
import java.net.InetSocketAddress; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket; public class EchoServerHandler extends SimpleChannelInboundHandler<DatagramPacket>{ boolean flag = false;
InetSocketAddress addr1 = null;
InetSocketAddress addr2 = null;
/**
* channelRead0 是对每个发送过来的UDP包进行处理
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet)
throws Exception {
ByteBuf buf = (ByteBuf) packet.copy().content();
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String str = new String(req, "UTF-8");
if(str.equalsIgnoreCase("L")){
//保存到addr1中 并发送addr2
addr1 = packet.sender();
System.out.println("L 命令, 保存到addr1中 ");
}else if(str.equalsIgnoreCase("R")){
//保存到addr2中 并发送addr1
addr2 = packet.sender();
System.out.println("R 命令, 保存到addr2中 ");
}else if(str.equalsIgnoreCase("M")){
//addr1 -> addr2
String remot = "A " + addr2.getAddress().toString().replace("/", "")
+" "+addr2.getPort();
ctx.writeAndFlush(new DatagramPacket(
Unpooled.copiedBuffer(remot.getBytes()), addr1));
//addr2 -> addr1
remot = "A " + addr1.getAddress().toString().replace("/", "")
+" "+addr1.getPort();
ctx.writeAndFlush(new DatagramPacket(
Unpooled.copiedBuffer(remot.getBytes()), addr2));
System.out.println("M 命令");
} } @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("服务器启动..."); super.channelActive(ctx);
}
}
左边客户端EchoClient.java
package com.jieli.nat.echo; import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel; /**
* 模拟P2P客户端
* @author
*
*/
public class EchoClient{ public static void main(String[] args) {
int port = 7778;
if(args.length != 0){
port = Integer.parseInt(args[0]);
}
Bootstrap b = new Bootstrap();
EventLoopGroup group = new NioEventLoopGroup();
try {
b.group(group)
.channel(NioDatagramChannel.class)
.option(ChannelOption.SO_BROADCAST, true)
.handler(new EchoClientHandler()); b.bind(port).sync().channel().closeFuture().await();
} catch (Exception e) {
e.printStackTrace();
} finally{
group.shutdownGracefully();
}
}
}
左边客户端EchoClientHandler.java
package com.jieli.nat.echo; import java.net.InetSocketAddress;
import java.util.Vector; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket; //L
public class EchoClientHandler extends SimpleChannelInboundHandler<DatagramPacket>{ @Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet)
throws Exception {
//服务器推送对方IP和PORT
ByteBuf buf = (ByteBuf) packet.copy().content();
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String str = new String(req, "UTF-8");
String[] list = str.split(" ");
//如果是A 则发送
if(list[0].equals("A")){
String ip = list[1];
String port = list[2];
ctx.writeAndFlush(new DatagramPacket(
Unpooled.copiedBuffer("打洞信息".getBytes()), new InetSocketAddress(ip, Integer.parseInt(port))));
Thread.sleep(1000);
ctx.writeAndFlush(new DatagramPacket(
Unpooled.copiedBuffer("P2P info..".getBytes()), new InetSocketAddress(ip, Integer.parseInt(port))));
}
System.out.println("接收到的信息:" + str);
} @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端向服务器发送自己的IP和PORT");
ctx.writeAndFlush(new DatagramPacket(
Unpooled.copiedBuffer("L".getBytes()),
new InetSocketAddress("183.1.1.1", 7402)));
super.channelActive(ctx);
}
}
右边客户端EchoClient2.java
package com.jieli.nat.echo; import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel; /**
* 模拟P2P客户端
* @author
*
*/
public class EchoClient2{ public static void main(String[] args) {
Bootstrap b = new Bootstrap();
EventLoopGroup group = new NioEventLoopGroup();
try {
b.group(group)
.channel(NioDatagramChannel.class)
.option(ChannelOption.SO_BROADCAST, true)
.handler(new EchoClientHandler2()); b.bind(7779).sync().channel().closeFuture().await();
} catch (Exception e) {
e.printStackTrace();
} finally{
group.shutdownGracefully();
}
}
}
右边客户端EchoClientHandler2.java
package com.jieli.nat.echo; import java.net.InetSocketAddress;
import java.util.Vector; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket; public class EchoClientHandler2 extends SimpleChannelInboundHandler<DatagramPacket>{ @Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet)
throws Exception {
//服务器推送对方IP和PORT
ByteBuf buf = (ByteBuf) packet.copy().content();
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String str = new String(req, "UTF-8");
String[] list = str.split(" ");
//如果是A 则发送
if(list[0].equals("A")){
String ip = list[1];
String port = list[2];
ctx.writeAndFlush(new DatagramPacket(
Unpooled.copiedBuffer("打洞信息".getBytes()), new InetSocketAddress(ip, Integer.parseInt(port))));
Thread.sleep(1000);
ctx.writeAndFlush(new DatagramPacket(
Unpooled.copiedBuffer("P2P info..".getBytes()), new InetSocketAddress(ip, Integer.parseInt(port))));
}
System.out.println("接收到的信息:" + str);
} @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端向服务器发送自己的IP和PORT");
ctx.writeAndFlush(new DatagramPacket(
Unpooled.copiedBuffer("R".getBytes()),
new InetSocketAddress("1831.1.1", 7402)));
super.channelActive(ctx);
}
}
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
udp穿透简单讲解和实现(Java)的更多相关文章
- P2P原理及UDP穿透简单说明
转:http://http://andylin02.iteye.com/blog/444666 P2P原理及UDP穿透简单说明 本文章出自cnntec.com的AZ猫著,如需要转发,请注明来自cnnt ...
- P2P原理及UDP穿透简单说明(转)
源: P2P原理及UDP穿透简单说明
- 简单的例子 关于Java内存管理的讲解
我想做的是,逐行读取文件,然后用该行的电影名去获取电影信息.因为源文件较大,readlines()不能完全读取所有电影名,所以我们逐行读取. 就这段代码,我想要在位置二处使用base64,然后结果呢? ...
- java,UDP协议简单实现
//UDP协议简单实现-----Serverpackage UDP; import java.net.DatagramPacket; import java.net.DatagramSocket; i ...
- WeX5的简单介绍及UI的简单讲解
WeX5的简单介绍及UI的简单讲解 (2016-01-13 14:49:05) 标签: it 分类: WeX5的初步自学 一.WeX5的简单讲解 1.WeX5是前端快速开发框架,可开发跨端运行应用.是 ...
- NAT的全然分析及其UDP穿透的全然解决方式
NAT的全然分析及其UDP穿透的全然解决方式 一:基本术语 防火墙 防火墙限制了私网与公网的通信,它主要是将(防火墙)觉得未经授权的的包丢弃,防火墙仅仅是检验包的数据,并不改动数据包中的IP地址和 ...
- 【转】P2P之UDP穿透NAT的原理与实现(附源代码)
作者:shootingstars (有容乃大,无欲则刚) 日期:2004-5-25 出处:P2P中国(PPcn.net) P2P 之 UDP穿透NAT的原理与实现(附源代码)原创:shootings ...
- DES加解密 cbc模式 的简单讲解 && C++用openssl库来实现的注意事项
DES cbc是基于数据块加密的.数据块的长度为8字节64bit.以数据块为单位循环加密,再拼接.每个数据块加密的秘钥一样,IV向量不同.第一个数据快所需的IV向量,需要我们提供,从第二个数据块开始, ...
- 简单谈一谈Java内部类的使用原因
使用内部类的原因 学习总得知其所以然,前面的一篇文章中我有提到过关于java内部类的几种用法以及一些示例,但是不明白内部类具体可以做什么,显然学习起来很渺茫,今天的文章简单说一说使用内部类的几个原因, ...
随机推荐
- atitit.表格的绑定client side 最佳实践
atitit.表格的绑定client side 最佳实践 1. 框架选型 1 2. #---原理和流程 1 1. 方法1 1 2. Dwr 例子 1 3. 方法2 2 4. Jq例如 2 1. 框架选 ...
- Quartz 框架的应用
本文将简单介绍在没有 Spring 的时候..如何来使用 Quartz... 这里跳过 Quartz 的其他介绍.如果想更加输入的了解 Quartz,大家可以点击下载Quartz的帮助文档. Quar ...
- Perl语言——简单说明
Perl语言——简单说明 一.简单说明 Perl语言全称:实用摘录与报表语言|病态折中式垃圾列表器.Perl名称并不是缩写词,而是个溯写字. Perl语言历史:Larry Wall(拉里·沃尔)20世 ...
- Top 20 Java Libries Used by Github's Most Popular Java Projects
Top 20 Java Libries Used by Github's Most Popular Java Projects:
- HOWTO: InstallScript MSI工程取Log
InstallShield的各种类型安装包如果遇到安装问题(尤其是在客户安装时遇到问题),获取Log分析是最有效的方法之一. 对于封装一个Setup.exe的InstallScript MSI工程,我 ...
- ELK——在 CentOS/Linux 把 Kibana 3.0 部署在 Nginx 1.9.12
上一篇文章<安装 logstash 2.2.0.elasticsearch 2.2.0 和 Kibana 3.0>,介绍了如何安装 Logstash.Elasticsearch 以及用 P ...
- SQL 游标使用实例
IF EXISTS(SELECT *FROM sysobjects WHERE name='sp_ContestSubmit') DROP PROC sp_ContestSubmit GO -- == ...
- C++实现单例模式
昨天面试的时候,面试官让我用C++或Java实现一个单例模式. 因为设计模式是在12年的时候学习过这门课,而且当时觉得这门课很有意思,所以就把课本读了几遍,所以印象比较深刻,但是因为实际编程中很少注意 ...
- 太阳升起并下落的小动画-SWIFT
一个小小的动画,太阳公公上山又下山.先上效果图. 用 lipecap 录的gif效果有点卡顿.好吧,说下如何实现的. 首先在一个大圆内先计算出内切九边形各个顶点的位置,接着连接相应的顶点变成一个九角星 ...
- ORACLE 10g 64位下载地址
http://download.oracle.com/otn/nt/oracle10g/10201/102010_win64_x64_database.zip http://download.orac ...