Java TCP SO_RESUEADDR,无法连接

时间:2012-03-20 06:35:18

标签: java tcp p2p

我不知道如何在标题中清楚地描述我的问题,对我来说这有点复杂。我正在做的是尝试实现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.

从输出中,我们可以看出交互流程如下:

  1. ps#1听2000年。
  2. ps#2听4000。
  3. ps#1连接到ps#2,来自localhost:2000 - &gt;本地主机:4000
  4. ps#2关闭在步骤#3建立的连接。
  5. ps#2尝试在2000年连接ps#1,失败!
  6. 为什么ps#2在步骤#4无法连接到ps#1?我还监控操作系统的净状态。

    以下是步骤#3之后的净状态。 enter image description here

    还有步骤#4之后的净状态。 enter image description here

    • 192.168.2.107是localhost
    你能否就我的案子发表评论?谢谢!

      

    我在重新连接失败时打印出原始异常消息,但我对这些异常并不太了解。

3 个答案:

答案 0 :(得分:3)

您不应绑定客户端套接字,也不应在客户端套接字上设置SO_REUSEADDR

如果您没有绑定客户端套接字,系统将自动为您分配端口号。

此外,SO_REUSEADDR并不意味着在套接字仍处于打开状态时可以重用地址(ip /端口号)。这样当套接字关闭并处于TIME_WAIT状态时,您可以再次绑定它。

答案 1 :(得分:0)

您的客户端线程在第一次消息交换后退出,它缺少while(true)部分。

答案 2 :(得分:0)

  1. 如果在没有成功连接的情况下完成连接重试,则需要退出,而不是进入I / O代码。这就是你得到实际例外的原因。

  2. 您还需要打印为什么连接失败。目前这是最重要的信息,而你正在压制它。打印异常消息。这是一般原则:不要自己编造消息,使用你给出的消息。它几乎肯定更具体。

  3. 绑定ServerSocket时,使用null代替新的InetAddress(“localhost”)。

  4. 我不相信你可以在套接字上重试连接。如果失败,请尝试创建新的Socket。