为什么在第一个连接结束后创建第二个连接会导致管道异常异常?

Why does creating a second connection after the first one has been ended result in a broken pipe exception?

在我的应用程序中,我有两个人在不同的设备上通过 wifi 直接对等连接进行连接。当它们连接时,将分配一个用户 主机”和另一个 "client"。

WifiP2pManager.ConnectionInfoListener connectionInfoListener = new WifiP2pManager.ConnectionInfoListener() {
    @Override
    public void onConnectionInfoAvailable( WifiP2pInfo wifiP2pinfo) {
         InetAddress groupOwnerAddress = wifiP2pinfo.groupOwnerAddress;

        if (wifiP2pinfo.groupFormed && wifiP2pinfo.isGroupOwner) {

            connectionStatus.setText(R.string.host);
            new ServerClass().start();


        } else if (wifiP2pinfo.groupFormed) {

            connectionStatus.setText(R.string.client);
            new ClientClass(groupOwnerAddress).start();


        }

    }

};

主机运行ServerClass,客户端运行ClientClass。他们都使用 SendRecieve class 互相发送数据,这些数据是通过处理程序接收的。这一切都很好

public class ServerClass extends Thread {
    Socket socket;
    ServerSocket serverSocket;

    @Override
    public void run() {
        super.run();
        try {
            serverSocket = new ServerSocket(8888);
            socket = serverSocket.accept();
            sendReceive = new SendReceive(socket);
            sendReceive.start();
        } catch (IOException e) {
            Log.v("MainActivity", "three" + e);
        }
    }
}

private class SendReceive extends Thread {
    Socket socket;
    OutputStream outputStream;
    InputStream inputStream;


    private SendReceive(Socket skt) {
        socket = skt;
        try {
            inputStream = socket.getInputStream();
            outputStream = socket.getOutputStream();
        } catch (IOException e) {
            Log.v("MainActivity", "four" + e);
        }
    }

    @Override
    public void run() {
        byte[] buffer = new byte[1024];
        int bytes;
        while (socket != null) {
            try {
                bytes = inputStream.read(buffer);
                //System.out.println(new String(buffer, "UTF8"));
                if (bytes > 0) {
                    handler.obtainMessage(MESSAGE_READ, bytes, -1, buffer).sendToTarget();
                }
            } catch (IOException e) {
                Log.v("MainActivity", "five" + e);
            }
        }
    }

    private void write(byte[] bytes) {
        try {
            outputStream.write(bytes);
        } catch (IOException e) {
            Log.v("MainActivity", "six" + e);
        }
    }
}

public class ClientClass extends Thread {
    Socket socket;
    String hostAdd;

    private ClientClass(InetAddress hostAddress) {
        hostAdd = hostAddress.getHostAddress();
        socket = new Socket();
    }

    @Override
    public void run() {
        try {
            socket.connect(new InetSocketAddress(hostAdd, 8888), 500);
            sendReceive = new SendReceive(socket);
            sendReceive.start();

        } catch (IOException e) {
            Log.v("MainActivity", "seven" + e);
        }
    }
}

然后当游戏结束时,玩家通过按下调用此方法的按钮断开连接

public void disconnect() {
    if (mManager != null && mChannel != null) {

        mManager.requestGroupInfo(mChannel, new WifiP2pManager.GroupInfoListener() {
            @Override
            public void onGroupInfoAvailable(WifiP2pGroup group) {
                if (group != null && mManager != null && mChannel != null
                        && group.isGroupOwner()) {
                    mManager.removeGroup(mChannel, new WifiP2pManager.ActionListener() {

                        @Override
                        public void onSuccess() {
                            Log.d("main", "removeGroup onSuccess -");
                        }

                        @Override
                        public void onFailure(int reason) {
                            Log.d("main", "removeGroup onFailure -" + reason);
                        }
                    });
                }
            }
        });

    }
}

当用户随后尝试通过寻找其他玩家然后与他们联系来与某人玩另一场游戏时出现问题。当他们在与他们联系的第一个人断开连接后执行此操作时,点对点连接不起作用。它抛出一个破损的管道异常,并且无法在两个用户之间发送信息。直到应用程序关闭然后再次打开,然后用户在应用程序打开后第一次搜索并连接到某人时,点对点连接才正常工作。本质上,如果我重新启动应用程序,由于某种原因,连接似乎工作正常。但是必须有一种不那么直截了当的方法来解决根本问题,我似乎无法弄清楚它是什么。我已经阅读了之前所有与管道损坏错误有关的问题,他们都在谈论一端关闭连接而另一端仍在尝试写入它们,但这里不是这种情况,因为之前的连接是在那时建立的结束,并生成一个全新的。有没有我遗漏的东西可以让我解决这个问题?

