我在本地计算机上使用以下代码:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Just for testing socket SO_RESUEADDR. If set SO_RESUEADDR to true, we can use
* a single local port to listen for incoming TCP connections, and to initiate
* multiple outgoing TCP connections concurrently. By this way we can implement
* TCP hole punching(establish P2P connection traversal through NAT over TCP).
*/
public class TcpPeer2 {
// TCP port is a different source from UDP port, it means you can listen on
// same port for both TCP and UDP at the same time.
private int localport = 7890;
private ServerSocket peerSock;
private Socket serverSocket;
public TcpPeer2(final String serverHost, final int serverPort, final int localPort)
throws Exception {
this.localport = localPort;
Thread server = new Thread(new Runnable() {
@Override
public void run() {
try {
peerSock = new ServerSocket();
System.out.println(peerSock.isBound());
peerSock.setReuseAddress(true);
System.out.println(peerSock.isBound());
peerSock.bind(new InetSocketAddress("localhost", localport));
System.out.println("[Server]The server is listening on " + localport + ".");
System.out.println(peerSock.isBound());
System.out.println(peerSock.isClosed());
System.out.println(peerSock.getLocalSocketAddress().toString());
//peerSock.
while (true) {
try {
serverSocket = peerSock.accept();
// just means finishing handshaking, and connection
// established.
System.out.println("[Server]New connection accepted"
+ serverSocket.getInetAddress() + ":" + serverSocket.getPort());
BufferedReader br = getReader(serverSocket);
PrintWriter pw = getWriter(serverSocket);
String req = br.readLine();
System.out.println("[Server][REQ]" + req);
pw.println(req);
// pw.close();
// br.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
// try {
// if (serverSocket != null)
// serverSocket.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
// server.setDaemon(true);
server.start();
Thread.currentThread();
// sleep several seconds before launch of client
Thread.sleep(5 * 1000);
final int retry = 5;
Thread client = new Thread(new Runnable() {
@Override
public void run() {
Socket socket = new Socket();
try {
socket.setReuseAddress(true);
System.out.println("[Client]socket.isBound():" + socket.isBound());
socket.bind(new InetSocketAddress("localhost", localport));
for (int i = 1; i < retry; i++) {
try {
socket.connect(new InetSocketAddress(serverHost, serverPort));
System.out.println("[Client]connect to " + serverHost + ":"
+ serverPort + " successfully.");
break;
} catch (Exception e) {
System.out.println("[Client]fail to connect " + serverHost + ":"
+ serverPort + ", try again.");
Thread.currentThread().sleep(i * 2 * 1000);
/**
* PeerA and PeerB
* <p>
* Alternatively, A's TCP implementation might
* instead notice that A has an active listen socket
* on that port waiting for incoming connection
* attempts. Since B's SYN looks like an incoming
* connection attempt, A's TCP creates a new stream
* socket with which to associate the new TCP
* session, and hands this new socket to the
* application via the application's next accept()
* call on its listen socket. A's TCP then responds
* to B with a SYN-ACK as above, and TCP connection
* setup proceeds as usual for client/server-style
* connections.
* <p>
* Since A's prior outbound connect() attempt to B
* used a combination of source and destination
* endpoints that is now in use by another socket,
* namely the one just returned to the application
* via accept(), A's asynchronous connect() attempt
* must fail at some point, typically with an
* “address in use” error. The application
* nevertheless has the working peer-to- peer stream
* socket it needs to communicate with B, so it
* ignores this failure.
*/
if (i == retry - 1) {
System.out
.println("[Client]Use the socket returned by ServerSocket.");
socket = serverSocket;
}
}
}
PrintWriter pw = getWriter(socket);
String msg = "hello world!";
pw.println(msg);
/**
* Got response from the server socket.
*/
BufferedReader br = getReader(socket);
String resp = br.readLine();
System.out.println("[Client][RESP-1]" + resp);
/**
* The client thread of other process will send request. If
* fail to establish connection with other peer, the Socket
* return by the ServerSocket will be used.
*/
resp = br.readLine();
System.out.println("[Client][RESP-2]" + resp);
// pw.close();
// br.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
// try {
// socket.close();
// } catch (Exception e) {
// e.printStackTrace();
// }
}
}
});
client.start();
}
private PrintWriter getWriter(Socket socket) throws IOException {
OutputStream socketOut = socket.getOutputStream();
return new PrintWriter(socketOut, true);
}
private BufferedReader getReader(Socket socket) throws IOException {
InputStream socketIn = socket.getInputStream();
return new BufferedReader(new InputStreamReader(socketIn));
}
public static void main(String[] args) throws Exception {
new TcpPeer2("127.0.0.1", 8000, 7000);
}
}
但是它给了我一个JVM-Bind Exception。
我已从以下链接下载此代码: http://ramonli.blogspot.in/2012/03/tcp-hole-punching-how-to-establish-tcp.html
理论上它应该工作正常而不是抛出任何例外。 因此,它应该是Java中TCP打孔的模板。
我做错了什么?
答案 0 :(得分:1)
即使使用SO_REUSEADDR,也无法在服务器仍在侦听时将客户端绑定到同一本地主机:端口。
请参阅man socket:
<强> SO_REUSEADDR 强>
表示验证提供的地址时使用的规则 在bind(2)调用中应该允许重用本地地址。对于 AF_INET套接字这意味着套接字可以绑定,除外 有一个活动的侦听套接字绑定到该地址。 当侦听套接字用IN绑定到INADDR_ANY时 特定端口然后无法绑定到此端口 任何本地地址。 Argument是一个整数布尔标志。
您可以将客户端绑定到socket.bind(new InetSocketAddress("localhost", 0));
的随机免费本地端口,但这样做会失败。