我不知道如何在标题中清楚地描述我的问题,对我来说这有点复杂。我正在做的是尝试实现TCP点对点演示,其中本地端口必须同时用于侦听和启动套接字。
我将详细说明。 我将给出一个java实现,它将在单个本地端口上侦听并启动连接。代码将解释我的想法。
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 TcpPeer {
// 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 TcpPeer(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();
peerSock.setReuseAddress(true);
peerSock.bind(new InetSocketAddress("localhost", localport));
System.out.println("[Server]The server is listening on " + localport + ".");
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) {
e.printStackTrace();
System.out.println("[Client]fail to connect " + serverHost + ":"
+ serverPort + ", try again.");
Thread.currentThread().sleep(i * 2 * 1000);
if (i == retry - 1) return;
}
}
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);
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 {
if (args.length != 3) {
System.out.println("[Usage] java " + TcpPeer.class.getCanonicalName()
+ " [serverHost] [serverPort] [localPort]");
System.exit(0);
}
new TcpPeer(args[0], Integer.parseInt(args[1]), Integer.parseInt(args[2]));
}
}
现在我们启动2个jvm进程:
ps#1> java TcpPeer localhost 2000 4000
ps#2> java TcpPeer localhost 4000 2000
最后当2个进程稳定时,它们将给出以下输出:
PS#1&GT;
[Server]The server is listening on 2000.
[Client]socket.isBound():false
[Client]connect to localhost:4000 successfully.
[Client][RESP-1]hello world!
PS#2 - ;
[Server]The server is listening on 4000.
[Server]New connection accepted/127.0.0.1:2000
[Server][REQ]hello world!
[Client]socket.isBound():false
java.net.BindException: Address already in use: connect
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333)
at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366)
at java.net.Socket.connect(Socket.java:525)
at java.net.Socket.connect(Socket.java:475)
at org.clinic4j.net.TcpPeer$2.run(TcpPeer.java:92)
at java.lang.Thread.run(Thread.java:619)
[Client]fail to connect localhost:2000, try again.
java.net.SocketException: Socket operation on nonsocket: connect
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333)
at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366)
at java.net.Socket.connect(Socket.java:525)
at java.net.Socket.connect(Socket.java:475)
at org.clinic4j.net.TcpPeer$2.run(TcpPeer.java:92)
at java.lang.Thread.run(Thread.java:619)
[Client]fail to connect localhost:2000, try again.
java.net.SocketException: Socket operation on nonsocket: connect
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333)
at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366)
at java.net.Socket.connect(Socket.java:525)
at java.net.Socket.connect(Socket.java:475)
at org.clinic4j.net.TcpPeer$2.run(TcpPeer.java:92)
at java.lang.Thread.run(Thread.java:619)
[Client]fail to connect localhost:2000, try again.
java.net.SocketException: Socket operation on nonsocket: connect
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333)
at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366)
at java.net.Socket.connect(Socket.java:525)
at java.net.Socket.connect(Socket.java:475)
at org.clinic4j.net.TcpPeer$2.run(TcpPeer.java:92)
at java.lang.Thread.run(Thread.java:619)
[Client]fail to connect localhost:2000, try again.
从输出中,我们可以看出交互流程如下:
为什么ps#2在步骤#4无法连接到ps#1?我还监控操作系统的净状态。
以下是步骤#3之后的净状态。
还有步骤#4之后的净状态。
我在重新连接失败时打印出原始异常消息,但我对这些异常并不太了解。
答案 0 :(得分:3)
您不应绑定客户端套接字,也不应在客户端套接字上设置SO_REUSEADDR
。
如果您没有绑定客户端套接字,系统将自动为您分配端口号。
此外,SO_REUSEADDR
并不意味着在套接字仍处于打开状态时可以重用地址(ip /端口号)。这样当套接字关闭并处于TIME_WAIT
状态时,您可以再次绑定它。
答案 1 :(得分:0)
您的客户端线程在第一次消息交换后退出,它缺少while(true)
部分。
答案 2 :(得分:0)
如果在没有成功连接的情况下完成连接重试,则需要退出,而不是进入I / O代码。这就是你得到实际例外的原因。
您还需要打印为什么连接失败。目前这是最重要的信息,而你正在压制它。打印异常消息。这是一般原则:不要自己编造消息,使用你给出的消息。它几乎肯定更具体。
绑定ServerSocket时,使用null
代替新的InetAddress(“localhost”)。
我不相信你可以在套接字上重试连接。如果失败,请尝试创建新的Socket。