logcat:

I/art: After code cache collection, code=245KB, data=226KB
D/ViewRootImpl@33c15f0[MainActivity]: ViewPostImeInputStage processPointer 0
D/ViewRootImpl@33c15f0[MainActivity]: ViewPostImeInputStage processPointer 1
D/ViewRootImpl@33c15f0[MainActivity]: mHardwareRenderer.destroy()#4
dispatchDetachedFromWindow
D/InputTransport: Input channel destroyed: fd=90
D/ViewRootImpl@589584e[MainActivity]: MSG_WINDOW_FOCUS_CHANGED 1
D/ViewRootImpl@589584e[MainActivity]: mHardwareRenderer.initializeIfNeeded()#2 mSurface={isValid=true -609390592}
V/InputMethodManager: Starting input: 
tba=android.view.inputmethod.EditorInfo@c3f369e nm : com.example.hosse.myapplication ic=null
I/InputMethodManager: [IMM] startInputInner - 
mService.startInputOrWindowGainedFocus
D/InputTransport: Input channel constructed: fd=91
Input channel destroyed: fd=94
E/ViewRootImpl: sendUserActionEvent() returned.
D/main: removeGroup onSuccess -
V/MainActivity: fivejava.net.SocketException: Software caused connection abort
D/AbsListView:  in onLayout changed 
D/ViewRootImpl@ca0894c[Toast]: ThreadedRenderer.create() translucent=true
D/InputTransport: Input channel constructed: fd=94
D/ViewRootImpl@ca0894c[Toast]: setView = android.widget.LinearLayout{6f7cd95 V.E...... ......I. 0,0-0,0} touchMode=true
D/ViewRootImpl@ca0894c[Toast]: dispatchAttachedToWindow
D/ViewRootImpl@ca0894c[Toast]: Relayout returned: oldFrame=[0,0][0,0] newFrame=[294,1596][786,1728] result=0x27 surface={isValid=true -593956864} 
surfaceGenerationChanged=true
D/mali_winsys: EGLint new_window_surface(egl_winsys_display*, void*, EGLSurface, EGLConfig, egl_winsys_surface**, egl_color_buffer_format*, EGLBoolean) returns 0x3000,  [492x132]-format:1
D/ViewRootImpl@ca0894c[Toast]: mHardwareRenderer.initialize() mSurface={isValid=true -593956864} hwInitialized=true
D/ViewRootImpl@ca0894c[Toast]: MSG_RESIZED_REPORT: frame=Rect(294, 1596 - 786, 1728) ci=Rect(0, 0 - 0, 0) vi=Rect(0, 0 - 0, 0) or=1
D/ViewRootImpl@ca0894c[Toast]: mHardwareRenderer.destroy()#4
D/ViewRootImpl@ca0894c[Toast]: dispatchDetachedFromWindow
D/InputTransport: Input channel destroyed: fd=94
D/ViewRootImpl@589584e[MainActivity]: ViewPostImeInputStage processPointer 0
D/ViewRootImpl@589584e[MainActivity]: ViewPostImeInputStage processPointer 1
I/art: Do partial code cache collection, code=249KB, data=240KB
I/art: After code cache collection, code=239KB, data=234KB
I/art: Increasing code cache capacity to 1024KB
D/AbsListView:  in onLayout changed 
D/ViewRootImpl@589584e[MainActivity]: ViewPostImeInputStage processPointer 0
D/ViewRootImpl@589584e[MainActivity]: ViewPostImeInputStage processPointer 1
D/AbsListView: onTouchUp() mTouchMode : 0
D/TextView: setTypeface with style : 0
D/TextView: setTypeface with style : 0
D/ViewRootImpl@c74834e[MainActivity]: ThreadedRenderer.create() translucent=true
D/InputTransport: Input channel constructed: fd=93
D/ViewRootImpl@c74834e[MainActivity]: setView = DecorView@30699d2[MainActivity] touchMode=true
I/Choreographer: Skipped 33 frames!  The application may be doing too much work on its main thread.
D/ViewRootImpl@c74834e[MainActivity]: dispatchAttachedToWindow
D/ViewRootImpl@c74834e[MainActivity]: Relayout returned: oldFrame=[0,0][0,0] newFrame=[32,514][1047,1477] result=0x27 surface={isValid=true -593956864} surfaceGenerationChanged=true
D/mali_winsys: EGLint new_window_surface(egl_winsys_display*, void*, EGLSurface, EGLConfig, egl_winsys_surface**, egl_color_buffer_format*, EGLBoolean) returns 0x3000,  [1207x1155]-format:1
D/ViewRootImpl@c74834e[MainActivity]: mHardwareRenderer.initialize() mSurface={isValid=true -593956864} hwInitialized=true
D/ViewRootImpl@c74834e[MainActivity]: MSG_WINDOW_FOCUS_CHANGED 1
D/ViewRootImpl@c74834e[MainActivity]: mHardwareRenderer.initializeIfNeeded()#2 mSurface={isValid=true -593956864}
V/InputMethodManager: Starting input: tba=android.view.inputmethod.EditorInfo@9f1dc6f nm : com.example.hosse.myapplication ic=null
I/InputMethodManager: [IMM] startInputInner - mService.startInputOrWindowGainedFocus
D/InputTransport: Input channel constructed: fd=90
Input channel destroyed: fd=91
I/Choreographer: Skipped 33 frames!  The application may be doing too much work on its main thread.
D/ViewRootImpl@c74834e[MainActivity]: MSG_RESIZED_REPORT: frame=Rect(32, 514 - 1047, 1477) ci=Rect(0, 0 - 0, 0) vi=Rect(0, 0 - 0, 0) or=1
D/ViewRootImpl@589584e[MainActivity]: MSG_WINDOW_FOCUS_CHANGED 0
D/ViewRootImpl@c74834e[MainActivity]: ViewPostImeInputStage processPointer 0
D/ViewRootImpl@c74834e[MainActivity]: ViewPostImeInputStage processPointer 1
D/ViewRootImpl@c74834e[MainActivity]: mHardwareRenderer.destroy()#4
D/ViewRootImpl@c74834e[MainActivity]: dispatchDetachedFromWindow
D/InputTransport: Input channel destroyed: fd=93
D/ViewRootImpl@589584e[MainActivity]: MSG_WINDOW_FOCUS_CHANGED 1
D/ViewRootImpl@589584e[MainActivity]: mHardwareRenderer.initializeIfNeeded()#2 mSurface={isValid=true -609390592}
V/InputMethodManager: Starting input: tba=android.view.inputmethod.EditorInfo@9fedd7c nm : com.example.hosse.myapplication ic=null
I/InputMethodManager: [IMM] startInputInner - mService.startInputOrWindowGainedFocus
D/InputTransport: Input channel constructed: fd=93
Input channel destroyed: fd=90
E/ViewRootImpl: sendUserActionEvent() returned.
D/ViewRootImpl@2e84605[Toast]: ThreadedRenderer.create() translucent=true
D/InputTransport: Input channel constructed: fd=91
D/ViewRootImpl@2e84605[Toast]: setView = android.widget.LinearLayout{f19865a V.E...... ......I. 0,0-0,0} touchMode=true
D/ViewRootImpl@2e84605[Toast]: dispatchAttachedToWindow
D/ViewRootImpl@2e84605[Toast]: Relayout returned: oldFrame=[0,0][0,0] newFrame=[310,1596][769,1728] result=0x27 surface={isValid=true -593956864} surfaceGenerationChanged=true
D/mali_winsys: EGLint new_window_surface(egl_winsys_display*, void*, EGLSurface, EGLConfig, egl_winsys_surface**, egl_color_buffer_format*, EGLBoolean) returns 0x3000,  [459x132]-format:1
D/ViewRootImpl@2e84605[Toast]: mHardwareRenderer.initialize() mSurface={isValid=true -593956864} hwInitialized=true
D/ViewRootImpl@2e84605[Toast]: MSG_RESIZED_REPORT: frame=Rect(310, 1596 - 769, 1728) ci=Rect(0, 0 - 0, 0) vi=Rect(0, 0 - 0, 0) or=1
D/ViewRootImpl@2e84605[Toast]: mHardwareRenderer.destroy()#4
D/ViewRootImpl@2e84605[Toast]: dispatchDetachedFromWindow
D/InputTransport: Input channel destroyed: fd=91
V/MainActivity: threejava.net.BindException: Address already in use
D/TextView: setTypeface with style : 0
D/ViewRootImpl@7903a0a[MainActivity]: ThreadedRenderer.create() translucent=true
D/InputTransport: Input channel constructed: fd=91
D/ViewRootImpl@7903a0a[MainActivity]: setView = DecorView@94082e2[MainActivity] touchMode=true
V/MainActivity: sixjava.net.SocketException: Broken pipe
D/ViewRootImpl@7903a0a[MainActivity]: dispatchAttachedToWindow
D/ViewRootImpl@7903a0a[MainActivity]: Relayout returned: oldFrame=[0,0][0,0] newFrame=[32,253][1047,1738] result=0x27 surface={isValid=true -593956864} surfaceGenerationChanged=true
D/ViewRootImpl@7903a0a[MainActivity]: mHardwareRenderer.initialize() mSurface={isValid=true -593956864} hwInitialized=true
D/mali_winsys: EGLint new_window_surface(egl_winsys_display*, void*, EGLSurface, EGLConfig, egl_winsys_surface**, egl_color_buffer_format*, EGLBoolean) returns 0x3000,  [1207x1677]-format:1
D/ViewRootImpl@7903a0a[MainActivity]: MSG_WINDOW_FOCUS_CHANGED 1
D/ViewRootImpl@7903a0a[MainActivity]: mHardwareRenderer.initializeIfNeeded()#2 mSurface={isValid=true -593956864}
V/InputMethodManager: Starting input: tba=android.view.inputmethod.EditorInfo@17e88f1 nm : com.example.hosse.myapplication ic=null
I/InputMethodManager: [IMM] startInputInner - mService.startInputOrWindowGainedFocus
D/InputTransport: Input channel constructed: fd=95
Input channel destroyed: fd=93
D/ViewRootImpl@7903a0a[MainActivity]: MSG_RESIZED_REPORT: frame=Rect(32, 253 - 1047, 1738) ci=Rect(0, 0 - 0, 0) vi=Rect(0, 0 - 0, 0) or=1
D/ViewRootImpl@589584e[MainActivity]: MSG_WINDOW_FOCUS_CHANGED 0
V/MainActivity: sixjava.net.SocketException: Broken pipe

