内网穿透实现
如果内网A机器要访问外网C机器,可通过TCP/IP直接连接或发送目标地址C地址的数据包对象。
但C机器如果要发送数据给A机器,则分两种情况。
1.如果是TCP Socket连接,必须在A与C建立TCP/IP连接
2.如果UDP,C必须收到A发来UDP包后,从这个数据包中得到A的回送地址,才可将数据回送给A,但其实C得到的地址并不是内网A的地址,而是网关B上的IP地址和端口。
如果A1要和B1通信,在TCP/IP通信模式下,必须要有一个相当于中转功能的公网服务器C存在,通过C实现了A1和B1的通信,交换数据的全程必须经过C机器。
如果是UDP发送模式下,A1和B1必须首先向C发送一个UDP包,C在收到数据包后,分别记录下A1和B1的数据包的发送地址,当A1要向B1发送数据时,先将数据发送到C,C再将这个数据包的目标地址设定为已记录的B1的地址后发送,B1即可接收到C转发来的A1的数据。
事实是,C得到A1或B1的地址并不是A1或B1在内网发送时的地址。C得到A1的地址是A2的网关地址,B1则是B2的网关IP地址,而端口则是网关上的端口(系统自动选择),并不是你指定的A1和B1上的发送端口
这意味着C向A或B发送数据包时,数据包的目标地址其实是它们的网关IP地址和某一端口,数据包是通过它们的网关转发到A1或B1的。
A网关和B网关是如何得知收到的数据包药发送给内网的某台机器呢?这就必须理解网关的NAT机制,NAT即网络地址转换(Network Address Translation)功能,当A1通过A网关向C发送数据包时,A网关则打开一个UDP端口,在此端口上接收到的数据即认为是应该给A1的数据,A网关则理解为具有端口映射功能的转发器。利用UDP通信的这种映射功能,就可以实现A1和B1之间的P2P通信
A1要和B1要实现相互间直接通信(P2P),必须经过以下步骤。
1.A1向公网服务器C发送数据包,C记录网关A2上的映射地址
2.B1向公网服务器C发送数据包,C记录网关B2上的映射地址。
3.C将A2或B2的地址发送给B1或A1
4.A1或B1再发送数据包时,数据包的目标地址写为收到的对方映射地址,即将数据包发给B2或A1上的地址。网关即可将收到的数据包转发给其映射的内网主机。
到这一步后的通信就和C没关系了,A1和B1只要记住对方的映射地址,即可点对点地将数据发送给对端机器,即实现了P2P通信。
建立P2P通信时,双方首先要通过服务器C交换地址信息,服务器C启动后,当接收到一个客户端发来的数据包时,将这个包的来源地址存入服务器的Set集合,然后将这个集合中的所有已存地址以对象为单位发送给这个客户端。
服务端:(如腾讯服务器)
import java.net.*;
import java.io.*;
import java.util.*;
public class DatagramRouteServer {
private Set<InetSocketAddress> clientAddSet=new HashSet(); //该容器存放所有客户端的地址
//启动UDP服务器,接收消息,转发消息
public void startServer()throws Exception{
DatagramSocket socket=new DatagramSocket(9090);
System.out.println("UDP服务器等待接收数据:"+socket.getLocalAddress());
while(true){
byte[] buffer=new byte[256];
//创建数据包
DatagramPacket packet=new DatagramPacket(buffer,buffer.length);
socket.receive(packet);
//得到发送方IP和端口
InetAddress clientAdd=packet.getAddress();
int clientPort=packet.getPort();
InetSocketAddress address=new InetSocketAddress(clientAdd,clientPort);
//将这个地址加入到Set中
clientAddSet.add(address);
//提取发送内容
byte[] recvData=packet.getData();
String s=new String(recvData).trim();
System.out.println("服务器收到数据:"+s+" 来自:"+address);
//给Set中的每一个地址发送信息,信息内容为新登入服务器的发送方来取地址了
for(InetSocketAddress dclient:clientAddSet){
String temf=address+",到服务器取地址了";
//转发服务器端的地址列表数据
ByteArrayOutputStream bous=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bous);
oos.writeObject(temf); //这里是字符串
oos.flush();
byte[] data=bous.toByteArray();
DatagramPacket mp=new DatagramPacket(data,data.length);
mp.setSocketAddress(dclient);
//DatagramPacket mp=new DatagramPacket(data,data.length,dclient);
socket.send(mp);
}
ByteArrayOutputStream bous=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bous);
oos.writeObject(clientAddSet); //将一个Set写入了流中。object包装过了 。和上面的都是Object强制转换的,故接收时要判断
oos.flush();
byte[] data=bous.toByteArray();
DatagramPacket sendP=new DatagramPacket(data,data.length);
sendP.setSocketAddress(address);
socket.send(sendP);
}
}
public static void main(String[] arg) throws Exception{
new DatagramRouteServer().startServer();
}
}
客户端(如QQA用户和QQB用户)
import java.net.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class DatagramNetClient extends Thread{
//公网服务器地址
private SocketAddress destAdd=new InetSocketAddress("172.16.1.25",9090);
private DatagramSocket sendSocket; //构造函数里初始化
//显示消息的文本框
private JTextArea jta_recive=new JTextArea(10,50); //10*25
//显示其他的客户地址
private JComboBox jta_addList=new JComboBox();
public DatagramNetClient(){
try{
sendSocket=new DatagramSocket();
}catch(Exception e){
e.printStackTrace();
}
}
@Override
public void run() {
try{
while(true){
byte[] recvData=new byte[2048];
DatagramPacket recvPacket=new DatagramPacket(recvData,recvData.length);
sendSocket.receive(recvPacket);
byte[] data=recvPacket.getData(); //收到的是Set中的ip地址,应该也是个对象流
//读取信息
ByteArrayInputStream bins=new ByteArrayInputStream(data);
ObjectInputStream oins=new ObjectInputStream(bins);
Object dataO=oins.readObject();
//收到的是两种信息
if(dataO instanceof Set){
Set<InetSocketAddress> othersAdds=(Set)dataO;
jta_addList.removeAllItems();
for(InetSocketAddress it:othersAdds){
jta_addList.addItem(it);
}
}else if(dataO instanceof String){
String s=(String)dataO;
System.out.println(s+"
");
}else{
String s="unkown msg:"+dataO;
jta_recive.append(s+"
");
}
}
}catch(Exception e){
e.printStackTrace();
}
}
public void setUpUI(){
JFrame jf=new JFrame("p2p测试-客户端");
jf.setLayout(new FlowLayout());
jf.setSize(500,400);
JButton jb_get=new JButton("获取其他客户端地址");
jf.add(jb_get);
jf.add(jta_addList);
jb_get.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
sendRequestMsg("取得地址");
}});
JLabel la_name=new JLabel("接收到的信息:");
JLabel la_users=new JLabel("发送给:");
final JTextField jtf_send=new JTextField(20); //发送输入框
JButton bu_send=new JButton("发送");
JScrollPane js=new JScrollPane(jta_recive);
jf.add(la_name);
jf.add(js);
jf.add(la_users);
jf.add(jtf_send);
jf.add(bu_send);
ActionListener sendListener=new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
String msg=jtf_send.getText();
InetSocketAddress dest=(InetSocketAddress)jta_addList.getSelectedItem(); //核心代码!!
sendP2PMsg(msg,dest);
jtf_send.setText("");
}
};
bu_send.addActionListener(sendListener);
jtf_send.addActionListener(sendListener);
jf.setVisible(true);
jf.setDefaultCloseOperation(3);
}
public void sendRequestMsg(String msg){
try{
byte[] buffer=msg.getBytes();
DatagramPacket dp=new DatagramPacket(buffer,buffer.length,destAdd);
sendSocket.send(dp);
}catch(Exception e){
e.printStackTrace();
}
}
public void sendP2PMsg(String msg,InetSocketAddress dest){
try{
ByteArrayOutputStream bous=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bous);
oos.writeObject(msg);
byte[] buffer=bous.toByteArray();
DatagramPacket dp=new DatagramPacket(buffer,buffer.length,dest);
sendSocket.send(dp);
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args){
DatagramNetClient sender=new DatagramNetClient();
sender.start();
sender.setUpUI();
}
}
联系客服