企业培训资讯_企业培训干货

当前位置:首页 > 新闻中心

网络穿透与音视频技术——常见网络穿越方法(NAT检测实践2)【华体会】

发布时间:2021-03-27    来源:华体会45177

本文摘要:(接上文《网络穿透与音视频技术(4)——NAT检测实践1》)2.3、检测历程实战——客户端2.2.3、主要代码——IP获取工具类这里注意一个问题:许多情况我们的客户端会有多个IP,但实际上我们只需要基于一个IP举行NAT检测。

(接上文《网络穿透与音视频技术(4)——NAT检测实践1》)2.3、检测历程实战——客户端2.2.3、主要代码——IP获取工具类这里注意一个问题:许多情况我们的客户端会有多个IP,但实际上我们只需要基于一个IP举行NAT检测。这里笔者给出一个工具类,可以为开发人员从客户端的多个IP下,返回一个满足条件的可用的IP。

package testCoordinate.utils;import java.io.IOException;import java.net.InetAddress;import java.net.NetworkInterface;import java.net.SocketException;import java.util.ArrayList;import java.util.Comparator;import java.util.Enumeration;import java.util.List;import java.util.regex.Pattern;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * 本历程JVM的内网IP地址<br> * 工具内,不允许举行继续和实例化操作 * @author yinwenjie */public final class NetIpUtils { /** * 日志 */ private final static Logger LOGGER = LoggerFactory.getLogger(NetIpUtils.class); private NetIpUtils() {} /** * 获取当前操作系统的一个非环路IP * @return */ public static String getNLoopAddress() { List<InetAddress> localAddresses = getLocalAddress(); if(localAddresses == null || localAddresses.isEmpty()) { return null; } return localAddresses.get(0).getHostAddress(); } /** * 获取当前操作系统的一个内网可用IP,也就是说网段为一下规模的IP,C类网段优先<br> * A类 10.0.0.0-10.255.255.255 <br> * B类 172.16.0.0-172.31.255.255 <br> * C类 192.168.0.0-192.168.255.255 <br> * @return 如果没有任何内网IP,则返回空 */ public static String getIntranetAddress() { String intranetAddress = null; List<String> ips = new ArrayList<>(); List<InetAddress> localAddresses = getLocalAddress(); if(localAddresses == null || localAddresses.isEmpty()) { return null; } localAddresses.stream().forEach(item -> ips.add(item.getHostAddress())); // 开始举行选择 if(ips.isEmpty()) return null; // C类地址优先 ips.sort(new Comparator<String>() { @Override public int compare(String o1, String o2) { return o2.compareTo(o1); } }); for (String ip : ips) { if(isInnerIP(ip)) return ip; } return intranetAddress; } /** * 获取当前操作系统的多个可用的内网IP(),也就是说网段为一下规模的IP<br> * A类 10.0.0.0-10.255.255.255 <br> * B类 172.16.0.0-172.31.255.255 <br> * C类 192.168.0.0-192.168.255.255 <br> * @return 如果没有任何内网IP,则返回空 */ public static String[] getIntranetAddresses() { List<InetAddress> localAddresses = getLocalAddress(); if(localAddresses == null || localAddresses.isEmpty()) { return null; } for (int index = 0 ; index < localAddresses.size() ; index++) { InetAddress ip = localAddresses.get(index); if(isInnerIP(ip.getHostAddress())) { localAddresses.remove(index); index--; } } List<String> ips = new ArrayList<>(); localAddresses.stream().forEach(item -> ips.add(item.getHostAddress())); return ips.toArray(new String[]{}); } /** * 该工具方法用于给挪用者一个当地内网地址,而且这个内网地址是至少可以毗连targetAddresses中的某一个目的源地址的 * @param targetAddresses * @return 如果没有满足要求的内网地址,则返回false */ @SuppressWarnings("rawtypes") public static String getIntranetAddress(String[] targetAddresses) { if(targetAddresses == null || targetAddresses.length == 0) { return null; } try { for (Enumeration ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements();) { NetworkInterface iface = (NetworkInterface) ifaces.nextElement(); if(iface.isVirtual()) { continue; } boolean needCheck = false; InetAddress inetAddr = null; CHECK:for (Enumeration inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements();) { inetAddr = (InetAddress) inetAddrs.nextElement(); // 清除loopback类型地址 if (!inetAddr.isLoopbackAddress() && inetAddr.isSiteLocalAddress()) { needCheck = true; break CHECK; } } // 如果条件建立,则不需要举行连通性测试 if(!needCheck || inetAddr == null) { continue; } for (String targetAddress : targetAddresses) { if(InetAddress.getByName(targetAddress).isReachable(iface, 0, 1000)) { return inetAddr.getHostAddress(); } } } } catch (IOException e) { LOGGER.error(e.getMessage() , e); return null; } return null; } /** * @return */ @SuppressWarnings("rawtypes") private static List<InetAddress> getLocalAddress() { List<InetAddress> localAddresses = new ArrayList<>(); try { for (Enumeration ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements();) { NetworkInterface iface = (NetworkInterface) ifaces.nextElement(); if(iface.isVirtual()) { continue; } for (Enumeration inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements();) { InetAddress inetAddr = (InetAddress) inetAddrs.nextElement(); // 清除loopback类型地址 if (!inetAddr.isLoopbackAddress() && inetAddr.isSiteLocalAddress()) { localAddresses.add(inetAddr); } } } } catch (SocketException e) { LOGGER.error(e.getMessage() , e); return null; } return localAddresses; } /** * 判断一个给定的IP是否为保留的内网IP段 * @param ipAddress * @return */ public static boolean isInnerIP(String ipAddress) { // 如果是一个不切合规范的IP地址,就不用判断了(来个简朴的) String patternRegx = "\\d{1,3}(\\.\\d{1,3}){3}"; if(ipAddress == null) return false; if(!Pattern.matches(patternRegx, ipAddress)) return false; /** * 私有IP: * A类 10.0.0.0-10.255.255.255 * B类 172.16.0.0-172.31.255.255 * C类 192.168.0.0-192.168.255.255 */ boolean isInnerIp = false; long ipNum = getIpNum(ipAddress); long aBegin = getIpNum("10.0.0.0"); long aEnd = getIpNum("10.255.255.255"); long bBegin = getIpNum("172.16.0.0"); long bEnd = getIpNum("172.31.255.255"); long cBegin = getIpNum("192.168.0.0"); long cEnd = getIpNum("192.168.255.255"); isInnerIp = isInner(ipNum, aBegin, aEnd) || isInner(ipNum, bBegin, bEnd) || isInner(ipNum, cBegin, cEnd); return isInnerIp; } private static long getIpNum(String ipAddress) { String[] ip = ipAddress.split("\\."); int a = (Integer.parseInt(ip[0]) << 24) & 0xFFFFFFFF; int b = (Integer.parseInt(ip[1]) << 16) & 0xFFFFFFFF; int c = (Integer.parseInt(ip[2]) << 8) & 0xFFFFFFFF; int d = Integer.parseInt(ip[3]) & 0xFFFFFFFF; return a + b + c + d; } private static boolean isInner(long userIp, long begin, long end) { return (userIp >= begin) && (userIp <= end); }}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199以上工具不光可以用在NAT检测的客户端准备历程,实际上还可以用在许多技术场景下(例如判断指定的IP信息是否是一个规范的内网地址),读者可以凭据情况直接举行使用。2.2.4、主要检测思路这之前的文章中已经举行了先容,NAT映射实现方式的检测顺序为,首先检查客户端和服务端的网络毗连之间是否至少存在一级NAT设备,如果谜底是肯定的那么举行Symmetric NAT检测,如果不是Symmetric NAT那么接着举行Full Cone NAT检测,如果不是Full Cone NAT则最后举行Address Restricted Cone NAT/Port Restricted Cone NAT 检测。

在整个检测历程中,服务器端只是起到一个辅助作用,主要的判断逻辑还是在客户端举行。基于这样的检测原理,检测客户端法式的设计思路如下图所示:如说图所示,监测客户端将请求发送和检测信息吸收划分可做成两个线程,主控制线程卖力控制整个监测顺序和检测节奏——通过阻塞行列向检测请求发送线程推送信息,而且在当前阶段的检测效果还没有获得响应(或者等候超时)之前举行阻塞等候。

华体会体育

华体会体育

主控制线程还在每个阶段吸收到响应效果后,举行NAT类型简直认。2.2.5、主要检测代码以下代码是请求发送线程:/** * 检测请求发送线程 * @author yinwenjie */private static class CheckRequestTask implements Runnable { private BlockingQueue<JSONObject> messageQueue; private String serverIp; private Integer serverPort; private DatagramChannel udpChannel; public CheckRequestTask(String serverIp ,Integer serverPort ,BlockingQueue<JSONObject> messageQueue , DatagramChannel udpChannel) { this.serverIp = serverIp; this.serverPort = serverPort; this.messageQueue = messageQueue; this.udpChannel = udpChannel; } @Override public void run() { while(true) { try { doHandle(); } catch(Exception e) { LOGGER.error(e.getMessage() , e); } } } /** * 举行发送 * @throws IOException */ private void doHandle() throws IOException { JSONObject jsonObject; try { jsonObject = messageQueue.take(); } catch (InterruptedException e) { LOGGER.error(e.getMessage() , e); return; } // 准备发送,凭据差别的type,使用差别的channel举行发送 String jsonContext = jsonObject.toJSONString(); byte[] jsonBytes = jsonContext.getBytes(); // 发送 LOGGER.info("客户端向检测服务[" + serverIp + ":" + serverPort + "]发送检测请求===:" + jsonContext); synchronized (CheckClient.class) { ByteBuffer conentBytes = ByteBuffer.allocateDirect(jsonBytes.length); try { udpChannel.connect(new InetSocketAddress(serverIp, serverPort)); conentBytes.put(jsonBytes); conentBytes.flip(); udpChannel.write(conentBytes); } finally { conentBytes.clear(); udpChannel.disconnect(); } } }}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859以上代码和第三方线程通信的机制就是messageQueue可阻塞行列。

以下代码是检测信息吸收线程:/** * 检测信息吸收线程 * @author yinwenjie */private static class CheckResponseTask implements Runnable { private Selector selector; private BlockingQueue<JSONObject> responseMessagesQueue; public CheckResponseTask(Selector selector ,BlockingQueue<JSONObject> responseMessagesQueue ) { this.selector = selector; this.responseMessagesQueue = responseMessagesQueue; } @Override public void run() { /* * 1、建设UDP Channel的吸收接听 * 2、剖析吸收到的数据报中的内容 * 3、将吸收到的信息发送到响应行列中 * */ while(true) { try { doHandle(); } catch(IOException e) { LOGGER.error(e.getMessage() , e); } } } private void doHandle() throws IOException { // 1、============= ByteBuffer bb = ByteBuffer.allocateDirect(2048); selector.select(); Iterator<SelectionKey> keys = selector.selectedKeys().iterator(); while(keys.hasNext()) { SelectionKey sk = keys.next(); keys.remove(); if(sk.isReadable()) { DatagramChannel curdc = (DatagramChannel) sk.channel(); try { curdc.receive(bb); } catch(Exception e) { LOGGER.warn(e.getMessage() , e); continue; } bb.flip(); byte[] peerbs = new byte[bb.limit()]; for(int i=0;i<bb.limit();i++){ peerbs[i]=bb.get(i); } // 2、============= String receStr = new String(peerbs); JSONObject requestObject = null; Integer type = null; try { requestObject = JSONObject.parseObject(receStr); if(requestObject == null) { continue; } type = requestObject.getInteger("type"); if(type == null) { continue; } } catch(Exception e) { LOGGER.error(e.getMessage() , e); } finally { bb.clear(); } // 3、=============== String targetIp = requestObject.getString("targetIp"); Integer targetPort = requestObject.getInteger("resoucePort"); LOGGER.info("=========吸收到检测效果,来自于服务器[" + targetIp + ":" + targetPort + "] " + receStr); try { this.responseMessagesQueue.put(requestObject); } catch (InterruptedException e) { LOGGER.error(e.getMessage() , e); } } } }}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081同样,检测信息吸收线程也是通过可阻塞行列和其它第三方线程实现交互以下代码是主控制线程:/** * NAT映射实现方式检测法式——客户端 * @author yinwenjie */public class CheckClient { private static Logger LOGGER = LoggerFactory.getLogger(CheckClient.class); public static void main(String[] args) throws Exception { /** * 当前的检查类型type: * 1、检测是否至少有一级NAT设备 * 2、Symmetric NAT检测 * 3、Full Cone NAT检测 * 4、Address Restricted Cone NAT/Port Restricted Cone NAT 检测 */ String serverIp1 = args[0]; String serverPort1Value = args[1]; Integer serverPort1 = Integer.parseInt(serverPort1Value); String serverIp2 = args[2]; String serverPort2Value = args[3]; Integer serverPort2 = Integer.parseInt(serverPort2Value); // 这是客户端的IP 和 端口信息。\\ 这里的目的IP可以举行调整 String clientIp = NetIpUtils.getIntranetAddress(new String[]{"61.139.2.69"}); String clientPortValue = args[4]; Integer clientPort = Integer.parseInt(clientPortValue); // 建设UDP毗连和监听 Selector selector = Selector.open(); DatagramChannel udpChannel = DatagramChannel.open(); udpChannel.configureBlocking(false); udpChannel.socket().bind(new InetSocketAddress(clientIp , clientPort)); udpChannel.register(selector, SelectionKey.OP_READ); /* * 1、使用type = 1的标志,举行“是否有NAT设备的检测” * 2、使用type = 2的标志,举行Symmetric NAT检测 * 3、使用type = 3的标志,举行Full Cone NAT检测 * 4、使用type = 4的标志,举行Address Restricted Cone NAT/Port Restricted Cone NAT 检测 * */ // 专门给服务器 IP1 + PORT1发消息的线程 BlockingQueue<JSONObject> requestMessageQueue1 = new LinkedBlockingQueue<>(); LinkedBlockingQueue<JSONObject> responseMessageQueue = new LinkedBlockingQueue<>(); BlockingQueue<JSONObject> requestMessageQueue2 = new LinkedBlockingQueue<>(); CheckRequestTask checkRequestTask1 = new CheckRequestTask(serverIp1 , serverPort1 , requestMessageQueue1 , udpChannel); Thread checkRequestThread1 = new Thread(checkRequestTask1); checkRequestThread1.start(); // 专门给服务器 IP2 + PORT2发消息的线程 CheckRequestTask checkRequestTask2 = new CheckRequestTask(serverIp2 , serverPort2 , requestMessageQueue2 , udpChannel); Thread checkRequestThread2 = new Thread(checkRequestTask2); checkRequestThread2.start(); CheckResponseTask checkResonanceTask = new CheckResponseTask(selector, responseMessageQueue); Thread checkResonanceThread = new Thread(checkResonanceTask); checkResonanceThread.start(); // 1、以下是检查一============================ // 要求客户端发送type == 1的检查请求(3次) Integer currentType = 1; JSONObject currentResult = checkHandle(currentType, requestMessageQueue1, responseMessageQueue, checkResonanceThread); // 对效果举行判断—— Validate.notNull(currentResult , "网络超时或者当地处置惩罚异常,导致检测失败"); String resouceIp = currentResult.getString("resouceIp"); Integer resoucePort = currentResult.getInteger("resoucePort"); // 如果条件建立,说明两个节点间client 到 server没有任何NAT设备 if(StringUtils.equals(clientIp, resouceIp) && clientPort.intValue() == resoucePort.intValue()) { LOGGER.warn("client和指定的server之间没有任何NAT设备,检查历程终止!"); return; } // 2、以下是检查二============================ currentType = 2; JSONObject currentResult1 = checkHandle(currentType, requestMessageQueue1, responseMessageQueue, checkResonanceThread); Validate.notNull(currentResult1 , "网络超时或者当地处置惩罚异常,导致检测失败"); String resouceIp1 = currentResult1.getString("resouceIp"); Integer resoucePort1 = currentResult1.getInteger("resoucePort"); JSONObject currentResult2 = checkHandle(currentType, requestMessageQueue2, responseMessageQueue, checkResonanceThread); Validate.notNull(currentResult2 , "网络超时或者当地处置惩罚异常,导致检测失败"); String resouceIp2 = currentResult2.getString("resouceIp"); Integer resoucePort2 = currentResult2.getInteger("resoucePort"); // 如果条件建立,说明是Symmetric NAT if(!StringUtils.equals(resouceIp1, resouceIp2) || resoucePort1.intValue() != resoucePort2.intValue()) { LOGGER.info("检查到Symmetric NAT"); return; } // 3、以下是检查三============================ currentType = 3; currentResult = checkHandle(currentType, requestMessageQueue1, responseMessageQueue, checkResonanceThread); if(currentResult != null) { LOGGER.info("检查到Full Cone NAT"); return; } // 4、以下是检查四============================ currentType = 4; currentResult = checkHandle(currentType, requestMessageQueue1, responseMessageQueue, checkResonanceThread); if(currentResult == null) { LOGGER.info("检查到Port Restricted Cone NAT"); } else { LOGGER.info("检查到Address Restricted Cone NAT"); } } /** * 检测类型1、2、3、4通用的网络发包和收报历程 * @param currentType * @param requestMessageQueue * @param responseMessageQueue * @param checkResonanceThread * @return */ private static JSONObject checkHandle(Integer currentType ,BlockingQueue<JSONObject> requestMessageQueue , LinkedBlockingQueue<JSONObject> responseMessageQueue , Thread checkResonanceThread) { JSONObject currentResult = null; try { for(int index = 0 ; index < 3 ; index++) { JSONObject message = new JSONObject(); message.put("type", currentType); message.put("ack", false); requestMessageQueue.put(message); } // 等候和获取服务器响应信息 for(int index = 0 ; index < 3 ; index++) { // 不用等候行列中的消息,有就取,没有就不取 JSONObject responseMessage = responseMessageQueue.poll(); if(responseMessage != null) { Integer responseType = responseMessage.getInteger("type"); if(responseType.intValue() == currentType.intValue()) { currentResult = responseMessage; } else if(responseType.intValue() <= currentType.intValue()) { index--; continue; } } synchronized (checkResonanceThread) { checkResonanceThread.wait(1000); } } } catch(InterruptedException e) { LOGGER.error(e.getMessage() , e); } return currentResult; } // ......... // 发送线程和接受线程作为CheckClient的子类存在于这里 // .........}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148===============================================================(后文将会以上放出的代码举行一些增补说明,然后先容几种现成的NAT检测法式和常见的网络穿透方法论)关注我,学习更多你想要的知识!。


本文关键词:华体会,华体会体育

本文来源:华体会-www.fuhuapan.com

分享到:
【华体会体育】红蓝大战传奇版PK:C罗鲁尼斗魔兽阿扎尔  2大门神 国家防总来渝防御长江嘉陵江涪江超保证洪水-华体会体育
热门文章
2014安徽成人高考10月25日开考12月开始录取 附考生守则:华体会体育
新舟教育迎来人工智能课堂时代可科学规划学习路径
【华体会体育】中国式并购:所有行业国企民企都在涉及重组
华体会体育-沪指暴跌6.42%失4500点 两市近千股跌停
【华体会体育】南华期货:悲观预期笼罩 “小苹果”能逆袭吗?
北京市上半年免疫规划疫苗接种工作平稳有序 建议市民预约接种|华体会体育
安徽三联汽修学院启动抗洪爱心助学 报读任一专业可享助学基金【华体会】
华体会体育_“作出”还是“做出”?“和”还是“及”?“缴纳”还是“交纳”
奥地利小镇火了:当地人抱怨接待不过来
联合贷款管理尚在征求意见 助贷模式是否遭遇围堵?【华体会】
6月底巴西失业率升至13.1%
美军两栖舰受损严重 两名参与灭火水手感染新冠_华体会体育
华体会:河南省公共创业支持项目治理办法(试行)
99%的樱桃催大的?专家指出膨大剂使用安全性有保障
【华体会体育】什么是孩子好的学习方法
客户案例
×