1

这是我的套接字服务器和客户端组件的简化版本。

主要目标是让客户端检测服务器何时宕机,让服务器检测客户端何时宕机。

当客户端或服务器被终止时(在 Windows 上),这完美地工作(得到 IOException“现有连接被远程主机强行关闭”)。

我还想检测运行客户端或服务器的机器何时进入睡眠(或休眠),最终使用相同的机制。

相反,当前行为是未检测到“另一台机器进入睡眠”事件,并且当机器被唤醒时,连接再次处于活动状态。此时“进程停止”事件像以前一样被检测到。

在客户端机器进入睡眠状态的情况下,罪魁祸首似乎是“selector.selectedKeys()”没有返回连接到睡眠机器的密钥。

Windows 上的套接字实现中是否缺少此功能?

有人对如何解决/解决此问题有任何建议吗?

package test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class TestServer {
    private ByteBuffer _inBuf;
    private int _serverPort;

    public static void main(String[] args) {
        TestServer server = new TestServer(7071);
        server.start();
    }

    public TestServer(int serverPort) {
        _serverPort = serverPort;
    }

    public void start() {
        _inBuf = ByteBuffer.allocate(512);
        System.out.println("Server starting on port "+_serverPort);
        new Thread() {
            public void run() {
                try {
                    Selector selector = Selector.open();
                    ServerSocketChannel server = ServerSocketChannel.open();
                    server.socket().bind(new InetSocketAddress(_serverPort));
                    server.configureBlocking(false);
                    SelectionKey serverKey = server.register(selector, SelectionKey.OP_ACCEPT);

                    while (true) {
                        selector.select();
                        Set<SelectionKey> keys = selector.selectedKeys();
                        for (Iterator<SelectionKey> i = keys.iterator(); i.hasNext(); ) {
                            SelectionKey key = i.next();
                            i.remove();
                            if (key == serverKey) {
                                if (key.isAcceptable()) {
                                    System.out.println("acceptable server key "+Integer.toHexString(key.hashCode()));
                                    try {
                                        SocketChannel client = server.accept();
                                        client.configureBlocking(false);
                                        SelectionKey clientKey = client.register(selector, SelectionKey.OP_READ);
                                        System.out.println("registered client key "+Integer.toHexString(clientKey.hashCode()));
                                    } catch (IOException x) {
                                        x.printStackTrace();
                                    }
                                }
                            } else {
                                if (!key.isReadable()) continue;
                                SocketChannel client = (SocketChannel) key.channel();
                                System.out.println("reading "+Integer.toHexString(key.hashCode()));
                                try {
                                    int no = client.read(_inBuf);
                                    if (no<0) throw new IOException("reached end-of-stream"+Integer.toHexString(key.hashCode()));
                                    if (no>0) System.out.println("read "+no+" bytes from "+Integer.toHexString(key.hashCode()));
                                } catch (IOException x) {
                                    System.out.println(x.getMessage()+" "+Integer.toHexString(key.hashCode()));
                                    key.cancel();
                                    try {
                                        client.close();
                                    } catch (IOException ignore) {
                                        ignore.printStackTrace();
                                    }
                                    continue;
                                }
                                _inBuf.flip();
                                _inBuf.compact();
                            }
                        }
                    }
                } catch (Exception x) {
                    x.printStackTrace();
                }
            }
        }.start();
    }
}

