Java TCP SO_RESUEADDR, не удается подключиться - PullRequest
1 голос
/ 20 марта 2012

Я не знаю, как четко описать мой вопрос в заголовке, так как для меня это немного сложно.Я пытаюсь реализовать одноранговую демонстрацию 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>

[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 -> localhost: 4000.
  4. ps # 2 закройте соединение, которое установилось на шаге # 3.
  5. ps # 2 попытаться подключиться к ps # 1 на 2000, не удалось!

Почему ps # 2 не может подключиться к ps # 1 на шаге # 4?Я также отслеживаю состояние сети ОС.

Ниже приведено состояние сети сразу после шага № 3.enter image description here

А также статус сети сразу после шага № 4.enter image description here

  • 192.168.2.107 is localhost

Не могли бы вы дать мне комментарий к моему делу?спасибо!

Я распечатал исходное сообщение об исключении, когда не удалось восстановить соединение, но у меня нет особого представления об этом исключении.

Ответы [ 3 ]

3 голосов
/ 20 марта 2012

Не следует связывать клиентский сокет, а также не устанавливать SO_REUSEADDR на клиентском сокете.

Если вы не привязываете сокет клиента, система автоматически назначит вам номер порта.

Кроме того, SO_REUSEADDR не означает, что адрес (номера ip / port) можно использовать повторно, пока сокет еще открыт. Это так, что когда сокет был закрыт и находится в состоянии TIME_WAIT, вы можете снова привязаться к нему.

0 голосов
/ 21 марта 2012
  1. Если вы пытаетесь выполнить повторные попытки подключения без успешного подключения, вам нужно выйти, а не попасть в код ввода-вывода.Вот почему вы получаете фактическое исключение.

  2. Вам также необходимо распечатать почему сбой подключения.На данный момент это самая важная информация, и вы ее подавляете.Распечатать сообщение об исключении.Это общий принцип: не составляйте свое собственное сообщение, используйте тот, который вам дали.Это почти наверняка более конкретно.

  3. Вместо нового InetAddress ("localhost") используйте null при привязке ServerSocket.

  4. IЯ не уверен, что вы можете повторить попытку подключения к сокету.Если это не удается, попробуйте создать новый сокет.

0 голосов
/ 20 марта 2012

Ваш клиентский поток завершается сразу после первого обмена сообщениями, ему не хватает while(true) части.

...