Java/Android : Socket closed when offloading work to a thread pool

标签 java android multithreading sockets tcp

我在 Android 上编写线程池 TCP 服务器时遇到了一个非常令人费解的错误。基本上,我的代码结构如下:

  1. 标准服务器循环(在其自己的线程内的循环中阻止对 socket.accept() 的调用),在传入连接时调用处理程序:

    socket = mServerSocket.accept();
    myHandler.onIncomingConnection(socket);
    
  2. 处理程序将对传入连接的所有进一步处理卸载到线程池:

    public class X {
        private final ExecutorService receiveThreadPool = Executors.newSingleThreadExecutor();
    
        [...]
    
        private final ConnectionHandler mHandler = new MyServer.ServerHandler() {
          @Override
          public void onIncomingConnection(final Socket socket) {
    
                      MLog.vv(TAG, "Socket status: " + (socket.isBound() ? "bound" : "unbound") + ", "
                                   + (socket.isConnected() ? "connected" : "unconnected") + ", "
                                   + (socket.isClosed() ? "closed" : "not closed") + ", "
                                   + (socket.isInputShutdown() ? "input shutdown" : "input not shut down") + ".");
    
                      // Process result
                      receiveThreadPool.execute(new Runnable() {
                          @Override
                          public void run() {
                              MLog.vv(TAG, "Socket status: " + 
                                            (socket.isBound() ? "bound" : "unbound") + ... ); // same code as above
    
                              BufferedOutputStream out = null;
    
                              try {
                                  out = new BufferedOutputStream(socket.getOutputStream());
                                  out.write(HELLO_MESSAGE);
                                  out.flush();
    
                                  [rest omitted...]
    
                              } catch (IOException e) {
                                  [...]
                              } finally {
                                  [close resources...]
                              }
    
         }
    

请注意,套接字在处理程序方法的签名中被定义为final,从而可以从匿名内部Runnable 类中访问它。但是,第一次向输出流写入 out.write(HELLO_MESSAGE); 由于关闭套接字异常而失败。 logcat 输出:

02-16 17:49:26.383  14000-14057/mypackage:remote V/ManagementServer﹕ Incoming connection from /192.168.8.33:47764
02-16 17:49:26.383  14000-14057/mypackage:remote V/ManagementServer﹕ Socket status: bound, connected, not closed, input not shut down.
02-16 17:49:26.393  14000-14077/mypackage:remote V/ManagementServer﹕ Socket status: bound, unconnected, closed, input not shut down.
02-16 17:49:26.398  14000-14077/mypackage:remote E/ManagementServer﹕ Error communicating with client:
    java.net.SocketException: Socket is closed
            at java.net.Socket.checkOpenAndCreate(Socket.java:665)
            at java.net.Socket.getInputStream(Socket.java:359)
            at  net.semeion.tusynctest.network.ManagementServer$1$1.run(ManagementServer.java:79)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
            at java.lang.Thread.run(Thread.java:818)

如日志输出所示,在将 Runnable 卸载到线程池后,套接字以某种方式将其状态从已连接更改为未连接/关闭。 如果我只是删除 ThreadPool.execute 行,一切都会按预期工作。我还尝试在外部 class X 中创建自己的静态 Runnable 类,将套接字作为参数传递给 Runnable 的构造函数。然而,这会引发同样的问题。

我在这里遗漏了什么吗?从理论上讲,这应该像魅力一样发挥作用。但不知何故,在启动线程并终止处理程序的 incomingConnection 方法时,套接字实例似乎发生了一些事情。

更多事实:

  • 这也不是客户端的错 - 对于客户端来说,看起来服务器关闭了套接字。正如所说,注释掉服务器端的线程池可以解决问题。)
  • 您可能会注意到 logcat 输出中的“:remote” - 服务器线程是从后台服务中创建的,该后台服务本身在单独的进程中运行。
  • 目前,我正在为池使用 SingleThreadExecutor,仅用于测试。恕我直言,执行者的类型应该没有区别。
  • 我尝试直接使用套接字的OutputStream(无缓冲),这没有什么区别。日志语句显示套接字本身改变了其状态。
  • 如果我在执行线程之前在处理程序中初始化 BufferedOutputstream,则会在 out.flush(); 行产生一个奇怪的“错误文件号”SocketException(可能是IPC 问题?!):

    java.net.SocketException: sendto failed: EBADF (Bad file number)
        at libcore.io.IoBridge.maybeThrowAfterSendto(IoBridge.java:546)
        at libcore.io.IoBridge.sendto(IoBridge.java:515)
        at java.net.PlainSocketImpl.write(PlainSocketImpl.java:504)
        at java.net.PlainSocketImpl.access$100(PlainSocketImpl.java:37)
        at java.net.PlainSocketImpl$PlainSocketOutputStream.write(PlainSocketImpl.java:266)
        at java.io.BufferedOutputStream.flushInternal(BufferedOutputStream.java:185)
        at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:85)
        at net.semeion.tusynctest.network.ManagementServer$1$1.run(ManagementServer.java:88)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
        at java.lang.Thread.run(Thread.java:818)
    Caused by: android.system.ErrnoException: sendto failed: EBADF (Bad file number)
        at libcore.io.Posix.sendtoBytes(Native Method)
        at libcore.io.Posix.sendto(Posix.java:206)
        at libcore.io.BlockGuardOs.sendto(BlockGuardOs.java:278)
        at libcore.io.IoBridge.sendto(IoBridge.java:513)
    

感谢您的任何提示:)

最佳答案

我现在已经发现问题了。令人尴尬的是,我忽略了最明显的麻烦来源,即我的 Server 类中的服务器循环:

new Thread(new Runnable() {
    @Override
    public void run() {
      while (mReceiving) {

        Socket recSocket = null;

        try {
            recSocket = mServerSocket.accept();

            // Process connection
            mTcpServerHandler.onIncomingConnection(recSocket);

        } catch (IOException e) {
                // ...
        } finally {
            if (recSocket != null) {
                try {
                    recSocket.close();
                } catch (IOException e) {
                    // log, ignore...
                }
            }
        }
     }
   }
 }).start();

因此,如果处理程序将处理卸载到新线程,该方法会立即返回,并且服务器循环的 finally block 会关闭套接字(这是最初的 Intent ,但并没有适合线程池方法)。太明显了,抱歉打扰了:)

关于Java/Android : Socket closed when offloading work to a thread pool,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35439271/

相关文章:

Java JFrame 删除和重绘组件不起作用

java - Java中的非阻塞IO解决方案

java - 排序时非常奇怪的 NullPointerException

java - 如何在 JavaFX 菜单中调用 Java 方法

java - Spring Boot/Gradle/Logback : bootRun fails with "Failed to instantiate [ch.qos.logback.classic.LoggerContext]": java. lang.AbstractMethodError:

android - 简单的facebook android sdk库

Android- noHistory = true 从最近回来时不工作

java - 无法解析 JSON : No JsonObject

java - 同步访问相同文件、并发访问不同文件

Java 等待代码直到 JFrame keyPressed