从 InputStream 读取时出现 SocketException 错误的文件描述符

SocketException bad file descriptor when reading from InputStream

我正在尝试构建一个 android 应用程序,用户可以在其中创建一个新帐户并让服务器执行该帐户创建并将信息添加到数据库中。

在服务器完成(或未能完成)这个任务后,我想要一个响应发送回客户端,表明任务成功或失败;我正在从属于我的异步任务 class 的同一个套接字中打开一个 inputstream 和一个 outputstream

似乎代码在从客户端发送到服务器时执行良好,并且服务器正确接收了所有内容。但是当客户端从服务器接收响应时,我 运行 进入错误:

09-02 15:58:02.266 30979-32154/com.example.zemcd.messagebottle E/NewAccountTask: error connecting
    java.net.SocketException: recvfrom failed: EBADF (Bad file descriptor)
        at libcore.io.IoBridge.maybeThrowAfterRecvfrom(IoBridge.java:588)
        at libcore.io.IoBridge.recvfrom(IoBridge.java:552)
        at java.net.PlainSocketImpl.read(PlainSocketImpl.java:481)
        at java.net.PlainSocketImpl.-wrap0(PlainSocketImpl.java)
        at java.net.PlainSocketImpl$PlainSocketInputStream.read(PlainSocketImpl.java:237)
        at libcore.io.Streams.readSingleByte(Streams.java:41)
        at java.net.PlainSocketImpl$PlainSocketInputStream.read(PlainSocketImpl.java:233)
        at com.example.zemcd.messagebottle.NewAccountTask.doInBackground(NewAccountTask.java:50)
        at com.example.zemcd.messagebottle.NewAccountTask.doInBackground(NewAccountTask.java:17)
        at android.os.AsyncTask.call(AsyncTask.java:295)
        at java.util.concurrent.FutureTask.run(FutureTask.java:237)
        at android.os.AsyncTask$SerialExecutor.run(AsyncTask.java:234)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
        at java.lang.Thread.run(Thread.java:818)
    Caused by: android.system.ErrnoException: recvfrom failed: EBADF (Bad file descriptor)
        at libcore.io.Posix.recvfromBytes(Native Method)
        at libcore.io.Posix.recvfrom(Posix.java:189)
        at libcore.io.BlockGuardOs.recvfrom(BlockGuardOs.java:250)
        at libcore.io.IoBridge.recvfrom(IoBridge.java:549)

这是我的 android 客户端异步任务的代码,我已经指出了错误发生的位置:

public class NewAccountTask extends AsyncTask<Void, Void, Void> {
private static final String TAG = "NewAccountTask";
private static final String AUTH_KEY = "9LEF1D97001X!@:";
private static final String REQUEST_NEW_ACCOUNT = "0";
private static final int PORT = 5555;

private String mContactId;
private String mPassword;
private Context mContext;


private Socket mSocket;
private String response;

public NewAccountTask(String contactId, String password, Context context){
    mContactId = contactId + ":";
    mPassword = password + ":";
    mContext = context;
}

@Override
protected Void doInBackground(Void... params) {
    try{
        mSocket = new Socket("192.168.0.111", PORT);
        OutputStream out = mSocket.getOutputStream();
        InputStream in  = mSocket.getInputStream();
        //write key to server
        byte[] buffer = (AUTH_KEY + mContactId + mPassword + REQUEST_NEW_ACCOUNT).getBytes();
        out.write(buffer, 0, buffer.length);
        out.flush();
        out.close();

        //i posted a log message here this one runs
        int c;
        while((c = in.read()) != -1) { <--ERROR OCCURS HERE!
            response += (char) c;
        }
        //and here this one never runs

        in.close();

        Log.i(TAG, "connection success!");
    }catch (IOException ioe){
        Log.e(TAG, "error connecting", ioe);
    }

    return null;
}

@Override
public void onPostExecute(Void result){
    Toast.makeText(mContext, response, Toast.LENGTH_LONG).show();
}
}

服务器发送响应的方法如下:

    public void sendResponse(byte[] response) {
    OutputStream out;
    try {
        out = sock.getOutputStream();
        out.write(response, 0, response.length);
        out.flush();
        out.close();
    } catch (IOException e) {
        e.printStackTrace();
    }

}

According to the documentationSocket#getOutputStream class :

getOutputStream

OutputStream getOutputStream ()

Returns an output stream for this socket.

If this socket has an associated channel then the resulting output stream delegates all of its operations to the channel. If the channel is in non-blocking mode then the output stream's write operations will throw an IllegalBlockingModeException.

Closing the returned OutputStream will close the associated socket.

这里需要注意的是:

Closing the returned OutputStream will close the associated socket.

并且在您的代码中,在您写入输出流之后:

out.write(buffer, 0, buffer.length);

你关闭了输出流

out.flush();
out.close();

根据文档,关闭套接字并因此关闭关联的 InputStream。因此例外。

两个操作都执行完后就关闭套接字连接,而不是分别关闭每个流。

另请注意 flush()close() 之前毫无用处。

我使用了其他答案中的建议以及针对该问题发表的评论来解决这个问题。然而,一个主要问题仍然存在。如果一次一次发送消息的尝试失败,则服务器将永远不会完成并发送响应。我用来解决这个问题的方法是在连接的套接字上设置超时。我将读取循环替换为:

mSocket = new Socket();
mSocket.setSoTimeout(500);
SocketAddress socketAddress = new InetSocketAddress("192.168.0.111", PORT);
mSocket.connect(socketAddress);
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(mSocket.getOutputStream()));
BufferedReader in = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));
Log.d(TAG, "streams aquired");

out.write(AUTH_KEY + mContactId + mPassword + REQUEST_NEW_ACCOUNT);
Log.d(TAG, "data sent");
out.newLine();
out.flush();

Log.d(TAG, "read began");
response = in.readLine();
out.close();
in.close();
Log.d(TAG, "streams closed");

Log.i(TAG, "connection success!");

并使用 OnFailureListener 的 public 接口来通知调用 activity 套接字超时。