在您提供的示例中,来自服务器的问题是由以下行中的 BindException 引起的:

serverSocket = new ServerSocket(8888);

异常处理不当,导致该函数中的其余逻辑被跳过。这给您留下了一个旧 SendRecieve,其中包含您上一场游戏遗留下来的旧 Socket。当您尝试写入此 Socket 时,您自然会遇到 "broken pipe" 异常。

建议的修复

使用 Exception.printStackTrace() 以便日志文件中的异常 "stand out" 更好,and/or 使用 Log.e()(我假设您没有注意到 "three" 打印出来,这就是导致这个问题的原因)。更好的异常处理逻辑是必要的;如果 ServerSocket()accept() 调用发生异常,则应分别处理绑定错误或网络 I/O 问题;也许通过通知用户,但至少通过改变程序状态,使播放无法进行。

首先要避免 BindException,您有两个选择:

  1. 允许单个 ServerSocket 在您的应用程序的生命周期内持续存在。这将需要您进行一些重新架构。

  2. 绑定前在 ServerSocket 上设置 SO_REUSEADDR 选项,例如:

...

serverSocket = new ServerSocket();
serverSocket.setReuseAddress(true);
serverSocket.bind(8888);

...

使用选项 2 时,请注意在服务器游戏结束后关闭旧 ServerSocket

仅仅因为您设置了 SO_REUSEADDR 并不意味着您仍然无法获得 BindException!您无法控制的其他一些应用程序可能已打开该端口。

以后避免这种情况的提示

当您 运行 遇到这样的问题时,最好 仔细检查您的所有假设 ;在这种情况下,假设正在执行哪些行,以及 "new" 你的 Socket 是如何执行的。更多日志记录可能会有所帮助。你可能想在每个 try 块的末尾放置一个记录器——类似于 "Server socket created!" 或 "connection accepted!"。这有助于让您确信事情正在按照您期望的方式执行。此外,由于您知道 "broken pipe" 错误涉及 Socket 生命周期,因此您可以在每个 SendReceive() 中转储 socket.toString() 的值。如果字符串没有改变,您就会知道您的 Socket 仍然是旧的。你不一定知道为什么,但你仍然有一个重要的线索。

编辑

另一个好技巧:null 处理完参考文献后。特别是在 Sockets 的情况下;在你 close() 他们之后,他们就没什么用了。这也有助于避免这个问题。