Почему создание второго соединения после завершения первого приводит к исключению из-за разорванного канала? - PullRequest
0 голосов
/ 29 января 2019

В моем приложении два человека на разных устройствах подключаются через Wi-Fi одноранговое соединение.Когда они подключены, одному пользователю назначается хост », а другому -« клиент ».

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 для отправки каждого из них.другие данные, которые получены через обработчик. Все это прекрасно работает

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

1 Ответ

0 голосов
/ 30 января 2019

В приведенном вами примере с сервера проблема вызвана BindException в этой строке:

serverSocket = new ServerSocket(8888);

Исключение обрабатывается неправильно, в результате чего остальная логика в этой функциибыть пропущенным.Это оставляет вам старый SendRecieve, который содержит старый Socket, оставшийся от вашей последней игры.Когда вы пытаетесь записать в это Socket, естественно, вы получаете исключение «сломанная труба».

ПРЕДЛАГАЕМЫЕ ИСПРАВЛЕНИЯ

Используйте Exception.printStackTrace(), чтобы исключения "встали"out "лучше в файле журнала и / или используйте Log.e() (я предполагаю, что вы не заметили распечатку" трех ", что и привело к этой проблеме).Необходима лучшая логика обработки исключений;если возникает исключение в вызовах ServerSocket() или accept(), вы должны обработать ошибку привязки или проблему сетевого ввода-вывода соответственно;возможно, уведомив пользователя, но, по крайней мере, изменив состояние программы так, чтобы воспроизведение не могло продолжаться.

Во-первых, чтобы избежать BindException, у вас есть два варианта:

  1. Разрешить сохранение одного ServerSocket в течение всего срока службы вашего приложения.Это потребует некоторой ре-архитектуры с вашей стороны.

  2. Установите параметр SO_REUSEADDR на ServerSocket перед привязкой, например:

...

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

...

При использовании варианта 2 позаботьтесь о том, чтобы закрыть старый ServerSocket после завершения игры на сервере.

Только потому, что выустановка SO_REUSEADDR не означает, что вы все еще не можете получить BindException!В некоторых других приложениях, которые вы не контролируете, этот порт может быть открыт.

СОВЕТЫ, ЧТОБЫ ИЗБЕГАТЬ ЭТОГО В БУДУЩЕМ

Когда вы столкнетесь с такой проблемой, это хорошая идея, чтобы тщательно перепроверить все ваши предположения ;в этом случае предположения о том, какие строки выполнялись и насколько «новым» был ваш Socket.Еще некоторые записи могут помочь.Возможно, вы захотите поставить регистратор в конце каждого блока try - что-то вроде «Серверный сокет создан!»или "соединение принято!"Это дает вам уверенность в том, что все работает так, как вы ожидаете.Кроме того, поскольку вы знали, что ошибка «сломанная труба» связана с жизненным циклом Socket, вы могли сбросить значение socket.toString() в каждом SendReceive().Если бы строки не изменились, вы бы знали, что ваш Socket был все еще старым.Вы не знаете почему , обязательно, но у вас все еще будет важная подсказка.

РЕДАКТИРОВАТЬ

Еще одна хорошая техника: null ваши ссылки после того, как вы сделали с ними.Особенно в случае Sockets;после того, как вы close() их, они очень мало полезны.Это также помогло бы избежать этой проблемы.

...