【问题标题】:client socket does not receive exactly what the server side socket sends客户端套接字没有准确接收服务器端套接字发送的内容
【发布时间】:2017-01-04 03:10:32
【问题描述】:

我一直在开发一个 Android 音频聊天程序,其行为类似于对讲机。用户按下通话按钮后,录音机开始记录用户所说的内容,并通过套接字将音频字节写入远程服务器。在服务器端,服务器套接字只是将它接收到的音频字节发送到其他客户端套接字。

我没有控制这些套接字行为的好方法。例如,要识别一个客户端套接字属于哪个用户?套接字除了写入的数据外,没有任何字段来携带附加信息。所以最后,我想出解决方案是使用传输音频数据的相同套接字来传输用户名字符串之类的东西。这很好用,因为在客户端套接字成功创建到服务器套接字的连接的情况下,android客户端会发送一个用户名字符串。

当我尝试发送用户名字符串以通知其他正在讲话的客户时,当用户按下通话按钮时,灾难发生了。让我举一个例子来说明这一点:

  1. 名为“user1”的用户按下通话按钮进行通话。
  2. 应用程序将字符串“usr:user1”发送到服务器端。
  3. 然后开始发送录音机生成的音频数据。

在服务器端,服务器接收到确切的“user1”和以下音频数据并重新发送到其他连接的客户端。但问题是客户端似乎并没有一直收到“usr:user1”。

