我在 Android 上编写线程池 TCP 服务器时遇到了一个非常令人费解的错误。基本上,我的代码结构如下:
标准服务器循环(在其自己的线程内的循环中阻止对
socket.accept()
的调用),在传入连接时调用处理程序:socket = mServerSocket.accept(); myHandler.onIncomingConnection(socket);
处理程序将对传入连接的所有进一步处理卸载到线程池:
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/