OutputStream.write()成功但数据未传送。

9
我在使用socket写入时遇到了非常奇怪的行为。 在我的移动客户端中,我使用以下方式初始化一个socket:
private void initSocket()
{
    socket = new Socket();
    socket.connect(new InetSocketAddress(host, port));

    os = new DataOutputStream(socket.getOutputStream());
    is = new DataInputStream(socket.getInputStream());
}

然后定期(每60秒)对此套接字读取和写入一些数据(这里的代码有点简化):

if(!isSocketInitialized())
{
    initSocket();
}

byte[] msg = getMessage();

os.write(msg);
os.flush();

int bytesAvailable = is.available( );
if(bytesAvailable>0)
{
    byte[] inputBuffer = new byte[bytesAvailable];

    int numRead = is.read(inputBuffer, 0, bytesAvailable);
    processServerReply(inputBuffer, numRead);
}

它是有效的。但是...有时候(非常少,可能每天1或2次),我的服务器无法接收数据。我的客户端日志看起来像:

Written A
Written B
Written C
Written D
Written E

等等。但在服务器端看起来像:

Received A
Received E

尽管在客户端看起来所有数据都已发送且没有任何异常,但B、C、D数据记录却未收到!

这样的间隙可能很小(2-3分钟),这不是太糟糕,但有时可能非常大(1-2小时=60-120个周期),这确实是我的客户面临的问题。

我真的不知道出了什么问题。数据似乎由客户端发送,但从未到达服务器端。我还使用代理进行了检查。

我只有日志,无法重现此问题(但它每天会多次发生在我的客户身上),有时在日志中我看到连接断开并出现异常“sendto failed: ECONNRESET (Connection reset by peer)”。之后程序关闭套接字并重新初始化:

// close
is.close();
os.close();
socket.close();

// reinitialize
initSocket();

尝试按照上述描述再次写入数据。然后我发现问题:连接建立,写入成功,但服务器上没有任何数据到达!可能与 ECONNRESET 有关,也可能不是,但我想提一下,因为这可能很重要。

对于任何想法和提示,我将非常感激。

P.S.也许它扮演了一些角色:客户端代码运行在正在移动的 Android 移动设备上(即在汽车中)。通过 GPRS 建立互联网连接。


更新:我可以复现它!至少部分地复现(客户端发送 A,B,C,D,E,而服务器只接收到 A)。如果:

  1. 建立连接,客户端读取和写入 -> OK

  2. 连接丢失(我关闭 WLAN 路由器 :)),我会出现 IOException,我关闭流和套接字 -> OK

  3. 我打开路由器,连接恢复,我重新初始化 socket,程序执行 write() 未出现异常,但...没有数据到达服务器。

顺便说一句,由于连接已恢复,available() 总是返回 0。


你不能使用 ByteArrayOutputStream 吗?类似 这样的方式 - jnthnjns
2
@Asok 是的,我可以。但为什么?为什么它能解决这个问题? - Valelik
1
@Asok 没问题,你不用道歉 :) 我对每一个建议都非常感激! - Valelik
有没有一种方法请求服务器发送确认(ACK)以表示数据已被接收?如果服务器即使在数据丢失时仍然发送ACK,那么这是服务器的问题。由于它是封闭软件,您无法修复它。但是,如果它只会为正确接收的数据发送ACK,您可以使用缺失的ACK来决定在间隔后是否尝试重新发送。这可能就是WhatsApp所做的。 - ADTC
@ADTC 服务器有一个 Web 接口,用户可以从那里向不同的客户端(例如我)发送文本消息。processServerReply 函数解析这些消息。 - Valelik
显示剩余6条评论
2个回答

2

取消available()测试。如果同行应该回复,那就读取它。否则有时你会读到回复,有时你不会,这样就会出现不同步的情况。正确使用available()的情况非常少,而这并不是其中之一。


谢谢您的建议,我正在重写代码,以便可以永久地读取数据而不阻塞写入(顺便问一下,您能推荐一些关于线程和网络的阅读材料吗?)。但是...您能否请解释一下您的建议如何帮助解决问题?我认为我在关闭或初始化时做错了什么。所以套接字是“打开”的和“连接”的,我可以“写入”等,但没有数据传递到服务器。 - Valelik
@Valelik 根据响应到达的速度有选择性地读取响应肯定是不正确的,对吧?你现在所做的将会引起各种问题,从你没有读取的“丢失数据”,到写入阻塞,再到“连接被对等方重置”。请告诉我们你尝试后发生了什么。 - user207421
我已经再次重写了我的通讯代码。现在,我为读取操作添加了一个额外的线程,所以不再使用 available() 方法。但是这并没有解决问题,我依然面临同样的困境。 - Valelik
我不同意这不是available()的正确用法(Valelik使用它来确定byte[]数组的大小的部分肯定是错误的)。如果您需要从多个(可能是无限的)流中读取并且不想创建潜在的无限线程来服务它们,则available() > 0对我来说似乎是一种不错的方法-它类似于C中的select()方法。但是,您必须记得在迭代之间sleep()以避免始终在100%的CPU负载上运行软件。 - gardarh
@gardarh,您刚才描述的是available()不同用法。这个用法仅在立即到达回复时读取它,这将不可避免地导致某些时间失去同步。 - user207421

1
这种奇怪的行为是由服务器端未关闭套接字引起的。我可以用小的客户端和服务器重现它们,两者都由几行代码组成,执行以下操作:
  1. 连接到服务器
  2. 模拟死区(例如关闭WiFi)
  3. 在客户端上关闭套接字
  4. 不要在服务器端关闭套接字
  5. 打开你的WiFi
  6. 从客户端建立一个新的连接
  7. 向此连接写入数据
恭喜!客户端无任何错误地写入数据,但服务器却没有收到...
但是,如果服务器也关闭了套接字,则服务器可以读取此数据。因此,解决方案应该是在服务器端超时后关闭套接字。
不幸的是,在我的情况下,这不可行,因为服务器是专有的第三方软件。

2
服务器出现了严重问题。它在原始连接上的读取应该超时或永久阻塞,或者获取EOS导致关闭该连接;并且它应该能够成功地从新连接中读取新数据。如果没有成功,那么它就是单线程的或存在其他并发问题。我不明白为什么你接受这个作为自己问题的答案,因为它根本没有回答问题,只是澄清了错误发生的情况。这不是一个答案。 - user207421

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接