<
P2P:NAT穿透-UDP打洞
>
上一篇

P2P:局域网内文件传输【带界面】
下一篇

Java_web+MySQL演示
UDP穿透实现【1对1->1对n】

PS: 此次是UDP打洞简单实现的讲解以及进一步的尝试,仅在Cone NAT下可打洞成功

一个问题

UDP打洞能干啥?

  • 通信
    • 跨过防火墙(开放端口)
    • 跨过局域网(Cone NAT)
    • 互相聊天(传递数据)

两个名词

```mermaid graph LR; A(NAT) --> B(基础NAT); A --> C(NAPT); B --> D[静态NAT]; B --> E[动态NAT]; C --> F(锥形NAT); C --> G[对称NAT]; F --> H[完全锥型]; F --> I[受限制锥型]; F --> J[端口受限制]; ```

Alt text

简要说明【理解NAT】

三个方法

若干推荐

不同NAT工作过程及举例: 点我直通车
NAT类型及对应穿透讲解: 点我直通车
介绍STUN穿透NAT的方法: 点我直通车
一个UDP打洞例子(内含测试NAT类型的代码): 点我直通车

测试环境

Alt text
Alt text
Alt text
Alt text
Alt text
Alt text
Alt text

PS:路由器和防火墙的UDP打洞的端口有个时间限制的,在一定时间内如果没有数据通讯会自动关闭

UDP打洞【1对1】

实现原理

三种实现方法,此篇文章讨论实现第一种

Alt text

效果展示

分别为serverS、client1、client2的终端界面
Alt text
Alt text
Alt text

说明:
设定奇数作为clientA,偶数作为clientB 数据只能一条一条的发,同样也只能一条一条的接收。
改进思路:将输入的内容单独存入缓冲区然后开线程来进行发送,并且开线程不断接收数据也存入缓冲区,这样应该显示的效果会更好。

