欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

用java实现一个简单的p2p连接以及stun NAT探测

程序员文章站 2022-07-01 15:30:16
...

        网上有很多相关的理论都大同小异,但java实现的不多,我就实现了一下,其实是没有什么意义的,只是因为提高一下编程水平,而且我还没有条件去做实验,因为这里要用到两个外网ip,所以代码到底行不行不一定,但是我觉得应该能行!至少理论实现上是对的。。。好吧,我还是不确定用java实现一个简单的p2p连接以及stun NAT探测用java实现一个简单的p2p连接以及stun NAT探测用java实现一个简单的p2p连接以及stun NAT探测

        具体实现全程都是用UDP来进行的,因为tcp需要三次握手连接,发送完随时都会关闭,关闭就不能发送了,而p2p需要长期的发送与接收的。stun的所用是探测NAT的类型,根据本内网的NAT和对方内网的NAT来进行相应的p2p连接

        p2p连接的基本步骤是:

        1.客户端A登录服务器S(stun)

        2.客户端B登录服务器S(stun)

        3.S记录下A,B的NAT信息,下次登录不用stun,另外也要把所有登录上S的客户端记录上,发送给其他每个客户端,这样每个客户端就知道可以连接的对象了

        3.A想连接B,给S发送个信息,这个信息一定要包含B的外网的ip,端口

        4.S发送信息给B,这个信息一定包含A的外网的ip,端口。B向A发送信息(打洞),A肯定接受不到,但不所谓,目的是打洞而已(在B的NAT中建立session)。

        5,.这样A就可以向B发送信息了

        下面的代码:

服务器的代码---

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Set;

