通过TCP套接字将音频写到服务器

17

我正在尝试通过TCP套接字将实时麦克风录音传输到服务器,并让服务器将输入流写入文件。连接已经建立,但是一段时间后,在客户端出现连接被拒绝的错误。

服务器代码:

    public class auServer extends Thread{
    private static ServerSocket serverSocket;
    private static int port = 3333; 

    public void run()
    {

        System.out.println("init success");
       while(true)
       {

          try
          {
              serverSocket = new ServerSocket(port);
              serverSocket.setSoTimeout(10000);
              Socket clientSoc = serverSocket.accept();
             System.out.println("Waiting for client on port " +serverSocket.getLocalPort() + "...");
             System.out.println("Just connected to " + clientSoc.getRemoteSocketAddress());
             InputStream in = clientSoc.getInputStream();
             while(in!=null)
             {
                 writeToFile(in);
             }
             System.out.println("socket");

             clientSoc.close();
          }catch(SocketTimeoutException s)
          {
             System.out.println("Socket timed out!");
             break;
          }catch(IOException e)
          {
             e.printStackTrace();
                     System.out.println("some io");
             break;
          } catch (Exception e) {
                    System.out.println("some e");
            e.printStackTrace();
        }
       }
    }

    private void writeToFile(InputStream in) throws IOException {
        // Write the output audio in byte
        String filePath = "8k16bitMono1.wav";
        short sData[] = new short[1024];
        byte[] bData = IOUtils.toByteArray(in);
        FileOutputStream os = null;
        try {
         os = new FileOutputStream(filePath);
        } catch (FileNotFoundException e) {
         e.printStackTrace();
        }
         System.out.println("Short wirting to file" + sData.toString());
         try {
          os.write(bData, 0, 2048);
         } catch (IOException e) {
          e.printStackTrace();
         }
        try {
         os.close();
        } catch (IOException e) {
         e.printStackTrace();
        }

    }


    public static void main(String[] args) {
        // TODO Auto-generated method stub
      try
      {
        Thread serverThread = new auServer();
        serverThread.run();
        System.out.println("runing");
       }catch(IOException e){
         e.printStackTrace();
      }
    }
}

和客户端:

private void streamData(byte[] bData) throws UnknownHostException, IOException, InterruptedException {  //bData is byte array to transmit
    Thread.sleep(500);
    Socket client = new Socket("10.221.40.41",3333);
    OutputStream outToServer = client.getOutputStream();
    outToServer.write(bData);
    if(!isRecording)
        client.close();
}

可能出了什么问题?谢谢。


2
有任何异常吗?请浏览一些“阻塞网络 I/O 教程”。代码存在多个问题。 - Fildor
1
尝试传输实时麦克风录音 - 你对“实时”的定义是什么? - Fildor
2
为什么您要为每个数据创建一个新的客户端?而服务器为什么要一遍又一遍地将这些数据写入同一个文件中?该文件不会增长,只包含一个数据。请解释一下您的想法。 - greenapps
2
os.write(bData, 0, 2048);。2048?你确定吗?为什么? - greenapps
1
你没有认真回答我的问题。 - greenapps
显示剩余14条评论
4个回答

15

我会逐句评论你的代码。

private static ServerSocket serverSocket;

这个内容没有必要是静态的。