package test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class TestClient {
    private static final int _connectionTimeoutNanos = 10 * 1000000;
    private String _serverHost;
    private int _serverPort;
    private SocketChannel _channel = null;
    private ByteBuffer _inBuf;

    public static void main(String[] args) {
        TestClient client = new TestClient("192.168.1.180", 7071);
        client.start();
    }

    public TestClient(String serverHost, int serverPort) {
        _serverHost = serverHost;
        _serverPort = serverPort;
    }

    public void start() {
        _inBuf = ByteBuffer.allocate(512);
        ClientThread thread = new ClientThread();
        thread.start();
    }

    private class ClientThread extends Thread {
        @Override
        public void run() {
            System.out.println("Client connecting to "+_serverHost+":"+_serverPort);
            SocketAddress socketAddress = new InetSocketAddress(_serverHost, _serverPort);
            while (true) {
                boolean connected = false;
                try {
                    _channel = SocketChannel.open();
                    _channel.configureBlocking(false);
                    try {
                        connected = _channel.connect(socketAddress);
                    } catch (IOException x) {
                        try {
                            _channel.close();
                        } catch (Throwable suppressed) {
                            x.addSuppressed(suppressed);
                        }
                        throw x;
                    }
                    long nanoStart = System.nanoTime();
                    while (!connected) {
                        connected = _channel.finishConnect();
                        if (!connected && (nanoStart+_connectionTimeoutNanos < System.nanoTime())) {
                            throw new IOException("Non blocking connect failed");
                        }
                    }

                    _channel.socket().setSoLinger(true, 10);
                    System.out.println("Connected to "+_serverHost+":"+_serverPort);

                    while (true) {
                        if (!readFromChannel()) break;
                    }

                    System.out.println("Disconnected from "+_serverHost+":"+_serverPort);
                } catch (IOException x) {
                    if (connected) {
                        System.out.println("Disconnected from "+_serverHost+":"+_serverPort+" "+x.getMessage());
                    }
                }
                try {Thread.sleep(1000);} catch (InterruptedException x) {}
            }
        }
    }

    public boolean readFromChannel() throws IOException {
        int no = _channel.read(_inBuf);
        if (no<0) {
            return false;
        }
        if (no>0) System.out.println("read "+no+" bytes from "+_serverHost+":"+_serverPort);
        _inBuf.flip();
        _inBuf.compact();
        return true;
    }
}
4

2 回答 2

1

这种行为因系统而异,甚至因配置而异。当计算机进入睡眠状态甚至暂时失去网络连接时,旧版本的 Windows 会关闭所有挂起的连接。这通常不是用户想要的,因为在临时中断的情况下,用户必须再次重新打开所有连接。所以它在前一段时间发生了变化,现在(默认情况下,它是可配置的)它的行为类似于其他系统(Linux,MacOs,...)。所以连接会一直保持到超时。

为了避免长期死连接,最好的选择是在套接字上设置SO_KEEPALIVE选项。然后双方及其操作系统将通过套接字发送虚拟数据包(不是有效负载数据,因此对应用层不可见),除非在合理的时间内收到响应,否则操作系统将终止连接。在 Java 中,您可以像下面这样实现:

channel.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
于 2016-03-16T08:27:13.630 回答
0

谢谢Zbynek,解决了这个问题:-)。

这是我必须做的:

1) 在 TestServer 代码中,在第 50 行的 client.configureBlocking(false) 之后,我添加了:

client.socket().setKeepAlive(true);

这相当于你的

client.setOption(StandardSocketOptions.SO_KEEPALIVE, true);

2) 在 TestClient 代码中,第 60 行之后:

_channel.socket().setSoLinger(true, 10);

我补充说:

_channel.socket().setKeepAlive(true);

3)使用regedit,在两台Windows机器上,我在下面添加了以下值

HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Tcpip/Parameters/

KeepAliveTime REG_DWORD 1000

它的默认值为 2 小时,我将其减少到 1 秒。

我将 KeepAliveInterval 保留为其默认值 1 秒,并将 TcpMaxDataRetransmissions 保留为其默认值 5。

与任何 Microsoft 软件一样,我必须重新启动两台机器。

注意我的一台机器是Win10,另一台是Win7。

通过这些更改,无论哪台机器进入睡眠状态,另一台机器上的组件都会检测到断开连接事件(在 5 秒内)。机器一醒来,其上的组件就会检测到连接不再存在,然后重新连接。正是我想要完成的。

再次感谢,弗拉基米尔

于 2016-03-17T13:51:25.743 回答