<
>
上一篇

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

Java_web+MySQL演示
   目录
  1. 一个问题
  2. 两个名词
    1. 简要说明【理解NAT】
  3. 三个方法
  4. 若干推荐
  5. 测试环境
  6. UDP打洞【1对1】
    1. 实现原理
    2. 效果展示
    3. 代码解析
  7. UDP打洞【1对n】
    1. 设计流程
    2. 效果展示
    3. 代码解析
  8. TCP穿透
  9. 参考链接
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