java nio 套接字未检测到机器何时进入睡眠或休眠状态
java nio socket not detecting when machine goes to sleep or hibernates
这是我的套接字服务器和客户端组件的简化版本。
主要目标是让客户端检测服务器何时宕机,以及让服务器检测客户端何时宕机。
当客户端或服务器被终止(获取 IOException "An existing connection was forcibly closed by the remote host")时,这(在 Windows 上)完美运行。
我还想检测客户端或服务器 运行 所在的机器何时进入睡眠(或休眠)状态,最终使用相同的机制。
相反,当前的行为是未检测到 "the other machine going to sleep" 事件,并且当机器被唤醒时,连接再次处于活动状态。此时 "the process going down" 事件像以前一样被检测到。
在客户端机器进入睡眠状态的情况下,罪魁祸首似乎是 "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;
}
}
此行为因系统而异,甚至其配置也不同。旧版本的 Windows 用于在计算机进入休眠状态甚至暂时失去网络连接时关闭所有挂起的连接。这通常不是用户想要的,因为如果只是暂时中断,用户必须再次重新打开所有连接。所以它在前一段时间发生了变化,现在(默认情况下,它是 configurable)它的行为与其他系统(Linux、MacOs、...)相似。所以连接一直保持到超时。
为了避免长期存在的死连接,最好的选择是在套接字上设置 SO_KEEPALIVE 选项。双方及其操作系统随后将通过套接字发送虚拟数据包(不是有效负载数据,因此对应用程序层不可见),除非在合理的时间内收到响应,否则 OS 将终止连接。在 Java 中,您可以像下面这样实现:
channel.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
谢谢 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/
保持活动时间REG_DWORD1000
它的默认值是2小时,我把它减少到1秒。
我将 KeepAliveInterval 保留为默认值 1 秒,将 TcpMaxDataRetransmissions 保留为默认值 5。
与任何 Microsoft 软件一样,我必须重新启动两台机器。
注意我的一台机器是Win10,一台是Win7
通过这些更改,无论哪台机器进入休眠状态,另一台机器上的组件都会检测到断开连接事件(5 秒内)。机器一唤醒,其上的组件就会检测到连接不再存在,然后重新连接。正是我想要完成的。
再次感谢,
弗拉基米尔
这是我的套接字服务器和客户端组件的简化版本。
主要目标是让客户端检测服务器何时宕机,以及让服务器检测客户端何时宕机。
当客户端或服务器被终止(获取 IOException "An existing connection was forcibly closed by the remote host")时,这(在 Windows 上)完美运行。
我还想检测客户端或服务器 运行 所在的机器何时进入睡眠(或休眠)状态,最终使用相同的机制。
相反,当前的行为是未检测到 "the other machine going to sleep" 事件,并且当机器被唤醒时,连接再次处于活动状态。此时 "the process going down" 事件像以前一样被检测到。
在客户端机器进入睡眠状态的情况下,罪魁祸首似乎是 "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;
}
}
此行为因系统而异,甚至其配置也不同。旧版本的 Windows 用于在计算机进入休眠状态甚至暂时失去网络连接时关闭所有挂起的连接。这通常不是用户想要的,因为如果只是暂时中断,用户必须再次重新打开所有连接。所以它在前一段时间发生了变化,现在(默认情况下,它是 configurable)它的行为与其他系统(Linux、MacOs、...)相似。所以连接一直保持到超时。
为了避免长期存在的死连接,最好的选择是在套接字上设置 SO_KEEPALIVE 选项。双方及其操作系统随后将通过套接字发送虚拟数据包(不是有效负载数据,因此对应用程序层不可见),除非在合理的时间内收到响应,否则 OS 将终止连接。在 Java 中,您可以像下面这样实现:
channel.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
谢谢 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/
保持活动时间REG_DWORD1000
它的默认值是2小时,我把它减少到1秒。
我将 KeepAliveInterval 保留为默认值 1 秒,将 TcpMaxDataRetransmissions 保留为默认值 5。
与任何 Microsoft 软件一样,我必须重新启动两台机器。
注意我的一台机器是Win10,一台是Win7
通过这些更改,无论哪台机器进入休眠状态,另一台机器上的组件都会检测到断开连接事件(5 秒内)。机器一唤醒,其上的组件就会检测到连接不再存在,然后重新连接。正是我想要完成的。
再次感谢, 弗拉基米尔