客户端的代码:public class Client { public static final int LOCAL_PORT = 5678;
InetAddress inetAddress = null;
public Client() {
try {
inetAddress = InetAddress.getByName("119.5.191.181");
} catch (UnknownHostException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
System.out.println("开始连接服务器");
//Socket socket = new Socket(InetAddress.getLocalHost(), 6789, InetAddress.getLocalHost(), LOCAL_PORT);
Socket socket = new Socket(inetAddress, 848, InetAddress.getLocalHost(), LOCAL_PORT);
Input_Thread in_ =  new Input_Thread(socket.getInputStream(), socket);
in_.start();
System.out.println("写入线程开启...");
Output_Thread out_ = new Output_Thread(socket.getOutputStream());
out_.start();
System.out.println("输出线程开启...");
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("Message is " + e.getMessage());
}
}

public static void main(String[] args) {
new Client();
}

public static void newConnect(String string) {
String str[] = string.split(":");
InetAddress inet_1 = null;
InetAddress inet_2 = null;
int port_1 = -1;
int port_2 = -1;
try {
System.out.println("连接对方的ip:" + str[3] + "\tPort:" + str[4]);
inet_1 = InetAddress.getByName(str[1]);
port_1 = Integer.parseInt(str[2]);
inet_2 = InetAddress.getByName(str[3]);
port_2 = Integer.parseInt(str[4]);
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
System.out.println("InetAddress:" + e.getMessage());
e.printStackTrace();
}
new ConnectThread(inet_2, port_2);
}
}class ConnectThread extends Thread {
Socket socket = null;
InetAddress inet = null;
int port;

public ConnectThread(InetAddress inet, int port) {
this.inet = inet;
this.port = port;
this.start();
}

public void run() {
System.out.println("成功开启线程...");
try {
this.sleep(4 * 1000);
} catch (InterruptedException e3) {
// TODO Auto-generated catch block
e3.printStackTrace();
}
System.out.println("连接中...");
try {
socket = new Socket(inet, port, InetAddress.getLocalHost(), Client.LOCAL_PORT);
socket.setReuseAddress(true);
} catch (UnknownHostException e2) {
// TODO Auto-generated catch block
System.out.println("Socket_1:" + e2.getMessage());
e2.printStackTrace();
} catch (IOException e2) {
// TODO Auto-generated catch block
System.out.println("Socket_2:" + e2.getMessage());
e2.printStackTrace();
}
if(socket == null) {
System.out.println("\n连接失败...");
}

System.out.println("连接成功...");
String string = "Hello!" + "\r\n";
OutputStream out = null;
try {
out = socket.getOutputStream();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
OutputStreamWriter writer = new OutputStreamWriter(out);
for(int i = 0; i < 3; i ++) {
try {
writer.write(string);
writer.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
InputStream in = null;
try {
in = socket.getInputStream();

while(true) {
byte[] b = new byte[80];
int i = in.read(b);
if (i == -1) {
break;
}
String str = new String(b, 0, b.length);
String s = str.trim();
System.out.println(str);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}class Input_Thread extends Thread {
InputStream in = null;
Socket socket = null;
public Input_Thread(InputStream input, Socket socket) {
this.in = input;
this.socket = socket;
}

public void run() {
try {
while(true) {
byte[] b = new byte[80];
int i = in.read(b);
if (i == -1) {
break;
}
String str = new String(b, 0 , b.length);
String string = str.trim();
System.out.println("Message is " + string);
if (string.indexOf("<connection>") != -1) {
System.out.println("收到建立连接的消息!!!");
socket.close();
Client.newConnect(string);
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("123_" + e.getMessage());
}
try {
in.close();
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("456_" + e.getMessage());
}
}
}class Output_Thread extends Thread {
private static Writer writer = null;
public Output_Thread(OutputStream out) {
writer = new OutputStreamWriter(out);
}

public static void sendMessage(String string) {
try {
writer.write(string);
writer.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

public void run() {
String line;
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
String s = InetAddress.getLocalHost().toString();
String str[] = s.split("/");
System.out.println(str[1] + ":" + Client.LOCAL_PORT);
String first = "<connection>:" + str[1] + ":" + Client.LOCAL_PORT;
writer.write(first);
writer.flush();
while(true) {
line = in.readLine();
if (line.equals(".")) {
break;
}
writer.write(line + "\r\n");
writer.flush();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
writer.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

解决方案 »

  1.   

    我记得NAT穿透技术都是通过UDP才能实现TCP有三次握手机制,必须要有一方做为发起方,一方做为接收方,但是接收方是不可见的,SYN必然失败,无解网上说通过TCP实现NAT穿透都是骗人的,其实质是服务建立两个TCP连接,然后转发数据
      

  2.   


    我在网上看了各种资料,在书上也见到过有说TCP穿透NAT的,不过都是理论的。理论就那么几句话。可是我程序各种试了两三天了,还是不得行呃。
    我在想的是两个客户端相互发送数据,也都在接收数据,我是该用ServerSocket来接收数据呢?还是用Socket来接收呢?
    今天晚上突然看到了端口复用,貌似我用错了serversocket = new ServerSocket();
    serversocket.setReuseAddress(true);
    serversocket.bind(socketAddress);
    socket = new Socket();
    socket.setReuseAddress(true);
    socket.bind(socketAddress);
    socket.connect(new InetSocketAddress(inet, port), 500);
    SocketOutputThread out = new SocketOutputThread(socket);
      

  3.   

    TCP打洞(TCP Hole Punching)
    建立一个P2P的TCP连接在NAT设备(私有网络)之后的主机之间稍微有点复杂相对UDP来说,但是TCP打洞在协议层面上和UDP及其相似。由于TCP打洞不是很好理解,当前已经存在的NAT设备很少支持这样的功能。当相关的NAT设备真的支持它的时候,相比与UDP打洞,TCP打洞既快又可靠。P2P的TCP通讯通过规范的NAT设备实际上可以更健壮相比UDP通讯,因为不像UDP,TCP协议的状态机制会给出一个标准的方法在NAT设备上来精确的维持特定TCP会话的生命周期。4.1 SOCKET和TCP端口重用
    应用程序完成TCP打洞(Hole Punching)主要的现实挑战不是来自于协议本身而是应用程序接
    口(API)问题。因为标准的伯克利套接字API(Berkeley Sockets API)设计的目的是为了客户服务
    器之间的规范通讯,API允许用一个TCP流套接字来初始化一个连接通过函数connet(),或者是通过
    listen() & accept()来接受一个TCP流套接字。而且TCP套接字通常有一对一相应的端口号在主机
    上:在应用程序绑定一个套接字在一个特定本地TCP端口之后,企图绑定相同的套接字在相同的端
    口上将导致失败。
    为了TCP打洞工作,无论如何,我们需要在一个单独的本地TCP端口上监听TCP连接请求,同时
    初始化多个出去的TCP连接。幸运地是,大多数的操作系统支持一个特殊的TCP套接字选项,常见
    命名为:SO_REUSEADDR,它允许应用程序绑定多个套接字在相同的本地端,只要这个选项在所有
    相关的套接字上被设定。BSD引进一个SO_REUSEPORT的选项它控制端口重用区分于地址重用;在这
    样的系统上两个选项都必须设定。4.2 打开P2P的TCP流
    假设客户端A希望和客户端B建立一个TCP联系。我们假设通常的情况A和B已经有活动的TCP连接和
    公共的服务器S。与UDP一样,服务器S记录每个客户端的私有和开放(公共)端点。在协议层,TCP
    打洞和UDP及其相似:
    1. 客户端A用它和服务器S的活动TCP连接会话来要求服务器帮助连接到客户端B。
    2. 服务器S用客户端B的开放(公共)和私有TCP端,同时发送A一对(公共和私有)端点
    到客户端B。
    3. 在A和B用来和服务器S连接的本地端口上,A和B同时异步地产生出去连接尝试在对方地公
    共和私有端点(服务器提供),同时同步地监听入进连接在它们各自的本地TCP端口。
    4. A和B等待发出的连接成功返回或者监听到连接接入请求出现。如果一个发出的连接尝试
    失败由于连接重置或者主机不可达这样网络错误,主机将按照应用程序定义的最大超时
    周期延时以后重新尝试连接。
    5. 当一个TCP连接产生,双发主机相互认证来确认它们连接到目的主机。如果认证失败,客户端将会关闭连接来继续等待新的连接产生。直到客户端首次成功地使用通过认证的流程产生的TCP流。
    不像UDP,每个客户端只需要一个SOCKET同时地用来与服务器S及其它对等的客户端通讯,用
    TCP地客户端应用程序必须管理几个SOCKET在客户节点的同一个本地TCP端口上,如图7所示:
    每个客户端需要一个流套接字代表它连接到服务器S;一个监听套接字在本地端口上接受其它
    客户端请求接入的连接;至少两个另外的流套接字用来初始化两个出去的连接分别到其它客
    户端的开放端点(public endpoint)和私有端点(private endpoint)。 考虑最常见的情景,A和B在不同的NAT设备之后,如图5所示那样,假定在图中显示的端
    口号现在是TCP而不是UDP端口号。发起到A和B私有端点的连接不是失败就是连接到错误的主
    机。与UDP类似,TCP应用程序鉴别它的P2P会话是非常重要的,因为可能发生这样的错误:本
    地私有网络内碰巧有一台主机和远程私有网络内的主机拥有相同的IP地址和开放端口而导致
    连接到错误主机。 双方客户端的出去连接尝试在对方的公共(开放)端,无论如何,导致各自的NAT设备打开
    一个新的洞为了A和B之间直接的TCP通讯。如果双方的NAT设备是规范的,这样一个新的TCP
    流在客户端之间自动形成。假定,如果A的第一个发向B的SYN包先于B的第一个发向A的SYN包
    到达B的NAT设备,B的NAT设备可以认为A的SYN包是一个不友好的请求连接进入而丢弃它。B
    的第一个发向A的SYN包因此得以通过A的NAT设备,因为A的NAT设备认为这是出去连接到B的会
    话A的第一个SYN包已经完成初始化的一部分。
     4.3 应用程序观察的行为(Behavior Observed by the Application)
    在TCP打洞期间,客户端观察到发生在它们的SOCKET上的依赖于时序和相关的TCP执行。假设A
    首先发起一个出去的SYN包到达B的开放(公共)端点被B的NAT设备丢弃,但是接下来B的第一个
    SYN包到A的开放(公共)端点将会通过A的NAT设备在A重新发送一个SYN包之前。取决于相关的
    操作系统,可能产生下面这样两种情况中的一种:
    •A的TCP执行注意到会话端进来的SYN包匹配A正在尝试初始化一个出去的会话。A的TCP栈因
    此关联这个新的会话到A本地应用程序用来连接到B的开放(公共)端的SOCKET上。应用程序异步
    connect()调用成功,在A本地应用程序的监听SOCKET上不会有动作发生。因为接受到的SYN包不包含一个ACK来应答之前A发出的SYN包,A的TCP应用程序将会回复B的开放(公共)端用一个SYN + ACK
    的包,其中SYN部分只是原来A发送的SYN包的再次发送而已,用的是相同的序列号。一旦B的TCP应用程序接受到A的SYN + ACK包以后,它将会响应一个ACK包发送给A(一个人意见:其实就是谁发起的TCP连接以及完成一个TCP连接过程),这样一个TCP会话在两端就进入了已连接状态。
    •可以选择地,A的TCP执行可能注意到A有一个活动监听SOCKET在指定端口等待一个连接请求。
    因此B的SYN包看上去像一个连接请求,A的TCP创建一个新的流套接字关联一个新的TCP会话,持有
    这个新的SOCKET对像通过监听listen()之后的accept()调用。和上面一样A的TCP应答B一个SYN +
     ACK包,TCP连接建立处理和普通的C/S连接一样。由于A最早试图连接到B的用的共用的源和目的
    端点,现在被另外一个SOCKET使用中,也就是说,一个通过accept()调用返回给应用程序,A的异
    步连接尝试必然失败在这些点,最典型的是给出一个“地址使用中”错误。不过应用程序已经工作用P2P的流套接字来和B通讯,因此它忽略这个失败(错误)。
    第一种情况常见于以BSD为基础的操作系统,第二种行为在Windows/Linux系统下更为常见。4.4 同时发生的TCP Open
    假设定时的连接尝试在打洞流程中出去,而导致初始化一个出去的SYN包从它们的客户端穿过它
    们各自的本地NAT设备,打开一个新的出去的TCP会话在每个NAT设备上,在到达远程的NAT设备之
    前。在这种幸运的场合下,NAT设备不拒绝任何一方初始化的SYN包,SYN包分别到达对方NAT设备。
    在这种场合下,客户端观察到的是TCP同时打开这样的事件:每一端的TCP接受一个原始的SYN包
    同时等待一个SYN + ACK的包。每个端响应一个SYN + ACK包,包中的SYN部分本质上是重发客户端
    自己以前的SYN包,包的ACK部分确认从其它客户端接受到的SYN包。
    各自应用程序所观察到的还是取决与相关的TCP完成行为,如前面一节所描述。如果两个客户
    端完成上面的第二种行为,应用程序所有的异步connect()调用都将会以失败而告终,但是在每个
    客户端仍然接受一个新的,工作的TCP流SOCKET连接通过accept()调用 - 好像这个TCP流魔术般
    的自身创建一样,只是在各自客户端被动的接受。只要应用程序不关心它最终接受它的P2P的TCP
    流SOCKET是否是通过connect()或者是accept()产生,导致正常的流程TCP过程如RFC793中描述的
    标准TCP状态机。
    选择第三节讨论的两种中任意一种网络组织情景在TCP中工作就和在UDP中一样。例如,在多级
    NAT设备情况下TCP打洞就像如图6所示一样,只要NAT设备本身是规则的(友好的)。4.5  顺序的打洞(Sequential Hole Punching)
    上面由NATTrav【4】 库完成的不同的TCP打洞过程,客户端尝试相互连接按照一定的时序而非并
    发进行。例如,1):客户端A首先通过服务器S通知B希望建立通讯,没有同时地在本地端口上监
    听;2):B产生一个连接尝试到A,因此在B的NAT设备上打洞但是之后失败因为超时或者TCP重置
    错误来自A的NAT设备或者是A本身;3):B关闭它到S的连接,并开始在它的本地端口开始监听连
    接;4):S接着关闭它和A的连接,同时提示A尝试直接连接到B。
    时序处理特别的有用在Windows XP SP2之前的主机系统上,它不支持同步的TCP打开,或者在
    SOCKET API不支持SO_REUSEADDR(地址重用)功能。顺序的打洞处理更依赖于时间选择,无论怎
    样,在通常情况下这样的处理可能比较慢一点,但是一般很健壮在通常情况下。例如上面的第二
    步(2):)中,B必须保证它的尝试连接connect()有足够的时间确保至少有一个SYN包经过所有
    在它这边网络上的NAT设备。一个SYN包丢失的很小延时,却给整个打洞过程带来更多的延时。时
    序的打洞过程也非常消耗两个客户端到服务器S的连接带宽,要求客户端打开刷新到服务器S的连
    接为每个新伪造的P2P连接。相比之下,并发的打洞过程常这样完成:只要双方客户端都产生一个
    出去的连接尝试,每个客户端各自保持和重用到服务器S的连接。==============================================================================
    我以前的文章,自己看看吧
      

  4.   

    用Java实现TCP打洞,会受制于初始链接阶段,Java API不支持发送独立的SYN
    也就是不能自己构造数据包
      

  5.   

    1socket_1 = new Socket();
    socket_1.setReuseAddress(true);
    socket_1.bind(socketAddress);
    try {
    socket_1.connect(new InetSocketAddress(inet, port), 1000);
    } catch (Exception e) {
    // TODO Auto-generated catch block
    System.out.println("Socket_1's Message is " + e.getMessage());
    socket_1.close();
    }
    socket_2 = new Socket();
    socket_2.setReuseAddress(true);
    socket_2.bind(socketAddress);
    if (!socket_2.isConnected()) {
    System.out.println("Socket is not connected");
    }这样写有什么问题吗????
      

  6.   

    每次进行两个客户端的第一次握手的时候,Socket都是连接超时,连不上
      

  7.   

    多方求助,终于得到一点进展,用TCP是不能进行穿透的,可以采用JXTA来实现这个功能。