代码解析

    public static void main(String[] args) throws IOException
	{
		System.out.println("server start");
		#绑定端口
		DatagramSocket server = new DatagramSocket(8002);
		byte[] buf = new byte[1024];
		DatagramPacket packet = new DatagramPacket(buf, buf.length);
		int count=0;
		String count_S;
        String clientA_IP = "",clientB_IP="";
        String ClientA="",ClientB="";
		int clientA_port = 0,clientB_port=0;
		InetAddress clientA_address = null,clientB_address=null;
		while(true)#循环监听
		{
			server.receive(packet);
			String requestMessage = new String(packet.getData(), 0, packet.getLength());
			System.out.println(requestMessage);
			if(requestMessage.contains("Request ID")){
                count++;#记录客户端数量
                count_S=String.valueOf(count);
                if(count%2!=0){
                    clientA_port = packet.getPort();
                    clientA_address = packet.getAddress();
                    ClientA=count_S+":" + clientA_address.getHostAddress() + ":" + clientA_port;
                    System.out.println("client "+ClientA);
                    clientA_IP = clientA_address.getHostAddress() + ":" + clientA_port;
                    sendID(count_S,clientA_port,clientA_address,server);#分配ID
                }
                else{
                    clientB_port = packet.getPort();
                    clientB_address = packet.getAddress();
                    ClientB=count_S+":" + clientB_address.getHostAddress() + ":" + clientB_port;
                    System.out.println("client "+ClientB);
                    clientB_IP = clientB_address.getHostAddress() + ":" + clientB_port;
                    sendID(count_S,clientB_port,clientB_address,server);#分配ID
                    try { #延时预防丢包问题
                        Thread.sleep(1000);#延时1秒
                     } catch (Exception e) { 
                         System.out.println("Got an exception!"); 
                     }
                    #异步给新节点与当前已读节点发送对方的的节点信息
                    sendIP(ClientA, clientB_port, clientB_address, server);
                    sendIP(ClientB, clientA_port, clientA_address, server);
                }
			}
		}
	}
	private static void sendID(String id, int port, InetAddress address, DatagramSocket server)
	{#发送ID
		byte[] sendBuf = id.getBytes();
		DatagramPacket sendPacket = new DatagramPacket(sendBuf, sendBuf.length, address, port);
		try {
			server.send(sendPacket);
			System.out.println("ID assignment successful!");
		} catch (IOException e) {
			e.printStackTrace();
		}
    }
	private static void sendIP(String Client, int port, InetAddress address, DatagramSocket server)
	{#发送节点信息
		byte[] sendBuf = Client.getBytes();
		DatagramPacket sendPacket = new DatagramPacket(sendBuf, sendBuf.length, address, port);
		try {
			server.send(sendPacket);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
    #发送请求
    SocketAddress target = new InetSocketAddress("192.168.0.101", 8002);
    DatagramSocket client = new DatagramSocket();
    String message = "Request ID";
    byte[] sendBuf = message.getBytes();
    DatagramPacket pack = new DatagramPacket(sendBuf, sendBuf.length, target);
    client.send(pack);
    #获取分配ID
    byte[] buf = new byte[1024];
    DatagramPacket packet = new DatagramPacket(buf, buf.length);
    client.receive(packet);
    String ID = new String(packet.getData(), 0, packet.getLength());
    Name = ID;
    System.out.println("本机被分配ID为:"+Name);

    #与客户端进行UDP打洞
    while(true){
        client.receive(packet);
        content = new String(packet.getData(), 0, packet.getLength());
        if(content.contains(":"))break;
    }
    String[] str = content.split(":");#字符串转行为字符串数组
    String Client_name = str[0];
    String Client_address = str[1];
    int Client_port = Integer.parseInt(str[2]);
    String requestClient = "Hello, client"+Client_name+". I'm client"+Name;
    sendBuf = requestClient.getBytes();
    SocketAddress ToClient_address = new InetSocketAddress(Client_address, Client_port);
    DatagramPacket sendPacket0 = new DatagramPacket(sendBuf, sendBuf.length, ToClient_address);
    client.send(sendPacket0);
    client.receive(packet);
    String Client_Message = new String(packet.getData(), 0, packet.getLength());
    System.out.println("接收消息:"+Client_Message);
    client.send(sendPacket0);
    System.out.println("成功打洞!"); 
    client.receive(packet);#清空上一个包
    #创建Scanner对象
    #System.in表示标准化输出,也就是键盘输出
    Scanner sc = new Scanner(System.in);
    sc.useDelimiter("\n");#获取包含空格的字符串
    while(true){
        if(sc.hasNext()){#利用hasNextXXX()判断是否还有下一输入项
            String Message = sc.next();#利用nextXXX()方法输出内容
            sendBuf = Message.getBytes();
            SocketAddress To_address = new InetSocketAddress(Client_address, Client_port);
            DatagramPacket sendMessage = new DatagramPacket(sendBuf, sendBuf.length, To_address);
            client.send(sendMessage);
        }
        client.receive(packet);
        String R_Message = new String(packet.getData(), 0, packet.getLength());
        System.out.println("接收消息:"+R_Message);
    }

UDP打洞【1对n】

设计流程

Alt text

效果展示

分别为client1、client2、client3的终端界面
Alt text
Alt text
Alt text

说明:
与多客户端间的打洞成功,但是本人能力有限多人聊天却没有成功,有兴趣的尝试下呀~

代码解析

    public static void main(String[] args) throws IOException
	{
		System.out.println("server start");
		#云服务器对应存储节点信息的列表地址
		String dir="C:/Users/Administrator/Desktop/ListFolder";
		String fileName="List.txt";
		#绑定端口
		DatagramSocket server = new DatagramSocket(8002);
		byte[] buf = new byte[1024];
		DatagramPacket packet = new DatagramPacket(buf, buf.length);
		int count=0;
		String count_S;
		String client_IP = "";
		int client_port = 0;
		InetAddress client_address = null;
		while(true)#循环监听
		{
			server.receive(packet);
			String requestMessage = new String(packet.getData(), 0, packet.getLength());
			System.out.println(requestMessage);
			if(requestMessage.contains("Request ID")){
			#获取数据包内容为对应请求则判断为新节点的加入
				File file = new File(dir,fileName);
				if(count==0)file.createNewFile();#初始服务器需要构建节点列表
				count++;#记录客户端数量
				count_S=String.valueOf(count);
				client_port = packet.getPort();
				client_address = packet.getAddress();
				String NewClient=count_S+":" + client_address.getHostAddress() + ":" + client_port;
				System.out.println("client "+NewClient);
				client_IP = client_address.getHostAddress() + ":" + client_port;
                sendID(count_S,client_port,client_address,server);#分配ID给新节点
				try { #延时预防丢包问题
					Thread.sleep(1000);#延时1秒
				 } catch (Exception e) { 
					 System.out.println("Got an exception!"); 
				 }
                FileOutputStream fos = null;
				fos = new FileOutputStream(file,true);#构建文件流,追加写
				BufferedReader br = new BufferedReader(new FileReader(dir + File.separatorChar + fileName));
                String line=null;
                while((line=br.readLine())!=null) {#按行读取已存节点信息
					String[] str = line.split(":");#根据":"分割节点信息
					InetAddress address = InetAddress.getByName(str[1]); #获取已存节点的IP
					int port=Integer.parseInt(str[2]);#获取已存节点的Port
					String OldClient = line;
					#异步给新节点与当前已读节点发送对方的的节点信息
					sendIP(OldClient, client_port, client_address, server);
					sendIP(NewClient, port, address,server);
					try { #延时3秒,保证异步
						Thread.sleep(3000);
					 } catch (Exception e) { 
						 System.out.println("Got an exception!"); 
					 }
				}
				#给新节点发送Over表示新节点已加入P2P网络
				sendIP("Over!", client_port, client_address, server);
				System.out.println("NewClient shared successful!");
                OutputStreamWriter os = new OutputStreamWriter(fos, "UTF-8");
                PrintWriter pw=new PrintWriter(os);#文件写入流
                pw.println(NewClient);#将新节点更新到节点列表中
				pw.close();
			}
		}
	}
	private static void sendID(String id, int port, InetAddress address, DatagramSocket server)
	{#发送ID
		byte[] sendBuf = id.getBytes();
		DatagramPacket sendPacket = new DatagramPacket(sendBuf, sendBuf.length, address, port);
		try {
			server.send(sendPacket);
			System.out.println("ID assignment successful!");
		} catch (IOException e) {
			e.printStackTrace();
		}
    }
	private static void sendIP(String NewClient, int port, InetAddress address, DatagramSocket server)
	{#发送节点信息
		byte[] sendBuf = NewClient.getBytes();
		DatagramPacket sendPacket = new DatagramPacket(sendBuf, sendBuf.length, address, port);
		try {
			server.send(sendPacket);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	String content ="";
	String NewClient ="";
	String Name = null;
	String dir = "/Users/ling/Desktop";/** 要改*/
	String FileName = "List.txt";
	File file = new File(dir,FileName);
	public void run(){
		try {
			#发送请求
			SocketAddress target = new InetSocketAddress("120.53.16.130", 8002);/** 要改*/
			DatagramSocket client = new DatagramSocket();
			String message = "Request ID";
			byte[] sendBuf = message.getBytes();
			DatagramPacket pack = new DatagramPacket(sendBuf, sendBuf.length, target);
			client.send(pack);
			#获取分配ID
			byte[] buf = new byte[1024];
			DatagramPacket packet = new DatagramPacket(buf, buf.length);
			client.receive(packet);
			String ID = new String(packet.getData(), 0, packet.getLength());
			Name = ID;
			System.out.println("本机被分配ID为:"+Name);

			#与全部客户端进行UDP打洞
			String flag="0";
			while(true){
				while(true){
					client.receive(packet);
					content = new String(packet.getData(), 0, packet.getLength());
					if(content.contains(":"))break;
					else if(content.equals("Over!")) {
						flag="1";
						break;
					}
				}
				if(flag.equals("1"))break;
				else{
					String[] str = content.split(":");#字符串转行为字符串数组
					String Client_name = str[0];
					String Client_address = str[1];
					int Client_port = Integer.parseInt(str[2]);
					String requestClient = "Hello, client"+Client_name+". I'm client"+Name;
					sendBuf = requestClient.getBytes();
					SocketAddress ToClient_address = new InetSocketAddress(Client_address, Client_port);
					DatagramPacket sendPacket0 = new DatagramPacket(sendBuf, sendBuf.length, ToClient_address);
					client.send(sendPacket0);
					client.receive(packet);
					String Client_Message = new String(packet.getData(), 0, packet.getLength());
					System.out.println("接收消息:"+Client_Message);
					client.send(sendPacket0);
					try {
						FileOutputStream fos = null;
						if(!file.exists()){
							file.createNewFile();#如果文件不存在,就创建该文件
							fos = new FileOutputStream(file);#首次写入获取
						}else{
							#如果文件已存在,那么就在文件末尾追加写入
							fos = new FileOutputStream(file,true);#这里构造方法多了一个参数true,表示在文件末尾追加写入
						}
						OutputStreamWriter os = new OutputStreamWriter(fos, "UTF-8");#指定以UTF-8格式写入文件
						PrintWriter pw=new PrintWriter(os);
						pw.println(content);#每输入一个数据,自动换行,便于我们每一行每一行地进行读取
						pw.close();
						os.close();
						fos.close();
					} catch (IOException e) {
						e.printStackTrace();
					}   
				}
			}
			System.out.println("成功接入P2P网络!"); 
			
			#循环监听是否有新节点加入并进行UDP打洞
			while (true) {
				try {
					Thread.sleep(1); 
				} catch (Exception e) {
					e.printStackTrace();
				}
				byte[] buf0 = new byte[1024];
				DatagramPacket packet0 = new DatagramPacket(buf0, buf0.length);
				while(true){
					client.receive(packet0);
					NewClient = new String(packet0.getData(), 0, packet0.getLength());
					if(NewClient.contains(":"))break;
				}
				String[] NewInfo = NewClient.split(":");
				String NewClient_name = NewInfo[0];
				String NewClient_address = NewInfo[1];
				int NewClient_port = Integer.parseInt(NewInfo[2]);
				#应答
				String requestNewClient = "Welcome, client"+NewClient_name+". I'm client"+Name;
				sendBuf = requestNewClient.getBytes();
				SocketAddress ToNewClient_address = new InetSocketAddress(NewClient_address, NewClient_port);
				DatagramPacket sendPacket1 = new DatagramPacket(sendBuf, sendBuf.length, ToNewClient_address);
				client.send(sendPacket1);#丢失
				#接收回复
				client.receive(packet);
				String NewClient_Message = new String(packet.getData(), 0, packet.getLength());
				System.out.println("接收消息:"+NewClient_Message);
				client.send(sendPacket1);
				System.out.println("与Client"+NewClient_name+"NAT穿透成功");
				try {
					FileOutputStream fos = null;
					fos = new FileOutputStream(file,true);#这里构造方法多了一个参数true,表示在文件末尾追加写入
					OutputStreamWriter os = new OutputStreamWriter(fos, "UTF-8");#指定以UTF-8格式写入文件
					PrintWriter pw=new PrintWriter(os);
					pw.println(NewClient);#每输入一个数据,自动换行,便于我们每一行每一行地进行读取
					pw.close();
					os.close();
					fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}   
			}

		} catch (IOException e) {
			System.out.println(e);
		}
	}

TCP穿透

网上查到的两种说法,不知道对错

参考链接

偷摸放个资源: 仅供学习

Top
Foot