while(true)
{
    try
    {
        serverSocket = new ServerSocket(port);
        serverSocket.setSoTimeout(10000);

在循环之前应该加上最后两行代码。这是连接被拒绝的原因,也会导致你没有提到的BindExceptions发生。不清楚你为什么需要timeout。

             Socket clientSoc = serverSocket.accept();
             System.out.println("Waiting for client on port " +serverSocket.getLocalPort() + "...");

不,你没有。他已经连接了。在accept()之前,你一直在等待。

             System.out.println("Just connected to " + clientSoc.getRemoteSocketAddress());
             InputStream in = clientSoc.getInputStream();
             while(in!=null)

循环和测试都是无效的。变量最初不为null,并且它永远不可能成为null。循环是徒劳的,因为writeToFile()方法完全排空了输入流,所以再也没有什么可读的了。这将导致垃圾数据,而您还没有提到。

             {
                 writeToFile(in);
             }
             System.out.println("socket");

一个无意义的消息。

             clientSoc.close();
accept()后面的所有代码都应该在一个单独的线程中执行。接受循环除了接受连接和启动线程外不应该做任何其他事情。
          }catch(SocketTimeoutException s)
          {
             System.out.println("Socket timed out!");

这里超时的是accept(),因为监听套接字是您唯一设置超时的套接字。我怀疑您不需要这个。

             break;
          }catch(IOException e)
          {
             e.printStackTrace();
                     System.out.println("some io");

又是一条无用的消息。

             break;
          } catch (Exception e) {
                    System.out.println("some e");

再来一个经验。当你遇到异常时,输出异常信息。不要输出一些无用的自定义消息。否则,调试就会成为猜谜游戏。

            e.printStackTrace();
        }
       }
    }

    private void writeToFile(InputStream in) throws IOException {
        // Write the output audio in byte
        String filePath = "8k16bitMono1.wav";
        short sData[] = new short[1024];

未使用。请删除。

        byte[] bData = IOUtils.toByteArray(in);

不要使用这个。它会浪费空间并增加延迟。请参见下面的正确解决方案。

        FileOutputStream os = null;
        try {
         os = new FileOutputStream(filePath);
        } catch (FileNotFoundException e) {
         e.printStackTrace();
        }

技术不佳,catch 的位置错误。依赖于 try 块中代码的成功执行的代码应该在同一个 try 块内部。目前你通过这个 try-catch 块就好像异常从未发生一样,这将导致下面的代码出现 NullPointerException

         System.out.println("Short wirting to file" + sData.toString());

另一个毫无意义的消息。拼写错误;sData里面没有任何东西;无论内容如何,sData.toString()都不会打印出任何有用的东西;而且是错误的,因为你根本没有写 sData

         try {
          os.write(bData, 0, 2048);

无论读取的数量是多少,这都将精确地写入2048字节。如果它少于2048字节,它将抛出ArrayIndexOutOfBoundsException或类似异常,我期望会在第二次调用时看到。虽然您没有提到,因为数组在第二次及以后的调用(如果有的话)应该是零长度。它应该是一个循环,一般形式如下:

int count;
byte[] buffer = new byte[8192]; // or more if you like
while ((count = in.read(buffer)) > 0)
{
    out.write(buffer, 0, count);
}

回到你的代码:

         } catch (IOException e) {
          e.printStackTrace();
         }
        try {
         os.close();
        } catch (IOException e) {
         e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        // TODO Auto-generated method stub
      try
      {
        Thread serverThread = new auServer();
        serverThread.run();

这将运行线程的run()方法。它不会启动一个线程。应该是serverThread.start()。

        System.out.println("runing");

拼写错误。在您解决了上面的启动/运行问题之前,直到服务器的run()方法退出后,您才能看到此消息。

以及客户端:

private void streamData(byte[] bData) throws UnknownHostException, IOException, InterruptedException {  //bData is byte array to transmit
    Thread.sleep(500);

毫无意义。移除它。不要将休眠放入网络编码中。

    Socket client = new Socket("10.221.40.41",3333);

不要为每个缓冲区创建一个新连接,而是在客户端的整个生命周期内使用同一个连接。

    OutputStream outToServer = client.getOutputStream();
    outToServer.write(bData);
    if(!isRecording)
        client.close();

这应该是无条件的,并且应该与套接字的创建一起放在其他地方。

可能会有什么问题?

问题。复数形式。多个问题。请参见上文。


2
很好的解释,这正是我期待的,评价(+1)。感谢您的努力,我会按照以上指南尝试并标记为答案,如果有效的话。再次感谢并抱歉之前的无礼 :) - kAmol
@EJP 在客户端,如果我不为每个缓冲区创建新连接并在每个循环中关闭它,它会同时发送完整的记录(10XXX~字节,直到我达到退出while的情况)。我想将其连续地(流式地) 以2048字节的方式发送,而不是在最后。 - kAmol
持续进行流式传输正是建议更改的内容。我不明白你所说的“不在结尾”的意思。 - user207421

3
我猜你的客户端是一个安卓应用。曾经我尝试通过套接字连接读写安卓应用中的数据。最终,我使用adb(安卓调试桥)解决了这个问题。
假设现在有一个Java应用程序,它创建了一个端口号为1992的Socket。还有一个安卓应用程序,它创建了一个端口号为1993的ServerSocket
接下来就是真正的问题:在运行这些应用程序之前,你需要在adb shell中(或者可以定位到cmd/terminal并执行该命令)执行以下命令。
adb forward tcp:1992 tcp:1993    

在端口转发后,您可以使用套接字的IO流在Java应用程序和Android应用程序之间进行数据的读写。

桌面应用程序代码片段:

while (flag) {
    try {
        final Socket socket = new Socket("localhost", 1992);
        new Thread(){
            @Override
            public void run() {                        
                while (flag) {                            
                    try {
                        new PrintWriter(socket.getOutputStream(), true).println(new Date().toString());
                    } catch (IOException ex) {
                        break;
                    }
                }
            }                    
        }.start();
    } catch (IOException ex) {
        // need port-forwarding
    }
}    

Android应用程序代码片段:

while (flag) {
    try {
        final Socket socket = new ServerSocket(1993).accept();
        new AsyncTask<Void, Void, Void>() {
            @Override
            public Void doInBackground(Void... params) {
                while (flag) {
                    try {
                        String line = new BufferedReader(new InputStreamReader(socket.getInputStream())).readLine();
                        Log.d("", line);
                    } catch (IOException ex) {
                        return null;
                    }
                }
                return null;
            }
        }.execute();
    } catch (IOException ex) {
        break;
    }
}    

你可以将音频转换为字节数组并将其写入输出流中。希望我的答案对你有用。

抱歉我的问题没有表述清楚。我的安卓应用将会向服务器流媒体音频。在我的代码中,我只传输字节数据,但我不明白为什么需要使用“ADB forward tcp”。请解释一下。 感谢您的努力。 - kAmol

3
创建服务器套接字必须在循环外完成。对于并行连接,您需要为每个连接启动一个线程。
同时,将超时应用于已建立的连接。在服务器套接字上设置超时后,接受连接会在超时后结束,这不是服务器想要的结果。
...
public void run() {
    ServerSocket serverSocket;
    try {
        serverSocket = new ServerSocket(port);
    } catch (Exception ex) {
        ex.printStackTrace();
        return;
    }

    System.out.println("init success");

    while (true) {
        try {
            System.out.println("Waiting for client on port " + serverSocket.getLocalPort() + "...");
            final Socket clientSoc = serverSocket.accept();
            clientSoc.setSoTimeout(10000);

            System.out.println("Just connected to " + clientSoc.getRemoteSocketAddress());


            new Thread() {
                public void run() {
                    try {
                        InputStream in = clientSoc.getInputStream();
                        writeToFile(in);
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            clientSoc.close();
                        } catch (Exception ignore) {
                        }
                    }
                }
            }.start();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}
...

1
我找到了解决方案。 问题实际上是由于编写错误的流而导致阻塞网络I/O。
我忽略了:
InputStream in = clientSoc.getInputStream();
DataInputStream DataIn =new DataInputStream(in)
while(DataIn!=null)
    {
        writeToFile(DataIn);
    }

还有客户端也要考虑。

OutputStream outToServer = client.getOutputStream();
DataOutputStream out = new DataOutputStream(outToServer);
out.write(sData);

2
不,你没有找到解决方案。while (DataIn != null) 没有任何意义。它既不能最初为 null,也永远不会为 null,因此循环将永远不会终止,并且尝试从同一流中多次写入是徒劳的,或者应该是徒劳的。在这种情况下更改为 DataInputStreamDataOutputStream 也是徒劳的。你的代码存在许多问题,但它们与这些问题完全不同。 - user207421
@EJP,我已经成功获取了直播流数据包并在服务器端进行了播放。DataIn!=null可能是一个错误的条件,但当我添加了DataOutputStream out = new DataOutputStream(outToServer);时,它可以正常工作。我承认代码可能存在问题,但请帮助我解决问题,而不是批评它并将其评为-1。 - kAmol
你肯定也改了别的东西。那个更改什么也没变。DataOutputStream.write()DataInputStream.read() 与任何其他读写方法没有区别。你在这里发布的代码会抛出运行时异常。如果你不想让你的代码受到批评,那么发布它就没有太多意义。如果它完美无缺,你也不会有问题。你到处都在臆断。你没有任何证据表明是谁对你的答案进行了负面评价。你似乎不理解这个网站的运作方式,也不懂得如何进行代码审查。 - user207421
如果你真的认为数据输入/输出流具有解决此问题所需的神奇属性,那么你需要说明原因以及这些属性是什么。否则,你的答案只是一种迷信式编程。 - user207421

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