TCP 消息在不确定的时间后停止
TCP messages halt after indeterminate period
我有一个 TCP 客户端,它每秒向 TCP 服务器发送一个 14 字节的字符串,然后只打印该字符串。
客户端是 Android 应用程序,服务器是笔记本电脑上的简单 Java 应用程序 运行。通信是从客户端到路由器的 wifi,然后是以太网到笔记本电脑。
客户端连接到服务器正常并开始发送消息,但在不确定的时间后服务器停止接收消息。
查看来自服务器的 wireshark 流量显示最后一条消息已被服务器确认,但客户端显然从未收到 ACK,因为它重新传输消息并且服务器发送 DUP ACK。之后不再显示消息,直到我明确关闭客户端上的 socket.outputStream()
,此时服务器收到所有消息并且 Wireshark 显示包含所有丢失消息的 TCP 帧。
注意每条消息后我都会脸红。 TCP_NODELAY 的效果为零。
- 为什么消息突然被缓冲了?
- 我能做些什么来阻止它?
注意以下代码并非为生产用途而设计。这就是我可以调试它的原因。
客户:
private class SocketSender implements Runnable {
private final Socket socket = new Socket();
public void run() {
Log.d(TAG, "Starting SocketSender");
toggleButtons(SocketState.Transmitting);
try {
startSending();
} catch (IOException e) {
Log.d(TAG, "", e);
}
toggleButtons(SocketState.AwaitingClose);
Log.d(TAG, "Stopping SocketSender");
}
private void startSending() throws IOException {
int counter = 1;
Log.d(TAG, "Opening Socket to " + HOST_ADDRESS + ":" + HOST_PORT);
//socket.setTcpNoDelay(true);
//socket.setPerformancePreferences(0, 1, 0);
socket.connect(new InetSocketAddress(HOST_ADDRESS, HOST_PORT), 3000);
Log.d(TAG, "OpenedSocket");
try {
final OutputStream stream = socket.getOutputStream();
final PrintWriter output = new PrintWriter(stream);
while (sending) {
final String message = "Ping pingId=" + counter;
output.println(message);
output.flush();
Log.d(TAG, "Sent message : " + message);
counter++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Log.d(TAG, "Sleep interrupted", e);
}
}
output.close();
} finally {
//socket.close();
}
}
private void closeSocket() {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
Log.d(TAG, "Could not close Socket", e);
}
}
}
}
服务器:
private void readStream() throws IOException {
final ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
final Socket socket = serverSocket.accept();
final InputStreamReader streamReader = new InputStreamReader(socket.getInputStream());
final BufferedReader in = new BufferedReader(streamReader);
final SimpleDateFormat format = new SimpleDateFormat("YY-MM-DD HH:mm:ss.SSS");
long lastMessage = System.currentTimeMillis();
while (true) {
final String inputLine = in.readLine();
long thisMessage = System.currentTimeMillis();
if (inputLine == null) {
System.out.println(" No more input from : " + socket);
break;
}
System.out.println(format.format(new Date(thisMessage)) + " '" + inputLine + "' from : " + socket + " millisSinceLastMsg=" + (thisMessage - lastMessage));
lastMessage = thisMessage;
}
socket.close();
}
这里的关键是发件人没有收到 ACK。那是网络问题,不是编码问题。令人惊讶的是,发送方没有填充其发送缓冲区、阻止、最终使挂起的发送超时并重置连接。
您最好在接收方设置读取超时,并在连接触发时关闭连接。这样客户端将获得连接重置并且至少会知道出现问题,然后它可以重新连接或进行任何适当的操作。
在测试了 8 个不同的接入点和 11 个不同的 phone 型号后,这似乎只是华为 Y300 与某些接入点结合使用时的问题。
我无法确定 AP 是什么导致了锁定,所以我刚刚决定尽可能避免这种情况 phone。
我有一个 TCP 客户端,它每秒向 TCP 服务器发送一个 14 字节的字符串,然后只打印该字符串。
客户端是 Android 应用程序,服务器是笔记本电脑上的简单 Java 应用程序 运行。通信是从客户端到路由器的 wifi,然后是以太网到笔记本电脑。
客户端连接到服务器正常并开始发送消息,但在不确定的时间后服务器停止接收消息。
查看来自服务器的 wireshark 流量显示最后一条消息已被服务器确认,但客户端显然从未收到 ACK,因为它重新传输消息并且服务器发送 DUP ACK。之后不再显示消息,直到我明确关闭客户端上的 socket.outputStream()
,此时服务器收到所有消息并且 Wireshark 显示包含所有丢失消息的 TCP 帧。
注意每条消息后我都会脸红。 TCP_NODELAY 的效果为零。
- 为什么消息突然被缓冲了?
- 我能做些什么来阻止它?
注意以下代码并非为生产用途而设计。这就是我可以调试它的原因。
客户:
private class SocketSender implements Runnable {
private final Socket socket = new Socket();
public void run() {
Log.d(TAG, "Starting SocketSender");
toggleButtons(SocketState.Transmitting);
try {
startSending();
} catch (IOException e) {
Log.d(TAG, "", e);
}
toggleButtons(SocketState.AwaitingClose);
Log.d(TAG, "Stopping SocketSender");
}
private void startSending() throws IOException {
int counter = 1;
Log.d(TAG, "Opening Socket to " + HOST_ADDRESS + ":" + HOST_PORT);
//socket.setTcpNoDelay(true);
//socket.setPerformancePreferences(0, 1, 0);
socket.connect(new InetSocketAddress(HOST_ADDRESS, HOST_PORT), 3000);
Log.d(TAG, "OpenedSocket");
try {
final OutputStream stream = socket.getOutputStream();
final PrintWriter output = new PrintWriter(stream);
while (sending) {
final String message = "Ping pingId=" + counter;
output.println(message);
output.flush();
Log.d(TAG, "Sent message : " + message);
counter++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Log.d(TAG, "Sleep interrupted", e);
}
}
output.close();
} finally {
//socket.close();
}
}
private void closeSocket() {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
Log.d(TAG, "Could not close Socket", e);
}
}
}
}
服务器:
private void readStream() throws IOException {
final ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
final Socket socket = serverSocket.accept();
final InputStreamReader streamReader = new InputStreamReader(socket.getInputStream());
final BufferedReader in = new BufferedReader(streamReader);
final SimpleDateFormat format = new SimpleDateFormat("YY-MM-DD HH:mm:ss.SSS");
long lastMessage = System.currentTimeMillis();
while (true) {
final String inputLine = in.readLine();
long thisMessage = System.currentTimeMillis();
if (inputLine == null) {
System.out.println(" No more input from : " + socket);
break;
}
System.out.println(format.format(new Date(thisMessage)) + " '" + inputLine + "' from : " + socket + " millisSinceLastMsg=" + (thisMessage - lastMessage));
lastMessage = thisMessage;
}
socket.close();
}
这里的关键是发件人没有收到 ACK。那是网络问题,不是编码问题。令人惊讶的是,发送方没有填充其发送缓冲区、阻止、最终使挂起的发送超时并重置连接。
您最好在接收方设置读取超时,并在连接触发时关闭连接。这样客户端将获得连接重置并且至少会知道出现问题,然后它可以重新连接或进行任何适当的操作。
在测试了 8 个不同的接入点和 11 个不同的 phone 型号后,这似乎只是华为 Y300 与某些接入点结合使用时的问题。
我无法确定 AP 是什么导致了锁定,所以我刚刚决定尽可能避免这种情况 phone。