public class P2PServer {
	protected static int serverPort;
	protected static int proxyPort;
	protected static String proxyIp;
	//内存缓存
	protected static HashMap<String,HashMap<Integer,Integer>> clientList=new HashMap<String,HashMap<Integer,Integer>>();
	private static StringBuffer lists=null;
	private static int udp_size=500;
	//服务器主程序
	public static void main(){
		try{
		Properties p=Stupid.getPZ();
		int p1=Integer.parseInt(p.getProperty("serverPort"));
		int p2=Integer.parseInt(p.getProperty("port"));
		String ip=p.getProperty("ip");
		serverPort=p1;
		proxyPort=p2;
		proxyIp=ip;	
		DatagramSocket socket=new DatagramSocket(serverPort);
		while(true){//下面是并发代码
			DatagramPacket request=new DatagramPacket(new byte[1024],1024);
			socket.receive(request);
			DatagramPacket response=null;
			String msg=null;
			//判断是不是已有数据
			int port=request.getPort();
			if(clientExit(request.getAddress().getHostAddress(),request.getPort())!=-1){
				msg=""+clientList.get(request.getAddress().getHostAddress()).get(request.getPort());
				response=new DatagramPacket(msg.getBytes(),msg.getBytes().length,request.getAddress(),request.getPort());
				socket.send(response);
				continue;
			}
			String bin=new String(request.getData(),0,request.getLength(),"ASCII");
			switch(bin){
			case "1":
				InetAddress i=request.getAddress();
				msg=i.getHostAddress()+":"+port;
				response=new DatagramPacket(msg.getBytes(), msg.getBytes().length,request.getAddress(),request.getPort());
				socket.send(response);
				break;
			case "2":
				//切换ip或者用udp代理,这里用代理
				i=InetAddress.getByName(proxyIp);
				response=new DatagramPacket(msg.getBytes(),msg.getBytes().length,i,proxyPort); 
				socket.send(response);
				break;
			case "3":
				getLists();
				int j=0;
				while(j!=-1){
					byte[] bn=new byte[500];
					j=readString(bn,j,j+500);
					response=new DatagramPacket(bn,bn.length,request.getAddress(),request.getPort());
					socket.send(response);
					DatagramPacket dp=new DatagramPacket(new byte[1],1);
					socket.receive(dp);
				}
				byte[] mop="-1".getBytes();
				response=new DatagramPacket(mop,mop.length,request.getAddress(),request.getPort());
				socket.send(response);
			default:
				if(bin.startsWith(">>>")){
					bin=bin.substring(3, bin.length());
					String[] bs=bin.split(":");
					bin=">>>"+request.getAddress().getHostAddress()+":"+request.getPort();
					response=new DatagramPacket(bin.getBytes(),
								bin.getBytes().length,InetAddress.getByName(bs[0]),Integer.parseInt(bs[1]));
					socket.send(response);
				}else if(bin.startsWith("<<<")){//存储客户端的NAT类型
					String ipx=request.getAddress().getHostAddress();
					HashMap<Integer,Integer> h1=new HashMap<Integer,Integer>(); 
					if(!clientList.containsKey(ipx)){
					h1.put(request.getPort(), Integer.parseInt(bin.substring(3,bin.length())));
					clientList.put(ipx, h1);
					}else{
						clientList.get(ipx).put(request.getPort(), Integer.parseInt(bin.substring(3,bin.length())));
					}
				}
			}
		}		
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	public static int clientExit(String ip,int port){
		if(!clientList.containsKey(ip))
		return -1;
		HashMap<Integer,Integer> l=clientList.get(ip);
		
		if(l.containsKey(port))
			return l.get(port);
		
		return -1;
	} 
	
	public static synchronized void getLists(){
		if(lists!=null)
			return;
		Set<String> ss=clientList.keySet();
		Iterator<String> it=ss.iterator();
		while(it.hasNext()){
			String k=it.next();
			HashMap<Integer,Integer> h=clientList.get(k);
			Iterator<Integer> it1=h.keySet().iterator();
			while(it1.hasNext()){
				Integer i=it1.next();
				Integer i1=h.get(i);
				lists.append(k+":"+i+":"+i1+" ");
			}
		}
	}
	
	public static int readString(byte[] r,int si,int oi){
		String s=lists.toString();
		byte[] b=s.getBytes();
		int l=b.length;
		for(int i=si;i<=oi;i++){
			b[i]=r[i];
		}
		if(oi>=l){
			return -1;
		}
		return oi+1;
	}
}

客户端的代码---

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Properties;

public class P2PClient {

	private static String serverIp;
	private static int serverPort;
	private static int proxyPort;
	private static String proxyIp;
	private static int clientPort;
	
	private static boolean full_cone_nat=false;
	private static boolean cone_nat=true;
	private static boolean full_Port_Cone_nat=true;
	private static boolean own_ip=false;
	
	protected static DatagramSocket socket=null;
	protected static DatagramPacket request;
	protected static DatagramPacket response;
	protected static HashMap<String,Integer> hm;
	
	protected P2PClient() throws Exception{
		Properties p=Stupid.getPZ();
		int port=Integer.parseInt(p.getProperty("serverPort"));
		String ip=p.getProperty("serverIp");
		serverIp=ip;
		serverPort=port;
		int _port=Integer.parseInt(p.getProperty("port"));
		String _ip=p.getProperty("ip");
		proxyIp=_ip;
		proxyPort=_port;
		clientPort=Integer.parseInt(p.getProperty("clientPort"));
		socket=new DatagramSocket(clientPort);
	}
	
	//首先用stun探测NAT类型
	public void stun() throws Exception{
		try{
		socket.setSoTimeout(5000);
		InetAddress host=InetAddress.getByName(serverIp);
		 request=new DatagramPacket("1".getBytes(),"1".getBytes().length,host,serverPort);
		 response=new DatagramPacket(new byte[1024],1024);
		String result=null;
		try{
		socket.send(request);
		socket.receive(response);
		result=new String(response.getData(),0,response.getLength(),"ASCII");
		}catch(Exception e){
			//超时抛异常
			System.out.println("服务器炸了或者你的路由器坏了"); 
			throw new Exception("nat error");
		}
			System.out.println("stun step 1 response from server "+result);
			String[] rss=result.split(":");
			if(rss.length==0){
				int r=Integer.parseInt(result);
				if(r==0)
					own_ip=true;
				else if(r==11)
					full_cone_nat=true;
				else if(r==666)
					cone_nat=false;
				return;
			}
			String ip=rss[0];
			String local=InetAddress.getLocalHost().getHostAddress();
			int NATport=Integer.parseInt(rss[1]);
			if(!ip.equals(local)){
				//客户端在内网里面
				request=new DatagramPacket("2".getBytes(),"2".getBytes().length,host,serverPort);
				response=new DatagramPacket(new byte[1024],1024);
				try{
				socket.send(request);
				socket.receive(response);//这里接收的是来自代理服务器
				full_cone_nat=true;
				System.out.println("全锥形NAT");
				
				}catch(Exception e){
					//一般肯定会到这里
					System.out.println("受限锥形NAT");
				}
				InetAddress i=InetAddress.getByName(proxyIp);
				request=new DatagramPacket(new byte[1],1,i,proxyPort);
				if(!full_cone_nat){
					response=new DatagramPacket(new byte[1024],1024);	
					socket.send(request);
					socket.receive(response);
					int _port=Integer.parseInt(new String(response.getData(),"ASCII"));
					if(_port!=NATport){
						System.out.println("对称NAT");
						cone_nat=false;//对称NAT 不是圆锥NAT
						return;
					}
				}
				
				socket.send(request);//在发送 让代理换个端口发回来
				try{
					response=new DatagramPacket(new byte[1024],1024);
					socket.receive(response);
				}catch(Exception e){
					full_Port_Cone_nat=false;
					System.out.println("端口受限锥形NAT");
				}
				
			}else{
				System.out.println("客户端拥有自己的外网ip,并不具有NAT");
				own_ip=true;
			}
		
		}catch(Exception e){
			throw e;
		}finally{//记录下来,下次不再探测
			DatagramPacket dp;
			String m="";
			if(!own_ip&&cone_nat){
				if(full_cone_nat){
					m="<<<"+11;
				}else{
					m="<<<"+10;
				}
			}else if(own_ip){
					m="<<<"+0;
			}else if(!cone_nat){
				    m="<<<"+666;
			}
			if(m!=""){
				dp=new DatagramPacket(m.getBytes(),m.getBytes().length,
						InetAddress.getByName(serverIp),serverPort);
				socket.send(dp);
			}
			System.out.println("探测NAT完成,与服务器成功建立连接");
			
			//if(socket!=null)
			//socket.close();
		}
	}
	public void bin(String ip,int port) throws Exception{
		InetAddress in=InetAddress.getByName(serverIp);
		String bin=">>>"+ip+":"+port;
		request=new DatagramPacket(bin.getBytes(),bin.getBytes().length,in,serverPort);
		socket.send(request);
		response=new DatagramPacket(new byte[1024],1024);
		try{
		socket.receive(response);
		}catch(Exception e){//来自目的主机的访问,肯定到不了
			System.out.println("连接成功");
		}
	}
	//获取其他客户端以及他们的NAT属性
	public void getClients() throws Exception{
		request=new DatagramPacket("3".getBytes(),"3".getBytes().length,
				InetAddress.getByName(serverIp),serverPort);
		
	}
	
	public void connect(String ip,int port) throws Exception{
		//首先探测NAT 看看能不能p2p
		synchronized(socket){
		stun();
		getClients();
		socket.notify();//唤醒其他进程,本线程进入等待状态
                socket.wait();
		String key=ip+":"+port;
		int bz=hm.get(key);
		//获得客户端B的NAT状态
		if(!cone_nat || bz==666){
			System.out.println("连接失败");
			return;
		}else if(bz==11 && !full_cone_nat){//目标客户端是全圆锥的,直联
			String bin="---";
			request=new DatagramPacket(bin.getBytes(),bin.getBytes().length,InetAddress.getByName(ip),port);
			socket.send(request);
			response=new DatagramPacket(new byte[1024],1024);
			try{
			socket.receive(response);
			}catch(Exception e){//来自目的主机的访问,肯定到不了
				System.out.println("连接成功");
			}
		}else if(bz==11 && full_cone_nat){//两个都是全圆锥,这种情况就不需要进行连接了
				System.out.println("连接成功");
		}else{
			bin(ip,port);
		}
		//连接结束,之后要把锁释放掉,交给p2pclientserver,让其他主机来访问本主机
		socket.notify();
		}
	}
	public static void main(String[] args){
		try{
		P2PClient p =new P2PClient();
                Thread t=new thread(new P2PClientServer());
                t.start();
		p.connect("0.0.0.0", -1);
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}

同样是客户端的,但这是客户端监听程序的代码---

import java.net.DatagramPacket;
import java.net.InetAddress;

public class P2PClientServer extends P2PClient implements Runnable{

	protected P2PClientServer() throws Exception {
		super();
	}
    private StringBuffer sb=null;
	@Override
	public void run() {
		synchronized(socket){
		try{
			socket.wait();//上来就堵住
		DatagramPacket request=new DatagramPacket(new byte[500],500);
    	while(true){
    		socket.receive(request); 
    		
    		String bin=new String(request.getData(),"ASCII");
    		if(bin.startsWith(">>>")){//请求客户端向某客户端打洞,来建立点对点连接
    			bin=bin.substring(3, bin.length());
				String[] bs=bin.split(":");
				bin=">>>"+request.getAddress().getHostAddress()+":"+request.getPort();
				DatagramPacket response=new DatagramPacket("---".getBytes(),
						"---".getBytes().length,InetAddress.getByName(bs[0]),Integer.parseInt(bs[1]));
				socket.send(response);
    		}else{
    			//接收server 3的发送过来的所有客户端的相关数据,NAT类型,ip,port等
    			if(!"-1".equals(bin)){
    			sb.append(bin.trim());
    			}
    			String[] sm=sb.toString().split(" ");
    			for(int i=0;i<sm.length;i++){
    				String[] op=sm[i].split(":");
    				hm.put(op[0]+":"+op[1], Integer.parseInt(op[2]));
    				//这里hm是变化中的所以就不做null的判断了
    			}
    			socket.notify();//唤醒父类的线程
    		}
    	}
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	}
}

代理服务器的代码,这个代理服务器用于stun探测---

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Properties;

public class P2PProxyServer {
	
	private P2PProxyServer(){
		
	}
	//下面是代理服务器的代理,非本服务器代码
		public static void main(String[] args) throws Exception{
			Properties p=Stupid.getPZ();
			int port=Integer.parseInt(p.getProperty("port"));
			int port_=Integer.parseInt(p.getProperty("_port"));
			DatagramSocket socket=new DatagramSocket(port);
			DatagramSocket _socket=null;
			
			while(true){
				
				DatagramPacket request=new DatagramPacket(new byte[1024],1024);
				socket.receive(request);//The following is the shared code
				InetAddress clientIp=null;
				int clientPort=0;
				int _port=0;//session port from server
				String result=new String(request.getData(),"ASCII");
				String[] s=result.split(":");
				
				if(s.length==2){
				clientIp=InetAddress.getByName(s[0]);
				clientPort=Integer.parseInt(s[1]);
				DatagramPacket response=new DatagramPacket(new byte[1],1,clientIp,clientPort);
				socket.send(response);
				}else{
					if(_port==0){
					_port=request.getPort();//session port from client
					String msg=_port+"";
					DatagramPacket response=new DatagramPacket(msg.getBytes(),msg.getBytes().length,clientIp,clientPort);
					socket.send(response);
					}else{
						//换个端口
						_socket=new DatagramSocket(port_);
						DatagramPacket response=new DatagramPacket(new byte[1],1,clientIp,clientPort);
						_socket.send(response);
					}
				}
			}
			
		}
}

一个工具类,没啥东西,就读取个properties配置文件

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.Properties;

import org.Yeah.FHttp;
import org.Yeah.FHttpSocket;

public class Stupid {

	public static Properties getPZ() throws Exception{
		Properties p=new Properties();
		BufferedReader in=new BufferedReader(new InputStreamReader(new FileInputStream(new File("ipAndPort.properties"))));
		p.load(in);
        return p;        
   
	}
	
}

最后是配置文件---

ip=1.1.1.1
port=9998
_port=9997
sererIp=1.1.1.1
serverPort=1314

clientPort=1314

欢迎大家指出我代码中的错误,相互提高