这是我检查接收到的数据的方法:

 is = socket.getInputStream();
 byte[] buffer = new byte[minBufSize];
 numOfReceived = is.read(buffer);
 if(numOfReceived!=-1&&numOfReceived!=minBufSize){
     byte[] ub = new byte[numOfReceived];
     for(int i=0;i<numOfReceived;i++){
         ub[i]=buffer[i];
     }
     String usersString = new String(ub, "UTF-8");
     if(usersString.contains("hj:")){
         System.out.println("current:");
         final String userOfTalking=usersString.substring(3,usersString.length());
         runOnUiThread(new Runnable() {
             @Override
             public void run() {
                 whoIsTalking.setText(userOfTalking+" is talking");
                 whoIsTalking.setVisibility(View.VISIBLE);
             }
         });
         continue;
     }

实际上,我不知道输入流是包含音频数据还是字符串数据。所以我尝试使用inputstream.read()的返回来找出输入流读取了多少字节:

  • 如果返回数不等于-1(套接字关闭)或缓冲区大小,我在outputstream.write中设置,那么我假设它是一个字符串。

但这是非常不可靠的。例如,如果我循环命令 socket.getoutstream.write(buffer,0,100),那么我应该从输入流中读取长度为 100 的缓冲区。但事实并非如此。我经常得到长度为 60、40 或任何小于 100 的缓冲区。

就像输出流没有像它声明的那样发送精确的 100 字节数据。所以我的字符串数据只是与以下音频数据混合。因此,当应用程序刚连接到服务器时发送用户名,其他客户端将收到正确的字符串,因为没有后续音频数据干扰它。

大家能给我一些意见吗?我的猜测对吗?我怎么解决这个问题?当用户按下通话按钮时,我设法在应用程序发送用户名字符串后调用 Thread.sleep(300) 以便在发送音频数据之间留出一些空间,以防它们混合。但它不起作用。非常感谢任何帮助!

【问题讨论】:

  • 哇,这是一堵文字墙。确实太长了,没看。无论如何,您需要minimal reproducible example
  • @JonathonReinhart 最小示例对于这个特定问题不太可能可行。建设性的输入将包括复制编辑。我尽力了。
  • @user1870797 我尽力编辑。如果可能的话,你认为你可以删除一些对澄清问题没有特别有用的细节吗?这可能会让一些人更容易解决你的问题。您还可以清理您的接收器 sn-p(它有一个杂散的 continue 和一些不平衡的 {})并包含您的 sending 代码的相关部分吗?另外,也许很明显,但是您使用的是 TCP(不是 UDP),对吗?您的问题可能会在此期间被搁置,但不要担心,它可以在稍微清理一下后重新打开。
  • @Jason C,感谢你们的建议和 Jason C 的编辑。抱歉杀了你的编辑。我对stackoverflow的功能不太熟悉。但非常感谢您的努力。我正在重新编辑我的问题以使其更清晰。
  • @Jason C 是的,它们是 tcp 套接字,我会添加这个。

标签: java android sockets


【解决方案1】:

自从我提出这个问题以来已经有一段时间了,我现在要给出自己的答案。希望还不算太晚。

实际上@Philip Couling 在他的回答中提出了一些非常有价值的见解,它帮助我证实了我对这个问题的原因的猜测——“操作系统正在对数据包之间的数据进行分段”。再次感谢他的贡献。

解决此问题的方法来自我的一位朋友。他告诉我,我可以在客户端创建一个新的套接字来连接到同一个服务器套接字,以字符串格式传输一些控制信息,告诉服务器谁开始说话,谁停止说话,甚至允许人们通过它聊天。每个套接字都会向服务器发送一个字符串,以“音频流:用户名”或“控制信息:用户名”等格式告诉服务器他们在做什么以及他们属于谁。并且服务器只是将它们分别存储到两个arraylist或hashmap中。因此,每次用户按下按钮流式传输音频时,都会将相应的控制信息字符串发送到服务器,告诉它流来自谁,然后服务器将这些信息通过套接字重定向到其他客户端进行控制。因此,现在我们在专用套接字中传输字符串数据,而不是传输音频流。因此,“操作系统对数据进行分段”不再是问题,因为字符串数据太短而无法触发操作系统对它们进行分段,还因为我们只是在特定事件上发送它们,而不是像发送音频流那样连续发送。

但是新的socket也带来了副作用。由于网络延迟,人们可能会在应用程序告诉他们有人停止说话后发现他们仍然收到了一段时间的语音。在极端的网络条件下,延迟可能超过 10 秒,如果有人在手机播放接收语音时开始说话,可能会导致强烈的噪音。

为了解决这个问题,在音频套接字中传输字符串通知可能是保持双方同步的唯一选择。但我认为我们可以在音频数据和字符串数据之间插入一些空字节,以确保字符串不会与其他数据混合。(空字节不应更改字符串。)但是我还没有尝试过这种方法。我会在检查后添加结果。

【讨论】:

    【解决方案2】:

    如果我正确地阅读了这篇文章...您发送了 100 个字节,但后续的 read 没有得到 100,它得到的更少?

    这可能有多种原因。一是你在写作时没有打电话给flush()。如果是这种情况,那么您有一个错误,您需要在您的发送代码中放置一个适当的 flush() 调用。

    另外,这可能是因为操作系统正在对数据包之间的数据进行分段。这对于小数据包(100 字节)不太可能,但对于大数据包很可能/必要......

    您永远不应该依赖于一次读取中出现的所有数据...您需要多次读取以组合所有数据。

    【讨论】:

    • 感谢您的回答。100 字节可能有点误导,因为正如您所说,它是一个小数据包。我实际使用的是这样的:outputstream.write(buffer,0,3200) in a loop.Is 3200 大到必须分片吗?我试图调用 flush() 但它没有帮助。就像您不能保证发送您声明的确切字节。你能给我更多关于多次读取和组装所有数据的信息吗,听起来很有用但我不能完全理解。
    • 是的,基本上你不能强迫它出现在你发送的相同块中,除非你使用 UDP。您需要将其视为数据流,而不是单个块。 4K 是一个数据包的常见最大大小,但 3K 很容易触发多个数据包。
    猜你喜欢
    • 1970-01-01
    • 2014-07-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-22
    • 2016-03-03
    相关资源
    最近更新 更多