Java TCP 心跳无法正常工作
Java TCP Heartbeat not working properly
项目: 我一直在为 android 开发无线鼠标应用程序,它使用 TCP 作为传输鼠标移动的网络协议。
Objective:
我决定实施心跳 TimerTask 是查看服务器是否已关闭的最佳方式。每隔 5 秒未进行鼠标移动(鼠标移动将计时器重置回 5),数组中 2 字节的心跳将发送到服务器。因此,如果服务器关闭,发送心跳后应该引发 IOException (Broken Pipe)
。
我的问题:
除了在抛出异常之前循环 3 次之外,心跳运行良好。目前,我将它设置为 5 秒后发送心跳。在服务器关闭后,它会在引发预期异常之前发送三个心跳(15 秒而不是 5 秒)。
我可以将心跳间隔设置为 2 秒,这又需要 6 秒来引发异常,但我想知道为什么它第一次不工作。
心跳码:
Timer task = new Timer();
task.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if (heartbeat == 1) {
byte buf[] = {-96, -96};
try {
bos.write(buf); // Buffered output stream
bos.flush(); // Exception gets thrown here
heartbeat = 5;
System.out.println("Testing 3");
} catch (IOException e) {
System.out.println("Testing 2");
launchHomescreen();
this.cancel();
e.printStackTrace();
}
} else {
System.out.println("Testing 1" + heartbeat);
heartbeat--;
}
}
}, 1000, 1000);
调试语句日志:
04-27 00:33:02.339 25410-26259/com.tutorials.jurko.androidmouse I/System.out﹕ Message sending: -99 -99 <-- This is the mouse click that should stop the server
04-27 00:33:03.190 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 15
04-27 00:33:04.201 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 14
04-27 00:33:05.192 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 13
04-27 00:33:06.193 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 12
04-27 00:33:07.203 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 3 <- Should throw the exception here
04-27 00:33:08.194 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 15
04-27 00:33:09.195 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 14
04-27 00:33:10.196 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 13
04-27 00:33:11.187 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 12
04-27 00:33:12.188 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 3
04-27 00:33:13.189 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 15
04-27 00:33:14.190 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 14
04-27 00:33:15.191 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 13
04-27 00:33:16.202 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 12
04-27 00:33:17.193 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 2 <- but doesn't do it till the third heartbeat
04-27 00:33:17.233 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ java.net.SocketException: sendto failed: EPIPE (Broken pipe)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at libcore.io.IoBridge.maybeThrowAfterSendto(IoBridge.java:499)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at libcore.io.IoBridge.sendto(IoBridge.java:468)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at java.net.PlainSocketImpl.write(PlainSocketImpl.java:508)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at java.net.PlainSocketImpl.access0(PlainSocketImpl.java:46)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at java.net.PlainSocketImpl$PlainSocketOutputStream.write(PlainSocketImpl.java:270)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at java.io.BufferedOutputStream.flushInternal(BufferedOutputStream.java:185)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:85)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at com.tutorials.jurko.androidmouse.MainActivity$connectTask.run(MainActivity.java:496)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at java.util.Timer$TimerImpl.run(Timer.java:284)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ Caused by: libcore.io.ErrnoException: sendto failed: EPIPE (Broken pipe)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at libcore.io.Posix.sendtoBytes(Native Method)
04-27 00:33:17.253 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at libcore.io.Posix.sendto(Posix.java:156)
04-27 00:33:17.253 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at libcore.io.BlockGuardOs.sendto(BlockGuardOs.java:177)
04-27 00:33:17.253 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at libcore.io.IoBridge.sendto(IoBridge.java:466)
04-27 00:33:17.253 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ ... 7 more
04-27 00:33:17.453 25410-25410/com.tutorials.jurko.androidmouse W/ApplicationPackageManager﹕ getCSCPackageItemText()
04-27 00:33:33.139 25410-25410/com.tutorials.jurko.androidmouse W/IInputConnectionWrapper﹕ getCursorCapsMode on inactive InputConnection
'connection reset'/'broken pipe' 不会立即发生,因为 TCP 缓冲和异步性。它发生在写入事件因未收到 ACK 而超时之后,这可能需要一分钟的时间。
它不一定与写入尝试次数有关,因此更改间隔不一定有帮助。
如果您想确定连接状态,请尝试在套接字上执行 recv
而不是发送心跳消息?如果服务器关闭并且连接消失,recv
将 return 归零。尽管在服务器由于 n/w 问题而无法访问的情况下,这仍然会失败。
如果客户端和服务器都发送数据包并相互确认,则应用程序保持活动状态效果最佳。依靠破管是不能完全满足要求的。
另一种选择是在 TCP 上启用 keep-alive
。
谢谢你们的回复,我解决问题的方法是在写入之前向套接字添加超时,然后再添加读取。如果读到 returns -1,那么我就知道服务器停止了。这个解决方案似乎也按照我想要的方式工作。
新代码:
Timer task = new Timer();
task.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if (heartbeat == 0) {
byte buf[] = {-96, -96};
byte send[] = new byte[1024]; // Added this
try {
socket.setSoTimeout(1000); // Added a timeout every read call
bos.write(buf); // Buffered output stream
bos.flush();
if(socket.getInputStream().read(send) == -1) { // End of stream reached
System.out.println("Testing 2");
launchHomescreen();
this.cancel();
}
heartbeat = 2;
System.out.println("Testing 3");
} catch (IOException e) {
System.out.println("Testing 2");
launchHomescreen();
this.cancel();
e.printStackTrace();
}
} else {
System.out.println("Testing 1" + heartbeat);
heartbeat--;
}
}
}, 1000, 1000);
项目: 我一直在为 android 开发无线鼠标应用程序,它使用 TCP 作为传输鼠标移动的网络协议。
Objective:
我决定实施心跳 TimerTask 是查看服务器是否已关闭的最佳方式。每隔 5 秒未进行鼠标移动(鼠标移动将计时器重置回 5),数组中 2 字节的心跳将发送到服务器。因此,如果服务器关闭,发送心跳后应该引发 IOException (Broken Pipe)
。
我的问题: 除了在抛出异常之前循环 3 次之外,心跳运行良好。目前,我将它设置为 5 秒后发送心跳。在服务器关闭后,它会在引发预期异常之前发送三个心跳(15 秒而不是 5 秒)。
我可以将心跳间隔设置为 2 秒,这又需要 6 秒来引发异常,但我想知道为什么它第一次不工作。
心跳码:
Timer task = new Timer();
task.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if (heartbeat == 1) {
byte buf[] = {-96, -96};
try {
bos.write(buf); // Buffered output stream
bos.flush(); // Exception gets thrown here
heartbeat = 5;
System.out.println("Testing 3");
} catch (IOException e) {
System.out.println("Testing 2");
launchHomescreen();
this.cancel();
e.printStackTrace();
}
} else {
System.out.println("Testing 1" + heartbeat);
heartbeat--;
}
}
}, 1000, 1000);
调试语句日志:
04-27 00:33:02.339 25410-26259/com.tutorials.jurko.androidmouse I/System.out﹕ Message sending: -99 -99 <-- This is the mouse click that should stop the server
04-27 00:33:03.190 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 15
04-27 00:33:04.201 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 14
04-27 00:33:05.192 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 13
04-27 00:33:06.193 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 12
04-27 00:33:07.203 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 3 <- Should throw the exception here
04-27 00:33:08.194 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 15
04-27 00:33:09.195 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 14
04-27 00:33:10.196 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 13
04-27 00:33:11.187 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 12
04-27 00:33:12.188 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 3
04-27 00:33:13.189 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 15
04-27 00:33:14.190 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 14
04-27 00:33:15.191 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 13
04-27 00:33:16.202 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 12
04-27 00:33:17.193 25410-26264/com.tutorials.jurko.androidmouse I/System.out﹕ Testing 2 <- but doesn't do it till the third heartbeat
04-27 00:33:17.233 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ java.net.SocketException: sendto failed: EPIPE (Broken pipe)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at libcore.io.IoBridge.maybeThrowAfterSendto(IoBridge.java:499)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at libcore.io.IoBridge.sendto(IoBridge.java:468)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at java.net.PlainSocketImpl.write(PlainSocketImpl.java:508)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at java.net.PlainSocketImpl.access0(PlainSocketImpl.java:46)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at java.net.PlainSocketImpl$PlainSocketOutputStream.write(PlainSocketImpl.java:270)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at java.io.BufferedOutputStream.flushInternal(BufferedOutputStream.java:185)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:85)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at com.tutorials.jurko.androidmouse.MainActivity$connectTask.run(MainActivity.java:496)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at java.util.Timer$TimerImpl.run(Timer.java:284)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ Caused by: libcore.io.ErrnoException: sendto failed: EPIPE (Broken pipe)
04-27 00:33:17.243 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at libcore.io.Posix.sendtoBytes(Native Method)
04-27 00:33:17.253 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at libcore.io.Posix.sendto(Posix.java:156)
04-27 00:33:17.253 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at libcore.io.BlockGuardOs.sendto(BlockGuardOs.java:177)
04-27 00:33:17.253 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ at libcore.io.IoBridge.sendto(IoBridge.java:466)
04-27 00:33:17.253 25410-26264/com.tutorials.jurko.androidmouse W/System.err﹕ ... 7 more
04-27 00:33:17.453 25410-25410/com.tutorials.jurko.androidmouse W/ApplicationPackageManager﹕ getCSCPackageItemText()
04-27 00:33:33.139 25410-25410/com.tutorials.jurko.androidmouse W/IInputConnectionWrapper﹕ getCursorCapsMode on inactive InputConnection
'connection reset'/'broken pipe' 不会立即发生,因为 TCP 缓冲和异步性。它发生在写入事件因未收到 ACK 而超时之后,这可能需要一分钟的时间。
它不一定与写入尝试次数有关,因此更改间隔不一定有帮助。
如果您想确定连接状态,请尝试在套接字上执行 recv
而不是发送心跳消息?如果服务器关闭并且连接消失,recv
将 return 归零。尽管在服务器由于 n/w 问题而无法访问的情况下,这仍然会失败。
如果客户端和服务器都发送数据包并相互确认,则应用程序保持活动状态效果最佳。依靠破管是不能完全满足要求的。
另一种选择是在 TCP 上启用 keep-alive
。
谢谢你们的回复,我解决问题的方法是在写入之前向套接字添加超时,然后再添加读取。如果读到 returns -1,那么我就知道服务器停止了。这个解决方案似乎也按照我想要的方式工作。
新代码:
Timer task = new Timer();
task.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if (heartbeat == 0) {
byte buf[] = {-96, -96};
byte send[] = new byte[1024]; // Added this
try {
socket.setSoTimeout(1000); // Added a timeout every read call
bos.write(buf); // Buffered output stream
bos.flush();
if(socket.getInputStream().read(send) == -1) { // End of stream reached
System.out.println("Testing 2");
launchHomescreen();
this.cancel();
}
heartbeat = 2;
System.out.println("Testing 3");
} catch (IOException e) {
System.out.println("Testing 2");
launchHomescreen();
this.cancel();
e.printStackTrace();
}
} else {
System.out.println("Testing 1" + heartbeat);
heartbeat--;
}
}
}, 1000, 1000);