最近公司要實如今各類網絡環境下面的多屏互動(機頂盒、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轉發到這個地址,這樣就實現了通訊。html
然而因爲目前存在着各類不一樣類型的NAT設備對NAT有着不一樣的實現方式(將內外地址映射成外網地址的時候有着不一樣的行爲方式),這就給NAT的穿透帶來了麻煩;目前主要的NAT類型有以下幾種:java
1)Full-cone NAT, also known as one-to-one NATandroid
- 一旦一個內網地址 (iAddr:iPort) 被映射到一個外部地址 (eAddr:ePort), 來自 iAddr:iPort 的任何數據包將經過 eAddr:ePort 發送.
- 任何外部主機可以經過eAddr:ePort這個地址發送數據包到iAddr:iPort.
2)Address-restricted-cone NATapache
- 一旦一個內網地址 (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 NATapp
- 來自相同內部ip和port發送到相同目的地ip和port的請求被映射到惟一的外部ip和port地址;若是相同的內部主機採用相同的ip和port地址發送到不一樣的目的地,那麼從新分配映射地址。
- 只有先前收到內部主機發送的包的外部主機纔可以發送返回包到內部主機。
針對前面三種NAT類型(即cone NAT)只要通訊雙方彼此知道對方的內部地址和外部地址的映射關係,而後經過UDP打洞的方式就能夠創建相互鏈接的通訊;可是第四種也就是Symmetric NAT的話因爲每次向不一樣目的地發送數據包時採用不一樣的外部地址,也就沒辦法經過直接的方式創建P2P鏈接。iphone
1.各類網絡環境下的P2P通訊解決方法:socket
(3)若是通訊雙方一方擁有獨立的公網地址另外一方在NAT後面,那麼能夠由位於NAT後面的一方主動發起通訊請求;ide
(4)若是通訊雙方都位於NAT後面,且雙方的NAT類型都是cone NAT,那麼能夠經過一個STUN服務器發現本身的NAT類型以及內網和外網傳輸地址映射信息,而後經過Signaling(信令服務器,實現了SIP協議的主機)交換彼此的NAT類型及內網和外網傳輸地址映射信息,而後經過UDP打洞的方式創建通訊鏈接;
2.協議及用到的相關技術介紹:
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地址;
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地址等。
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
1 /**
2 * Copyright (c) 2014 All Rights Reserved. 3 * TODO 4 */
5
6 import java.beans.PropertyChangeEvent; 7 import java.beans.PropertyChangeListener; 8 import java.io.BufferedReader; 9 import java.io.InputStreamReader; 10 import java.net.DatagramSocket; 11 import java.net.SocketAddress; 12 import java.util.List; 13
14 import org.apache.commons.lang3.StringUtils; 15 import org.apache.log4j.Logger; 16 import org.ice4j.Transport; 17 import org.ice4j.TransportAddress; 18 import org.ice4j.ice.Agent; 19 import org.ice4j.ice.Component; 20 import org.ice4j.ice.IceMediaStream; 21 import org.ice4j.ice.IceProcessingState; 22 import org.ice4j.ice.LocalCandidate; 23 import org.ice4j.ice.NominationStrategy; 24 import org.ice4j.ice.RemoteCandidate; 25 import org.ice4j.ice.harvest.StunCandidateHarvester; 26 import org.ice4j.ice.harvest.TurnCandidateHarvester; 27 import org.ice4j.security.LongTermCredential; 28
29 import test.SdpUtils; 30
31 public class IceClient { 32
33 private int port; 34
35 private String streamName; 36
37 private Agent agent; 38
39 private String localSdp; 40
41 private String remoteSdp; 42
43 private String[] turnServers = new String[] { "stun.jitsi.net:3478" }; 44
45 private String[] stunServers = new String[] { "stun.stunprotocol.org:3478" }; 46
47 private String username = "guest"; 48
49 private String password = "anonymouspower!!"; 50
51 private IceProcessingListener listener; 52
53 static Logger log = Logger.getLogger(IceClient.class); 54
55 public IceClient(int port, String streamName) { 56 this.port = port; 57 this.streamName = streamName; 58 this.listener = new IceProcessingListener(); 59 } 60
61 public void init() throws Throwable { 62
63 agent = createAgent(port, streamName); 64
65 agent.setNominationStrategy(NominationStrategy.NOMINATE_HIGHEST_PRIO); 66
67 agent.addStateChangeListener(listener); 68
69 agent.setControlling(false); 70
71 agent.setTa(10000); 72
73 localSdp = SdpUtils.createSDPDescription(agent); 74
75 log.info("=================== feed the following"
76 + " to the remote agent ==================="); 77
78 System.out.println(localSdp); 79
80 log.info("======================================"
81 + "========================================\n"); 82 } 83
84 public DatagramSocket getDatagramSocket() throws Throwable { 85
86 LocalCandidate localCandidate = agent 87 .getSelectedLocalCandidate(streamName); 88
89 IceMediaStream stream = agent.getStream(streamName); 90 List<Component> components = stream.getComponents(); 91 for (Component c : components) { 92 log.info(c); 93 } 94 log.info(localCandidate.toString()); 95 LocalCandidate candidate = (LocalCandidate) localCandidate; 96 return candidate.getDatagramSocket(); 97
98 } 99
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 password); 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 }