- 浏览: 505433 次
- 性别:
- 来自: 广州
最新评论
-
cppmule:
Play!在国内实际产品级应用案例有吗?有哪些公司在用?国外的 ...
play总结性介绍 -
你好javaword:
netty的个人使用心得 -
hyfwuhui:
java 并发环境下使用ConcurrentHashMap -
asialee:
朋在无锡 写道可以将Channels使用静态导入的方式:imp ...
netty的个人使用心得 -
朋在无锡:
可以将Channels使用静态导入的方式:import sta ...
netty的个人使用心得
最近公司要实现在各种网络环境下面的多屏互动(机顶盒、android phone、iphone及PC端)的需求;由于IP地址资源有限的原因,目前我们使用的各种终端设备都位于局域网后面也就是多台设备共享同一个公网IP;例如:如果位于局域网里面的一个终端Agent A要与互联网上的另一个终端Agent B通信,当A发送的data packet经过局域网出口处的NAT设备时,NAT会将data packet里面的source address字段替换成相应的公网IP和Port,然后再发送data packet到Agent B。Agent B看到的source address就是经过转换后的IP和Port并不知道Agent A的局域网地址;当Agent B的响应到达Agent A的NAT设备后,NAT设备查找内存中保存的和这个外网地址相对应的内网地址,如果找到后就将这个data packet转发到这个地址,这样就实现了通信。
然而由于目前存在着各种不同类型的NAT设备对NAT有着不同的实现方式(将内外地址映射成外网地址的时候有着不同的行为方式),这就给NAT的穿透带来了麻烦;目前主要的NAT类型有如下几种:
1)Full-cone NAT, also known as one-to-one NAT
一旦一个内网地址 (iAddr:iPort) 被映射到一个外部地址 (eAddr:ePort), 来自 iAddr:iPort 的任何数据包将通过 eAddr:ePort 发送. 任何外部主机能够通过eAddr:ePort这个地址发送数据包到iAddr:iPort.
2)Address-restricted-cone NAT
一旦一个内网地址 (iAddr:iPort) 被映射到一个外部地址 (eAddr:ePort), 来自 iAddr:iPort 的任何数据包将通过 eAddr:ePort 发送. 仅只有接收到主机(iAddr:iPort)通过eAddr:ePort发送的数据包的外部主机通过该主机的任何端口发送到eAddr:ePort的数据包才能够被正确的转发到iAddr:iPort.也就是说主机有关端口无关.
3)Port-restricted cone NAT
类似于address restricted cone NAT, 但是端口号有限制.
一旦一个内网地址 (iAddr:iPort) 被映射到一个外部地址 (eAddr:ePort), 来自 iAddr:iPort 的任何数据包将通过 eAddr:ePort 发送. 仅只有接收到主机(iAddr:iPort)通过eAddr:ePort发送的数据包的外部主机通过该主机的相同端口发送到eAddr:ePort的数据包才能够被正确的转发到iAddr:iPort.
4)Symmetric NAT
来自相同内部ip和port发送到相同目的地ip和port的请求被映射到唯一的外部ip和port地址;如果相同的内部主机采用相同的ip和port地址发送到不同的目的地,那么重新分配映射地址。 只有先前收到内部主机发送的包的外部主机才能够发送返回包到内部主机。
针对前面三种NAT类型(即cone NAT)只要通信双方彼此知道对方的内部地址和外部地址的映射关系,然后通过UDP打洞的方式就可以建立相互连接的通信;但是第四种也就是Symmetric NAT的话由于每次向不同目的地发送数据包时采用不同的外部地址,也就没办法通过直接的方式建立P2P连接。
1.各种网络环境下的P2P通信解决方法:
(1)如果通信双方在同一个局域网内,这种情况下可以不借助任何外力直接通过内网地址通信即可; (2)如果通信双方都在有独立的公网地址,这种情况下当然可以不借助任何外力直接通信即可;
(3)如果通信双方一方拥有独立的公网地址另一方在NAT后面,那么可以由位于NAT后面的一方主动发起通信请求;
(4)如果通信双方都位于NAT后面,且双方的NAT类型都是cone NAT,那么可以通过一个STUN服务器发现自己的NAT类型以及内网和外网传输地址映射信息,然后通过Signaling(信令服务器,实现了SIP协议的主机)交换彼此的NAT类型及内网和外网传输地址映射信息,然后通过UDP打洞的方式建立通信连接;
4>如果通信双方有一方的NAT类型是Symmetric NAT,则无法直接建立P2P连接,这个时候就需要借助TURN(Traversal Using Relay NAT)服务器即转发服务器来实现间接的通信;
2.协议及用到的相关技术介绍:
SDP: 也就是Session Description Protocol的缩写,当初始化多媒体电视会议、IP电话、视频流等会话的时候,参与者之间会要求传送媒介的详细、传输地址和其他会话描述元数据等信息;SDP为这些信息提供一种和传输方式无关的标准的表现形式。也就是说SDP仅仅只是一种描述会话信息的格式。它主要被各种不同的传输协议作为一种信息交换的格式使用列如:HTTP、RTSP、SIP、Email等各种协议。 如ICE里面的SDP内容为: v=0 o=ice4j.org 0 0 IN IP4 192.168.106.215 s=- t=0 0 a=ice-options:trickle a=ice-ufrag:bc01a a=ice-pwd:1boove7ehnpo1lqho7unefni36 m=audio 3030 RTP/AVP 0 c=IN 192.168.106.215 IP4 a=mid:audio a=candidate:1 1 udp 2130706431 192.168.106.215 3030 typ host a=candidate:2 1 udp 1694498815 121.15.130.xxx 64923 typ srflx raddr 192.168.106.215 rport 3030
STUN:也就是Session Traversal Utilities for NAT的缩写,NAT会话穿透工具;STUN提供了一种方式使一个端点能够确定NAT分配给它的和本地私有IP地址和端口相对应的公网IP地址和端口以及NAT的类型信息。它也为端点提供了一种方式保持一个NAT绑定不过期。NAT绑定过期则表示为相同的内网地址重新分配外网地址也就是端口号。
TURN:也就是Traversal Using Relay NAT的缩写,TURN是STUN协议的扩展,在实际应用中他也可以充当STUN的角色;如果一个位于NAT后面的设备想要和另外一个位于NAT后面的设备建立通信,当采用UDP打洞技术不能改实现的时候就必须要一台中间服务器扮演数据包转发的角色,这台TURN服务器需要拥有公网的IP地址;
SIP:也就是Session Initiation Protocol的缩写,是一种Signaling(信令)通信协议;有许多互联网应用需要创建有多个参与者的会话和管理参与者之间相互的数据交换,然而如果这些工作让应用的参与者来实现是比较复杂的如:用户也许在端点之间移动、通过多个名称寻址和也许同时使用几种不同的媒介通信。有许多协议能够实现各种形式的多媒体会话进行数据传送例如声音、视频或者文本消息。SIP能够和这些协议一同合作,使一个客服端能够发现参与这个会话的其他客服端并共享同一会话。为了定位后面加入会话的参与者等功能,SIP能够为代理服务器创建基础设施,客服端可以通过这个代理服务器实现会话注册、邀请参与会话等功能。SIP是一个创建、修改和终止会话的灵活的多种用途的工具,不依赖于底层的传输协议并且不依赖于被创建的会话类型。
ICE:也就是Interactive Connectivity Establishment的缩写,是实现NAT穿透的一种技术方案;ICE是一种NAT穿透技术,通过offer/answer模型建立基于UDP的媒介流。ICE是offer/answer模型的扩展,通过在offer和answer的SDP里面包含多种IP地址和端口,然后对本地SDP和远程SDP里面的IP地址进行配对,然后通过P2P连通性检查进行连通性测试工作,如果测试通过即表明该传输地址对可以建立连接。其中IP地址和端口(也就是地址)有以下几种:本机地址、通过STUN服务器反射后获取的server-reflexive地址(内网地址被NAT映射后的地址)、relayed地址(和TURN转发服务器相对应的地址)及Peer reflexive地址等。
3.ICE进行NAT穿透的基本过程: 在通常的ICE部署环境中,我们有两个客服端想要建立通信连接,他们可以直接通过signaling服务器(如SIP服务器)执行offer/answer过程来交换SDP消息。 在ICE过程开始的时候,客服端忽略他们各自的网络拓扑结构,不管是不是在NAT设备后面或者多个NAT后面,ICE允许客服端发现他们的所在网络的拓扑结构的信息,然后找出一个或者更多的可以建立通信连接的路径。 下图显示了一个典型的ICE部署环境,客服端L和R都在各自的NAT设备后面,下面简单描述下ICE建立通信的过程: (1)L和R先分别通过STUN和TURN服务器获取自己的host address,server-reflexive address、relayed address(和TURN转发服务器相对应的地址),其中server-reflexive address和relayed address通过定时刷新保证地址不过期。这些地址通常叫做candinate地址。 (2)给这些candinate地址分配优先级排序并格式化成SDP格式,通过SIP服务器交换彼此的SDP; (3)交换完成后根据一定的原则把本地的候选和远程的候选进行配对,每一对都有自己的优先级并根据优先级进行排序后放入Check列表里面(两边都会有相同的Check列表)。 (4)然后进行连接性测试,测试前会选择一个客服端扮演Controlled角色和另一个扮演Controling角色,连通性检查完成后扮演Controling角色的客服端负责在有效的Candinate对列表里面选择一个作为一个被选中的传输通道并通知Controlled的客服端。 (5)利用被选中的candinate地址对进行通信。 \
4.ICE JAVA实现代码 我这里的样例代码采用ICE4J来实现,ICE4J的API文档可以参考http://bluejimp.com/jitsi/ice4j/javadoc/,在这个实现里面没有利用SIP服务器进行SDP信息的交换而是采用手动输入的方式,在生产环境中可以部署一台WebRTC服务器(NodeJS有很多开源的WebRTC实现模块) 加载中...加载中...
view sourceprint?
001.
/**
002.
* Copyright (c) 2014 All Rights Reserved.
003.
* TODO
004.
*/
005.
006.
import java.beans.PropertyChangeEvent;
007.
import java.beans.PropertyChangeListener;
008.
import java.io.BufferedReader;
009.
import java.io.InputStreamReader;
010.
import java.net.DatagramSocket;
011.
import java.net.SocketAddress;
012.
import java.util.List;
013.
014.
import org.apache.commons.lang3.StringUtils;
015.
import org.apache.log4j.Logger;
016.
import org.ice4j.Transport;
017.
import org.ice4j.TransportAddress;
018.
import org.ice4j.ice.Agent;
019.
import org.ice4j.ice.Component;
020.
import org.ice4j.ice.IceMediaStream;
021.
import org.ice4j.ice.IceProcessingState;
022.
import org.ice4j.ice.LocalCandidate;
023.
import org.ice4j.ice.NominationStrategy;
024.
import org.ice4j.ice.RemoteCandidate;
025.
import org.ice4j.ice.harvest.StunCandidateHarvester;
026.
import org.ice4j.ice.harvest.TurnCandidateHarvester;
027.
import org.ice4j.security.LongTermCredential;
028.
029.
import test.SdpUtils;
030.
031.
public class IceClient {
032.
033.
private int port;
034.
035.
private String streamName;
036.
037.
private Agent agent;
038.
039.
private String localSdp;
040.
041.
private String remoteSdp;
042.
043.
private String[] turnServers = new String[] { "stun.jitsi.net:3478" };
044.
045.
private String[] stunServers = new String[] { "stun.stunprotocol.org:3478" };
046.
047.
private String username = "guest";
048.
049.
private String pass<a href="http://www.it165.net/edu/ebg/" target="_blank" class="keylink">word</a> = "anonymouspower!!";
050.
051.
private IceProcessingListener listener;
052.
053.
static Logger log = Logger.getLogger(IceClient.class);
054.
055.
public IceClient(int port, String streamName) {
056.
this.port = port;
057.
this.streamName = streamName;
058.
this.listener = new IceProcessingListener();
059.
}
060.
061.
public void init() throws Throwable {
062.
063.
agent = createAgent(port, streamName);
064.
065.
agent.setNominationStrategy(NominationStrategy.NOMINATE_HIGHEST_PRIO);
066.
067.
agent.addStateChangeListener(listener);
068.
069.
agent.setControlling(false);
070.
071.
agent.setTa(10000);
072.
073.
localSdp = SdpUtils.createSDPDescription(agent);
074.
075.
log.info("=================== feed the following"
076.
+ " to the remote agent ===================");
077.
078.
System.out.println(localSdp);
079.
080.
log.info("======================================"
081.
+ "========================================\n");
082.
}
083.
084.
public DatagramSocket getDatagramSocket() throws Throwable {
085.
086.
LocalCandidate localCandidate = agent
087.
.getSelectedLocalCandidate(streamName);
088.
089.
IceMediaStream stream = agent.getStream(streamName);
090.
List<Component> components = stream.getComponents();
091.
for (Component c : components) {
092.
log.info(c);
093.
}
094.
log.info(localCandidate.toString());
095.
LocalCandidate candidate = (LocalCandidate) localCandidate;
096.
return candidate.getDatagramSocket();
097.
098.
}
099.
100.
public SocketAddress getRemotePeerSocketAddress() {
101.
RemoteCandidate remoteCandidate = agent
102.
.getSelectedRemoteCandidate(streamName);
103.
log.info("Remote candinate transport address:"
104.
+ remoteCandidate.getTransportAddress());
105.
log.info("Remote candinate host address:"
106.
+ remoteCandidate.getHostAddress());
107.
log.info("Remote candinate mapped address:"
108.
+ remoteCandidate.getMappedAddress());
109.
log.info("Remote candinate relayed address:"
110.
+ remoteCandidate.getRelayedAddress());
111.
log.info("Remote candinate reflexive address:"
112.
+ remoteCandidate.getReflexiveAddress());
113.
return remoteCandidate.getTransportAddress();
114.
}
115.
116.
/**
117.
* Reads an SDP description from the standard input.In production
118.
* environment that we can exchange SDP with peer through signaling
119.
* server(SIP server)
120.
*/
121.
public void exchangeSdpWithPeer() throws Throwable {
122.
log.info("Paste remote SDP here. Enter an empty line to proceed:");
123.
BufferedReader reader = new BufferedReader(new InputStreamReader(
124.
System.in));
125.
126.
StringBuilder buff = new StringBuilder();
127.
String line = new String();
128.
129.
while ((line = reader.readLine()) != null) {
130.
line = line.trim();
131.
if (line.length() == 0) {
132.
break;
133.
}
134.
buff.append(line);
135.
buff.append("\r\n");
136.
}
137.
138.
remoteSdp = buff.toString();
139.
140.
SdpUtils.parseSDP(agent, remoteSdp);
141.
}
142.
143.
public void startConnect() throws InterruptedException {
144.
145.
if (StringUtils.isBlank(remoteSdp)) {
146.
throw new NullPointerException(
147.
"Please exchange sdp information with peer before start connect! ");
148.
}
149.
150.
agent.startConnectivityEstablishment();
151.
152.
// agent.runInStunKeepAliveThread();
153.
154.
synchronized (listener) {
155.
listener.wait();
156.
}
157.
158.
}
159.
160.
private Agent createAgent(int rtpPort, String streamName) throws Throwable {
161.
return createAgent(rtpPort, streamName, false);
162.
}
163.
164.
private Agent createAgent(int rtpPort, String streamName,
165.
boolean isTrickling) throws Throwable {
166.
167.
long startTime = System.currentTimeMillis();
168.
169.
Agent agent = new Agent();
170.
171.
agent.setTrickling(isTrickling);
172.
173.
// STUN
174.
for (String server : stunServers){
175.
String[] pair = server.split(":");
176.
agent.addCandidateHarvester(new StunCandidateHarvester(
177.
new TransportAddress(pair[0], Integer.parseInt(pair[1]),
178.
Transport.UDP)));
179.
}
180.
181.
// TURN
182.
LongTermCredential longTermCredential = new LongTermCredential(username,
183.
pass<a href="http://www.it165.net/edu/ebg/" target="_blank" class="keylink">word</a>);
184.
185.
for (String server : turnServers){
186.
String[] pair = server.split(":");
187.
agent.addCandidateHarvester(new TurnCandidateHarvester(
188.
new TransportAddress(pair[0], Integer.parseInt(pair[1]), Transport.UDP),
189.
longTermCredential));
190.
}
191.
// STREAMS
192.
createStream(rtpPort, streamName, agent);
193.
194.
long endTime = System.currentTimeMillis();
195.
long total = endTime - startTime;
196.
197.
log.info("Total harvesting time: " + total + "ms.");
198.
199.
return agent;
200.
}
201.
202.
private IceMediaStream createStream(int rtpPort, String streamName,
203.
Agent agent) throws Throwable {
204.
long startTime = System.currentTimeMillis();
205.
IceMediaStream stream = agent.createMediaStream(streamName);
206.
// rtp
207.
Component component = agent.createComponent(stream, Transport.UDP,
208.
rtpPort, rtpPort, rtpPort + 100);
209.
210.
long endTime = System.currentTimeMillis();
211.
log.info("Component Name:" + component.getName());
212.
log.info("RTP Component created in " + (endTime - startTime) + " ms");
213.
214.
return stream;
215.
}
216.
217.
/**
218.
* Receive notify event when ice processing state has changed.
219.
*/
220.
public static final class IceProcessingListener implements
221.
PropertyChangeListener {
222.
223.
private long startTime = System.currentTimeMillis();
224.
225.
public void propertyChange(PropertyChangeEvent event) {
226.
227.
Object state = event.getNewValue();
228.
229.
log.info("Agent entered the " + state + " state.");
230.
if (state == IceProcessingState.COMPLETED) {
231.
long processingEndTime = System.currentTimeMillis();
232.
log.info("Total ICE processing time: "
233.
+ (processingEndTime - startTime) + "ms");
234.
Agent agent = (Agent) event.getSource();
235.
List<IceMediaStream> streams = agent.getStreams();
236.
237.
for (IceMediaStream stream : streams) {
238.
log.info("Stream name: " + stream.getName());
239.
List<Component> components = stream.getComponents();
240.
for (Component c : components) {
241.
log.info("------------------------------------------");
242.
log.info("Component of stream:" + c.getName()
243.
+ ",selected of pair:" + c.getSelectedPair());
244.
log.info("------------------------------------------");
245.
}
246.
}
247.
248.
log.info("Printing the completed check lists:");
249.
for (IceMediaStream stream : streams) {
250.
251.
log.info("Check list for stream: " + stream.getName());
252.
253.
log.info("nominated check list:" + stream.getCheckList());
254.
}
255.
synchronized (this) {
256.
this.notifyAll();
257.
}
258.
} else if (state == IceProcessingState.TERMINATED) {
259.
log.info("ice processing TERMINATED");
260.
} else if (state == IceProcessingState.FAILED) {
261.
log.info("ice processing FAILED");
262.
((Agent) event.getSource()).free();
263.
}
264.
}
265.
}
266.
}
267.
268.
import java.io.IOException;
269.
import java.net.DatagramPacket;
270.
import java.net.DatagramSocket;
271.
import java.net.SocketAddress;
272.
import java.util.concurrent.TimeUnit;
273.
274.
275.
public class PeerA {
276.
277.
public static void main(String[] args) throws Throwable {
278.
try {
279.
IceClient client = new IceClient(2020, "audio");
280.
client.init();
281.
client.exchangeSdpWithPeer();
282.
client.startConnect();
283.
final DatagramSocket socket = client.getDatagramSocket();
284.
final SocketAddress remoteAddress = client
285.
.getRemotePeerSocketAddress();
286.
System.out.println(socket.toString());
287.
new Thread(new Runnable() {
288.
289.
public void run() {
290.
while (true) {
291.
try {
292.
byte[] buf = new byte[1024];
293.
DatagramPacket packet = new DatagramPacket(buf,
294.
buf.length);
295.
socket.receive(packet);
296.
System.out.println("receive:"
297.
+ new String(packet.getData(), 0, packet
298.
.getLength()));
299.
} catch (IOException e) {
300.
// TODO Auto-generated catch block
301.
e.printStackTrace();
302.
}
303.
304.
}
305.
}
306.
}).start();
307.
308.
new Thread(new Runnable() {
309.
310.
public void run() {
311.
int count = 1;
312.
while (true) {
313.
try {
314.
byte[] buf = ("send msg " + count++ + "").getBytes();
315.
DatagramPacket packet = new DatagramPacket(buf,
316.
buf.length);
317.
318.
packet.setSocketAddress(remoteAddress);
319.
socket.send(packet);
320.
System.out.println("send msg");
321.
TimeUnit.SECONDS.sleep(10);
322.
} catch (Exception e) {
323.
// TODO Auto-generated catch block
324.
e.printStackTrace();
325.
}
326.
327.
}
328.
}
329.
}).start();
330.
} catch (Exception e) {
331.
// TODO Auto-generated catch block
332.
e.printStackTrace();
333.
}
334.
335.
}
336.
337.
}
然而由于目前存在着各种不同类型的NAT设备对NAT有着不同的实现方式(将内外地址映射成外网地址的时候有着不同的行为方式),这就给NAT的穿透带来了麻烦;目前主要的NAT类型有如下几种:
1)Full-cone NAT, also known as one-to-one NAT
一旦一个内网地址 (iAddr:iPort) 被映射到一个外部地址 (eAddr:ePort), 来自 iAddr:iPort 的任何数据包将通过 eAddr:ePort 发送. 任何外部主机能够通过eAddr:ePort这个地址发送数据包到iAddr:iPort.
2)Address-restricted-cone NAT
一旦一个内网地址 (iAddr:iPort) 被映射到一个外部地址 (eAddr:ePort), 来自 iAddr:iPort 的任何数据包将通过 eAddr:ePort 发送. 仅只有接收到主机(iAddr:iPort)通过eAddr:ePort发送的数据包的外部主机通过该主机的任何端口发送到eAddr:ePort的数据包才能够被正确的转发到iAddr:iPort.也就是说主机有关端口无关.
3)Port-restricted cone NAT
类似于address restricted cone NAT, 但是端口号有限制.
一旦一个内网地址 (iAddr:iPort) 被映射到一个外部地址 (eAddr:ePort), 来自 iAddr:iPort 的任何数据包将通过 eAddr:ePort 发送. 仅只有接收到主机(iAddr:iPort)通过eAddr:ePort发送的数据包的外部主机通过该主机的相同端口发送到eAddr:ePort的数据包才能够被正确的转发到iAddr:iPort.
4)Symmetric NAT
来自相同内部ip和port发送到相同目的地ip和port的请求被映射到唯一的外部ip和port地址;如果相同的内部主机采用相同的ip和port地址发送到不同的目的地,那么重新分配映射地址。 只有先前收到内部主机发送的包的外部主机才能够发送返回包到内部主机。
针对前面三种NAT类型(即cone NAT)只要通信双方彼此知道对方的内部地址和外部地址的映射关系,然后通过UDP打洞的方式就可以建立相互连接的通信;但是第四种也就是Symmetric NAT的话由于每次向不同目的地发送数据包时采用不同的外部地址,也就没办法通过直接的方式建立P2P连接。
1.各种网络环境下的P2P通信解决方法:
(1)如果通信双方在同一个局域网内,这种情况下可以不借助任何外力直接通过内网地址通信即可; (2)如果通信双方都在有独立的公网地址,这种情况下当然可以不借助任何外力直接通信即可;
(3)如果通信双方一方拥有独立的公网地址另一方在NAT后面,那么可以由位于NAT后面的一方主动发起通信请求;
(4)如果通信双方都位于NAT后面,且双方的NAT类型都是cone NAT,那么可以通过一个STUN服务器发现自己的NAT类型以及内网和外网传输地址映射信息,然后通过Signaling(信令服务器,实现了SIP协议的主机)交换彼此的NAT类型及内网和外网传输地址映射信息,然后通过UDP打洞的方式建立通信连接;
4>如果通信双方有一方的NAT类型是Symmetric NAT,则无法直接建立P2P连接,这个时候就需要借助TURN(Traversal Using Relay NAT)服务器即转发服务器来实现间接的通信;
2.协议及用到的相关技术介绍:
SDP: 也就是Session Description Protocol的缩写,当初始化多媒体电视会议、IP电话、视频流等会话的时候,参与者之间会要求传送媒介的详细、传输地址和其他会话描述元数据等信息;SDP为这些信息提供一种和传输方式无关的标准的表现形式。也就是说SDP仅仅只是一种描述会话信息的格式。它主要被各种不同的传输协议作为一种信息交换的格式使用列如:HTTP、RTSP、SIP、Email等各种协议。 如ICE里面的SDP内容为: v=0 o=ice4j.org 0 0 IN IP4 192.168.106.215 s=- t=0 0 a=ice-options:trickle a=ice-ufrag:bc01a a=ice-pwd:1boove7ehnpo1lqho7unefni36 m=audio 3030 RTP/AVP 0 c=IN 192.168.106.215 IP4 a=mid:audio a=candidate:1 1 udp 2130706431 192.168.106.215 3030 typ host a=candidate:2 1 udp 1694498815 121.15.130.xxx 64923 typ srflx raddr 192.168.106.215 rport 3030
STUN:也就是Session Traversal Utilities for NAT的缩写,NAT会话穿透工具;STUN提供了一种方式使一个端点能够确定NAT分配给它的和本地私有IP地址和端口相对应的公网IP地址和端口以及NAT的类型信息。它也为端点提供了一种方式保持一个NAT绑定不过期。NAT绑定过期则表示为相同的内网地址重新分配外网地址也就是端口号。
TURN:也就是Traversal Using Relay NAT的缩写,TURN是STUN协议的扩展,在实际应用中他也可以充当STUN的角色;如果一个位于NAT后面的设备想要和另外一个位于NAT后面的设备建立通信,当采用UDP打洞技术不能改实现的时候就必须要一台中间服务器扮演数据包转发的角色,这台TURN服务器需要拥有公网的IP地址;
SIP:也就是Session Initiation Protocol的缩写,是一种Signaling(信令)通信协议;有许多互联网应用需要创建有多个参与者的会话和管理参与者之间相互的数据交换,然而如果这些工作让应用的参与者来实现是比较复杂的如:用户也许在端点之间移动、通过多个名称寻址和也许同时使用几种不同的媒介通信。有许多协议能够实现各种形式的多媒体会话进行数据传送例如声音、视频或者文本消息。SIP能够和这些协议一同合作,使一个客服端能够发现参与这个会话的其他客服端并共享同一会话。为了定位后面加入会话的参与者等功能,SIP能够为代理服务器创建基础设施,客服端可以通过这个代理服务器实现会话注册、邀请参与会话等功能。SIP是一个创建、修改和终止会话的灵活的多种用途的工具,不依赖于底层的传输协议并且不依赖于被创建的会话类型。
ICE:也就是Interactive Connectivity Establishment的缩写,是实现NAT穿透的一种技术方案;ICE是一种NAT穿透技术,通过offer/answer模型建立基于UDP的媒介流。ICE是offer/answer模型的扩展,通过在offer和answer的SDP里面包含多种IP地址和端口,然后对本地SDP和远程SDP里面的IP地址进行配对,然后通过P2P连通性检查进行连通性测试工作,如果测试通过即表明该传输地址对可以建立连接。其中IP地址和端口(也就是地址)有以下几种:本机地址、通过STUN服务器反射后获取的server-reflexive地址(内网地址被NAT映射后的地址)、relayed地址(和TURN转发服务器相对应的地址)及Peer reflexive地址等。
3.ICE进行NAT穿透的基本过程: 在通常的ICE部署环境中,我们有两个客服端想要建立通信连接,他们可以直接通过signaling服务器(如SIP服务器)执行offer/answer过程来交换SDP消息。 在ICE过程开始的时候,客服端忽略他们各自的网络拓扑结构,不管是不是在NAT设备后面或者多个NAT后面,ICE允许客服端发现他们的所在网络的拓扑结构的信息,然后找出一个或者更多的可以建立通信连接的路径。 下图显示了一个典型的ICE部署环境,客服端L和R都在各自的NAT设备后面,下面简单描述下ICE建立通信的过程: (1)L和R先分别通过STUN和TURN服务器获取自己的host address,server-reflexive address、relayed address(和TURN转发服务器相对应的地址),其中server-reflexive address和relayed address通过定时刷新保证地址不过期。这些地址通常叫做candinate地址。 (2)给这些candinate地址分配优先级排序并格式化成SDP格式,通过SIP服务器交换彼此的SDP; (3)交换完成后根据一定的原则把本地的候选和远程的候选进行配对,每一对都有自己的优先级并根据优先级进行排序后放入Check列表里面(两边都会有相同的Check列表)。 (4)然后进行连接性测试,测试前会选择一个客服端扮演Controlled角色和另一个扮演Controling角色,连通性检查完成后扮演Controling角色的客服端负责在有效的Candinate对列表里面选择一个作为一个被选中的传输通道并通知Controlled的客服端。 (5)利用被选中的candinate地址对进行通信。 \
4.ICE JAVA实现代码 我这里的样例代码采用ICE4J来实现,ICE4J的API文档可以参考http://bluejimp.com/jitsi/ice4j/javadoc/,在这个实现里面没有利用SIP服务器进行SDP信息的交换而是采用手动输入的方式,在生产环境中可以部署一台WebRTC服务器(NodeJS有很多开源的WebRTC实现模块) 加载中...加载中...
view sourceprint?
001.
/**
002.
* Copyright (c) 2014 All Rights Reserved.
003.
* TODO
004.
*/
005.
006.
import java.beans.PropertyChangeEvent;
007.
import java.beans.PropertyChangeListener;
008.
import java.io.BufferedReader;
009.
import java.io.InputStreamReader;
010.
import java.net.DatagramSocket;
011.
import java.net.SocketAddress;
012.
import java.util.List;
013.
014.
import org.apache.commons.lang3.StringUtils;
015.
import org.apache.log4j.Logger;
016.
import org.ice4j.Transport;
017.
import org.ice4j.TransportAddress;
018.
import org.ice4j.ice.Agent;
019.
import org.ice4j.ice.Component;
020.
import org.ice4j.ice.IceMediaStream;
021.
import org.ice4j.ice.IceProcessingState;
022.
import org.ice4j.ice.LocalCandidate;
023.
import org.ice4j.ice.NominationStrategy;
024.
import org.ice4j.ice.RemoteCandidate;
025.
import org.ice4j.ice.harvest.StunCandidateHarvester;
026.
import org.ice4j.ice.harvest.TurnCandidateHarvester;
027.
import org.ice4j.security.LongTermCredential;
028.
029.
import test.SdpUtils;
030.
031.
public class IceClient {
032.
033.
private int port;
034.
035.
private String streamName;
036.
037.
private Agent agent;
038.
039.
private String localSdp;
040.
041.
private String remoteSdp;
042.
043.
private String[] turnServers = new String[] { "stun.jitsi.net:3478" };
044.
045.
private String[] stunServers = new String[] { "stun.stunprotocol.org:3478" };
046.
047.
private String username = "guest";
048.
049.
private String pass<a href="http://www.it165.net/edu/ebg/" target="_blank" class="keylink">word</a> = "anonymouspower!!";
050.
051.
private IceProcessingListener listener;
052.
053.
static Logger log = Logger.getLogger(IceClient.class);
054.
055.
public IceClient(int port, String streamName) {
056.
this.port = port;
057.
this.streamName = streamName;
058.
this.listener = new IceProcessingListener();
059.
}
060.
061.
public void init() throws Throwable {
062.
063.
agent = createAgent(port, streamName);
064.
065.
agent.setNominationStrategy(NominationStrategy.NOMINATE_HIGHEST_PRIO);
066.
067.
agent.addStateChangeListener(listener);
068.
069.
agent.setControlling(false);
070.
071.
agent.setTa(10000);
072.
073.
localSdp = SdpUtils.createSDPDescription(agent);
074.
075.
log.info("=================== feed the following"
076.
+ " to the remote agent ===================");
077.
078.
System.out.println(localSdp);
079.
080.
log.info("======================================"
081.
+ "========================================\n");
082.
}
083.
084.
public DatagramSocket getDatagramSocket() throws Throwable {
085.
086.
LocalCandidate localCandidate = agent
087.
.getSelectedLocalCandidate(streamName);
088.
089.
IceMediaStream stream = agent.getStream(streamName);
090.
List<Component> components = stream.getComponents();
091.
for (Component c : components) {
092.
log.info(c);
093.
}
094.
log.info(localCandidate.toString());
095.
LocalCandidate candidate = (LocalCandidate) localCandidate;
096.
return candidate.getDatagramSocket();
097.
098.
}
099.
100.
public SocketAddress getRemotePeerSocketAddress() {
101.
RemoteCandidate remoteCandidate = agent
102.
.getSelectedRemoteCandidate(streamName);
103.
log.info("Remote candinate transport address:"
104.
+ remoteCandidate.getTransportAddress());
105.
log.info("Remote candinate host address:"
106.
+ remoteCandidate.getHostAddress());
107.
log.info("Remote candinate mapped address:"
108.
+ remoteCandidate.getMappedAddress());
109.
log.info("Remote candinate relayed address:"
110.
+ remoteCandidate.getRelayedAddress());
111.
log.info("Remote candinate reflexive address:"
112.
+ remoteCandidate.getReflexiveAddress());
113.
return remoteCandidate.getTransportAddress();
114.
}
115.
116.
/**
117.
* Reads an SDP description from the standard input.In production
118.
* environment that we can exchange SDP with peer through signaling
119.
* server(SIP server)
120.
*/
121.
public void exchangeSdpWithPeer() throws Throwable {
122.
log.info("Paste remote SDP here. Enter an empty line to proceed:");
123.
BufferedReader reader = new BufferedReader(new InputStreamReader(
124.
System.in));
125.
126.
StringBuilder buff = new StringBuilder();
127.
String line = new String();
128.
129.
while ((line = reader.readLine()) != null) {
130.
line = line.trim();
131.
if (line.length() == 0) {
132.
break;
133.
}
134.
buff.append(line);
135.
buff.append("\r\n");
136.
}
137.
138.
remoteSdp = buff.toString();
139.
140.
SdpUtils.parseSDP(agent, remoteSdp);
141.
}
142.
143.
public void startConnect() throws InterruptedException {
144.
145.
if (StringUtils.isBlank(remoteSdp)) {
146.
throw new NullPointerException(
147.
"Please exchange sdp information with peer before start connect! ");
148.
}
149.
150.
agent.startConnectivityEstablishment();
151.
152.
// agent.runInStunKeepAliveThread();
153.
154.
synchronized (listener) {
155.
listener.wait();
156.
}
157.
158.
}
159.
160.
private Agent createAgent(int rtpPort, String streamName) throws Throwable {
161.
return createAgent(rtpPort, streamName, false);
162.
}
163.
164.
private Agent createAgent(int rtpPort, String streamName,
165.
boolean isTrickling) throws Throwable {
166.
167.
long startTime = System.currentTimeMillis();
168.
169.
Agent agent = new Agent();
170.
171.
agent.setTrickling(isTrickling);
172.
173.
// STUN
174.
for (String server : stunServers){
175.
String[] pair = server.split(":");
176.
agent.addCandidateHarvester(new StunCandidateHarvester(
177.
new TransportAddress(pair[0], Integer.parseInt(pair[1]),
178.
Transport.UDP)));
179.
}
180.
181.
// TURN
182.
LongTermCredential longTermCredential = new LongTermCredential(username,
183.
pass<a href="http://www.it165.net/edu/ebg/" target="_blank" class="keylink">word</a>);
184.
185.
for (String server : turnServers){
186.
String[] pair = server.split(":");
187.
agent.addCandidateHarvester(new TurnCandidateHarvester(
188.
new TransportAddress(pair[0], Integer.parseInt(pair[1]), Transport.UDP),
189.
longTermCredential));
190.
}
191.
// STREAMS
192.
createStream(rtpPort, streamName, agent);
193.
194.
long endTime = System.currentTimeMillis();
195.
long total = endTime - startTime;
196.
197.
log.info("Total harvesting time: " + total + "ms.");
198.
199.
return agent;
200.
}
201.
202.
private IceMediaStream createStream(int rtpPort, String streamName,
203.
Agent agent) throws Throwable {
204.
long startTime = System.currentTimeMillis();
205.
IceMediaStream stream = agent.createMediaStream(streamName);
206.
// rtp
207.
Component component = agent.createComponent(stream, Transport.UDP,
208.
rtpPort, rtpPort, rtpPort + 100);
209.
210.
long endTime = System.currentTimeMillis();
211.
log.info("Component Name:" + component.getName());
212.
log.info("RTP Component created in " + (endTime - startTime) + " ms");
213.
214.
return stream;
215.
}
216.
217.
/**
218.
* Receive notify event when ice processing state has changed.
219.
*/
220.
public static final class IceProcessingListener implements
221.
PropertyChangeListener {
222.
223.
private long startTime = System.currentTimeMillis();
224.
225.
public void propertyChange(PropertyChangeEvent event) {
226.
227.
Object state = event.getNewValue();
228.
229.
log.info("Agent entered the " + state + " state.");
230.
if (state == IceProcessingState.COMPLETED) {
231.
long processingEndTime = System.currentTimeMillis();
232.
log.info("Total ICE processing time: "
233.
+ (processingEndTime - startTime) + "ms");
234.
Agent agent = (Agent) event.getSource();
235.
List<IceMediaStream> streams = agent.getStreams();
236.
237.
for (IceMediaStream stream : streams) {
238.
log.info("Stream name: " + stream.getName());
239.
List<Component> components = stream.getComponents();
240.
for (Component c : components) {
241.
log.info("------------------------------------------");
242.
log.info("Component of stream:" + c.getName()
243.
+ ",selected of pair:" + c.getSelectedPair());
244.
log.info("------------------------------------------");
245.
}
246.
}
247.
248.
log.info("Printing the completed check lists:");
249.
for (IceMediaStream stream : streams) {
250.
251.
log.info("Check list for stream: " + stream.getName());
252.
253.
log.info("nominated check list:" + stream.getCheckList());
254.
}
255.
synchronized (this) {
256.
this.notifyAll();
257.
}
258.
} else if (state == IceProcessingState.TERMINATED) {
259.
log.info("ice processing TERMINATED");
260.
} else if (state == IceProcessingState.FAILED) {
261.
log.info("ice processing FAILED");
262.
((Agent) event.getSource()).free();
263.
}
264.
}
265.
}
266.
}
267.
268.
import java.io.IOException;
269.
import java.net.DatagramPacket;
270.
import java.net.DatagramSocket;
271.
import java.net.SocketAddress;
272.
import java.util.concurrent.TimeUnit;
273.
274.
275.
public class PeerA {
276.
277.
public static void main(String[] args) throws Throwable {
278.
try {
279.
IceClient client = new IceClient(2020, "audio");
280.
client.init();
281.
client.exchangeSdpWithPeer();
282.
client.startConnect();
283.
final DatagramSocket socket = client.getDatagramSocket();
284.
final SocketAddress remoteAddress = client
285.
.getRemotePeerSocketAddress();
286.
System.out.println(socket.toString());
287.
new Thread(new Runnable() {
288.
289.
public void run() {
290.
while (true) {
291.
try {
292.
byte[] buf = new byte[1024];
293.
DatagramPacket packet = new DatagramPacket(buf,
294.
buf.length);
295.
socket.receive(packet);
296.
System.out.println("receive:"
297.
+ new String(packet.getData(), 0, packet
298.
.getLength()));
299.
} catch (IOException e) {
300.
// TODO Auto-generated catch block
301.
e.printStackTrace();
302.
}
303.
304.
}
305.
}
306.
}).start();
307.
308.
new Thread(new Runnable() {
309.
310.
public void run() {
311.
int count = 1;
312.
while (true) {
313.
try {
314.
byte[] buf = ("send msg " + count++ + "").getBytes();
315.
DatagramPacket packet = new DatagramPacket(buf,
316.
buf.length);
317.
318.
packet.setSocketAddress(remoteAddress);
319.
socket.send(packet);
320.
System.out.println("send msg");
321.
TimeUnit.SECONDS.sleep(10);
322.
} catch (Exception e) {
323.
// TODO Auto-generated catch block
324.
e.printStackTrace();
325.
}
326.
327.
}
328.
}
329.
}).start();
330.
} catch (Exception e) {
331.
// TODO Auto-generated catch block
332.
e.printStackTrace();
333.
}
334.
335.
}
336.
337.
}
发表评论
-
netty4更新详解
2015-11-14 10:52 644netty现在应该是java界最流行的网络框架之一了,高性能, ... -
Lua使用protocolbuf
2015-11-10 16:04 871在https://code.google.com/p/prot ... -
领域模型设计
2015-09-12 17:29 696一:面向对象设计中最简单的部分与最难的部分 如果说事务脚本是 ... -
关于分表与分库思路
2015-07-06 15:36 630首先主要实现应该参考开源产品,目前比较能上台面的是 tddl, ... -
音视频即时通讯开发中使用P2P技术的好处
2015-04-01 02:59 745在服务器的配置文件“A ... -
nat穿透原理
2015-04-01 02:01 1055一直以来,说起NAT穿透,很多人都会被告知使用UDP打孔这个技 ... -
Erlang学习记录(二)——基本数据类型
2015-03-30 03:51 476Erlang学习记录(二)—— ... -
集群、分布式、负载均衡区别与联系
2015-03-25 22:54 5941、Linux集群主要分成三 ... -
: 结构化数据的共享存储
2015-03-24 04:25 0开发笔记 (6) : 结构化数据的共享存储 开始这个话题前, ... -
:如何构建超强伸缩性的游戏服务器而集容错、负载均衡和无限伸缩性于一身
2015-03-24 04:04 921附标题:如何构建超强 ... -
edis在游戏服务器中的应用
2015-03-24 02:57 625edis在游戏服务器中的应 ... -
社交游戏之双机热备方案 预防单点故障
2015-03-23 04:46 883某一天深夜,单盘配置的服务器出现硬盘损坏,导致该服务器上所提 ... -
游戏服务器集群设计思路
2015-03-23 04:45 798对于我们的游戏服务器端来说,除了要满足一般的MMO服务 ... -
Erlang类型及函数声明规格
2015-03-04 14:33 665Erlang类型及函数声明规格 Author: Mail: ... -
(转)erlang lists模块函数使用大全
2015-02-12 16:26 629一,带函数Pred 1, all(Pred ... -
超越并行框架erlang之流的通讯框架
2015-02-05 17:41 660http://blog.codingnow.com/2011/ ... -
如何使用 Oracle Linux 中的硬件故障管理
2014-11-10 14:38 1478如何使用 Oracle Linux 中 ... -
配置nginx
2014-06-14 18:04 621http://www.cnblogs.com/wenanry/ ... -
centos 安装mysql
2014-06-13 12:23 602你是root權限嗎?_操作系統的_ 兩種方法 1 使用替換法 ... -
一个定期备份MySQL数据库的Shell脚本
2014-06-11 10:01 911一个定期备份MySQL数据库的Shell脚本 S ...
相关推荐
在实际应用中,根据网络环境和特定需求选择合适的NAT穿透解决方案至关重要。例如,对于VoIP应用,ICE技术因为提供了灵活的解决方案而被广泛使用。而对于需要在严格的安全和管理政策下运行的网络环境,SBC可能是一个...
NAT穿透与P2P通信是互联网通信领域中的关键技术,特别是在多点通信、实时...使用这些工具和库,开发者可以构建出更加高效、稳定且低延迟的P2P通信解决方案,使得用户无论身处何地,都能实时查看到家中的摄像头画面。
9. **安全性**:在设计NAT穿透解决方案时,必须考虑到安全性,避免未授权的设备参与通信或滥用服务器资源。 以上就是关于“Java实现UDP穿透NAT技术”的一些核心知识点,实际的代码实现会根据具体的需求和环境有所...
### P2P网络中TCP穿透NAT的解决方案 #### 引言 随着互联网的迅猛发展,IPv4地址资源日益紧张,网络地址转换(NAT)技术成为缓解IP地址不足的有效手段。然而,NAT的引入同时也给点对点(P2P)通信带来了挑战,尤其...
因此,NAT穿透技术应运而生,它旨在解决这个问题,使得内网设备可以有效地与外部网络设备进行通信。 NAT主要分为三种类型:一对一NAT、端口地址转换PAT(Port Address Translation)和网络级NAT。一对一NAT是每个...
### NAT穿透研究论文知识点概述 #### 一、引言与背景 随着互联网技术的发展和网络带宽的不断...通过上述技术,开发者可以根据实际应用场景选择最适合的NAT穿透方案,确保网络应用程序能够在存在NAT的环境下正常运行。
### 端到端通信中TCP穿越NAT的解决方案 #### 概述 随着互联网的快速发展及IPv4地址资源的紧缺,网络地址转换(Network Address Translation, NAT)技术被广泛应用于解决IP地址不足的问题。然而,NAT技术的普及却给...
3. **ICE(Interactive Connectivity Establishment)**:这是一种综合性的NAT穿透方案,结合了STUN和 TURN(Traversal Using Relays around NAT)服务器。TURN服务器作为中继,当直接的UDP Hole Punching失败时,...
**UDP穿透解决方案** 1. **STUN(简单Traversal of NAT)服务器**:客户端向公网上的STUN服务器发送请求,服务器返回客户端的公网映射地址和端口,客户端利用这些信息尝试与另一端的客户端建立连接。 2. **TURN...
用户可能需要先阅读文档理解原理,然后运行脚本来测试和应用这个解决方案。 TCP穿透NAT的实现通常包括以下几个步骤: 1. 客户端A和B分别与公共服务器建立TCP连接。 2. 服务器记录A和B的公网IP和端口。 3. 服务器将B...
本文提出了一种结合UPnP与打洞技术的方法,通过UPnP提供的自动端口映射能力,为P2P通信在智能家居中的NAT穿透提供了有效的解决方案。 文章的研究表明,在实现设备的网络连接需求时,针对NAT穿透问题的解决方案至关...
- `FileTransViaNAT.sln`:这是Visual Studio的解决方案文件,包含了项目的所有信息。 - `FileTransViaNAT.suo`:这是Visual Studio的用户选项文件,保存了用户的个性化设置,不影响程序逻辑。 - `FileRcv`和`...
ICE是一种更现代的解决NAT穿透的方法,它结合了STUN(Session Traversal Utilities for NAT)和TURN(Traversal Using Relays around NAT)协议。STUN服务器帮助终端发现其公共IP地址和端口,而TURN服务器作为中继,...
标题 "P2P穿透NAT源代码 delphi2007" 提到的是一个使用Delphi 2007开发的程序,其...同时,通过阅读和调试代码,开发者可以掌握如何在实际应用中处理NAT问题,这对于构建分布式系统和跨NAT环境的通信解决方案至关重要。
二、NAT穿透技术 1. 非对称NAT:在非对称NAT中,设备的出站和入站连接使用不同的端口映射,因此常规的TCP/UDP端口复用策略无法工作。 2. 对称NAT:对称NAT根据源IP和端口以及目的IP和端口创建不同的映射,进一步增加...
详细介绍了Nat穿透的几种解决方案。
IPv6解决方案的双栈+NAT技术涉及到互联网协议版本6(IPv6)和网络地址转换(NAT)的技术融合。IPv6是为了解决IPv4地址耗尽的问题而设计的下一代互联网协议。它提供了几乎无限的地址空间,并且在安全性、自动配置和...