java实现udp打洞,服务器转发消息

来源:互联网 发布:上瘾网络剧第十六集 编辑:程序博客网 时间:2024/06/06 19:16

这篇文章主要面向新手来学习何为udp打洞,并且通过服务器端转发消息。这里更多是通过代码的理解,而不是原理上。事实上,原理性的文章在网上也已经有很多,随便百度一搜就是一大把,而且实质上简单易于上手的代码并不多。所以我总结了一些我对于udp的简单的理解。如有不到位的地方,请指出。另外,服务器我用的是阿里云的服务器,文章中出现的IP都是随手乱写的。

打洞(hole punching),字面上意思很简单明了,老鼠会打洞,打了洞就能够随心所欲在原来封闭的地方跑来跑去。而我们的计算机网络也可以“打洞”,用术语来说就是NAT穿透。关于NAT(Network Address Translation,网络地址转换)不知道的朋友,可以查阅一下资料,这里就不再多说。关于NAT的打洞,我再多说一句,打洞就是在NAT做一个内网和外网的端口映射。(不知道这样说对不对)

好了,废话不多说,进入正题。

图1 打洞过程


这里先解释一下。客户端A这里的IP和端口是本机内网的,往外网发送信息,都是要经过NAT才能顺利发送出去的。

要知道,我们内网往外网发送信息是很容易的事,而外网想要发送信息到内网,则是要先进行打洞。


初步了解了原理,我们便来分析代码。下面是客户端代码。

import java.util.*;import java.net.*;public class Client{private static int SERVER_PORT = 1234;private static String SERVER_IP = "39.38.102.144";public static Map<String,String> map=new HashMap<String,String>();public static void main(String args[]) throws Exception{SocketAddress server = new InetSocketAddress(SERVER_IP, SERVER_PORT);DatagramSocket ds= new DatagramSocket();String str="getIp";byte buff[]=str.getBytes();byte buffer[]=new byte[8*1024];DatagramPacket dp=new DatagramPacket(buff,0,buff.length,server);Scanner sc=new Scanner(System.in);ds.send(dp);//发送信息到服务器dp.setData(buffer,0,8*1024);ds.receive(dp);String message=new String(dp.getData(),0,dp.getLength());System.out.println("本机端口、IP"+message);MyThread mt=new MyThread(ds);Thread thread=new Thread(mt);thread.start();while(true){str=sc.next();if(str.startsWith("aa")){buff=str.getBytes();dp.setData(buff,0,buff.length);ds.send(dp);}else{String tempString=str;Set<String> set=map.keySet();Iterator<String> iter=set.iterator();while(iter.hasNext()){String key=iter.next();String value=map.get(key);tempString="sendMessage"+","+key+","+value+","+tempString;buff=tempString.getBytes();dp.setData(buff,0,buff.length);ds.send(dp);System.out.println("发送成功");}}}}}class MyThread implements Runnable{DatagramSocket ds=null;DatagramPacket dp=null;String message="";public MyThread(DatagramSocket ds){this.ds=ds;}public void run(){try{byte buffer[]=new byte[1024];dp=new DatagramPacket(buffer,0,buffer.length);byte buff[]=new byte[1024];while(true){dp.setData(buff,0,1024);System.out.println("准备接收");ds.receive(dp);message=new String(dp.getData(),0,dp.getLength());if(message.startsWith("udp")){String port=message.split(",")[1];String ip=message.split(",")[2];Client.map.put(port,ip);System.out.println("目标端口、IP:"+port+","+ip);}else{System.out.println("接收到"+message);}}}catch(Exception e){e.printStackTrace();}}}

下面是服务器代码。

import java.net.*;import java.util.*;public class Server{public static Map<String,String> map=new HashMap<String,String>();public static void main(String args[]) throws Exception{int clientPort=0;String ip="";DatagramSocket ds= new DatagramSocket(1234);String message="";String str="";byte by[]=new byte[2000];DatagramPacket dp=new DatagramPacket(by,0,by.length);SocketAddress socketAddress = null;InetSocketAddress inetSocketAddress = null;while(true){System.out.println("wait...");ds.receive(dp);System.out.println("success");message=new String(dp.getData(),0,dp.getLength());System.out.println("message:"+message);socketAddress=dp.getSocketAddress();inetSocketAddress = (InetSocketAddress) socketAddress;clientPort=inetSocketAddress.getPort();ip=inetSocketAddress.getAddress().getHostAddress();if(message.startsWith("getIp")){map.put(String.valueOf(clientPort),ip);str=clientPort+","+ip;byte buf[]=str.getBytes();dp.setData(buf,0,buf.length);ds.send(dp);System.out.println(clientPort+","+ip);}if(message.startsWith("aa")){Set<String> set=map.keySet();Iterator<String> iter=set.iterator();while(iter.hasNext()){String key=iter.next();String value=map.get(key);System.out.println(key+"clientPort"+clientPort);if(clientPort!=Integer.valueOf(key)){str="udp"+","+key+","+value;byte buf[]=str.getBytes();dp.setData(buf,0,buf.length);ds.send(dp);System.out.println("send");}}}if(message.startsWith("sendMessage")){String port=message.split(",")[1];String targetIp=message.split(",")[2];str=message.split(",")[3];byte buf[]=str.getBytes();SocketAddress sa=new InetSocketAddress(targetIp,Integer.valueOf(port));DatagramPacket target=new DatagramPacket(buf,0,buf.length,sa);ds.send(target);}dp.setData(by,0,by.length);}}}

第一步,打洞。客户端和服务端都要DatagramSocket和DatagramPacket对象。服务端(Server)先启动之后到了receive()方法,这是一个阻塞方法,会一直等待接收到消息才继续运行下去;客户端(ClientA)启动之后给服务端发送一个获取本机IP、端口的消息;服务器接收到消息后,立马调用方法并把IP和端口记录在HashMap总并发送到客户端;此时客户端会收到包含IP和端口信息的消息,并且此时打洞已经成功了。

打洞成功之后,原理上客户端和服务端可以互发消息,但是这里服务端的主要功能是转发消息,所以感兴趣的朋友可以在这个基础上修改一下,难度不大。

第二步,获取对方IP和端口。在第一步的基础上,再在另外一台计算机上启动另外一个客户端(ClientB)。ClientB再次完成了第一步的操作。重点来了,客户端为了能够同时进行收发信息,这里用了多线程,并且为了避免生成DatagramSocket对象时会再产生一个端口,我们要往多线程中传入刚才用过了的DatagramSocket对象。当然这是程序自上往下就会执行的内容,下面是要我们自己操作的内容。在Server,ClinetA和ClientB都启动了的基础上,两个客户端都往服务端发送"aa",其作用是获取对方的IP和端口,服务器接收到这个消息后,立马从HashMap中取得IP和端口,并往客户端发送。客户端接收到消息后,进行解析并存入HashMap中。

第三步,服务器转发消息。ClientA(或ClientB)输入消息后,程序对消息进行封装发送到服务端,其中消息包括从HashMap中取出的IP、端口和输入的消息,服务端收到消息后再进行解析,然后往ClientB(或ClientA)发送消息。由于之前已经打洞,所以ClientB(或ClientA)是可以接收到服务器转发的消息的。


看到这里,不知道大家是不是对udp打洞和服务端转发消息有所理解了呢?其中还有不少细节的地方,大家感兴趣的话不妨花点时间看一下怎么样呢?

原创粉丝点击