NetworkStream изключения и кодове за връщане

Опитвам се да се образовам за тънкостите на четенето от NetworkStream и разбирането на различните начини, по които могат да възникнат проблеми. Имам следния код:

    public async Task ReceiveAll()
    {
        var ns = this.tcp.GetStream();
        var readBuffer = new byte[1000];
        while (true)
        {
            int bytesRead;
            try
            {
                bytesRead = await ns.ReadAsync(readBuffer, 0, readBuffer.Length);
                if (bytesRead == 0)
                {
                    // Remote disconnection A?
                    break;
                }
            }
            catch (IOException)
            {
                // Remote disconnection B?
                break;
            }
            catch (ObjectDisposedException)
            {
                // Local disconnection?
                break;
            }

            /*Do something with readBuffer */
        }
    }

Отбелязах три точки в кода, където програмата казва „нещо се е объркало, няма смисъл да продължавам“.

„Локалното прекъсване на връзката“ не е точно нещо нередно, ще се случи, когато затворя локално гнездото, което е единственият начин да изляза от цикъла при нормални обстоятелства. Не мисля, че нещо друго може да причини това, така че мисля, че е безопасно просто да преглътна изключението.

Двете точки за „Дистанционно изключване“ са това, за което не съм сигурен. Знам, че ReadAsync ще върне 0, ако връзката е прекратена дистанционно (A), но IOException изглежда също се задейства при някои обстоятелства. Ако моят отдалечен клиент е C# конзола, тогава затварянето на сокета изглежда прави „A“ случващо се, а затварянето на прозореца на конзолата изглежда прави „B“ случващо се. Не съм сигурен, че разбирам каква е разликата между тези сценарии?

И накрая, малко общ въпрос, но има ли нещо явно нередно с този код или моите горни предположения?

Благодаря.

РЕДАКТИРАНЕ: В отговор на моето използване на ObjectDisposedException за прекъсване на цикъла:

Ето как изглежда моят метод „Стоп“ (от същия клас като по-горе):

    public void Stop()
    {
        this.tcp.Close();
    }

Това кара чакащия „ReadAsync“ да се изключи с ObjectDisposedException. AFAIK няма друг начин да се прекъсне това. Промяна на това на:

    public void Stop()
    {
        this.tcp.Client.Shutdown(SocketShutdown.Both);
    }

Изглежда всъщност не прави нищо с чакащото повикване, просто продължава да чака.


person Barguast    schedule 12.02.2015    source източник


Отговори (1)


Когато NetworkStream върне 0, това означава, че сокетът е получил пакет за прекъсване на връзката от отдалечената страна. Ето как трябва да завършват мрежовите връзки.

Правилният начин за прекратяване на връзката (особено ако сте в пълнодуплексен разговор) е да се обадите на socket.Shutdown(SocketShutdown.Send) и да дадете известно време на отдалечената страна да затвори своя канал за изпращане. Това гарантира, че получавате всички чакащи данни, вместо да прекъсвате връзката. ObjectDisposedException никога не трябва да бъде част от нормалния поток на приложението.

Всички хвърлени изключения показват, че нещо се е объркало и мисля, че е безопасно да се каже, че вече не можете да разчитате на текущата връзка.

TL;DR

Не виждам нищо лошо във вашия код, но (особено при пълна дуплексна комуникация) бих изключил канала за изпращане и бих изчакал 0-байтов пакет, за да предотвратя получаването на ObjectDisposedExceptions по подразбиране:

  1. използвайте tcp.Shutdown(SocketShutdown.Send), за да кажете на отдалечената страна, че искате да прекъснете връзката

  2. вашата верига все още може да получава данни, които отдалечената страна е изпращала

  3. вашият цикъл, ако всичко е наред, ще получи 0-байтов пакет, показващ, че отдалечената страна прекъсва връзката

  4. цикълът завършва по правилния начин

  5. може да искате да решите да изхвърлите сокета след определен период от време, ако не сте получили 0-байтовия пакет

person C.Evenhuis    schedule 12.02.2015
comment
Благодаря. Манипулаторът ObjectDisposedException е само там, тъй като не мисля, че има друг начин за „анулиране“ на ReadAsync, когато този цикъл работи, без да изхвърляте гнездото. Ако „Отдалечено прекъсване на връзката A“ е, защото отдалеченият клиент „чисто“ се изключва, тогава какво конкретно причинява B - т.е. как локалният клиент знае, че отдалеченият клиент е изчезнал? - person Barguast; 12.02.2015
comment
В отговор на вашата редакция: Бих използвал изключване в две стъпки, за да предотвратя получаването на ObjectDisposedExceptions по подразбиране.. Можете ли да разясните това? - person Barguast; 12.02.2015
comment
@Barguast: можете да предотвратите ObjectDisposedException, ако Shutdown() при четене = 0. - person jgauffin; 12.02.2015
comment
@Barguast ситуация B се случва, когато е възникнал откриваем проблем или отдалечената страна не е отговорила за период от време, конфигуриран в операционната система - person C.Evenhuis; 12.02.2015
comment
@jgauffin Мисля, че този проблем възникна, когато локалната страна иска да се изключи - person C.Evenhuis; 12.02.2015
comment
@C.Evenhuis: Същото и там. Използвайте Shutdown() вместо Close(). Тогава четенето също ще получи 0. ObjectDisposedException е възможно само ако разработчикът не изчака грациозно изключване. - person jgauffin; 12.02.2015
comment
Не съм сигурен дали се обясних правилно. Горното е „сървърен код“ и идва, след като е направена отдалечена връзка и сървърът трябва да обработи всички изпратени данни. Сървърът може да бъде спрян от потребителя, в който момент трябва да кажа на тези цикли (и техните чакащи ReadAsync-и) да спрат. Не мисля, че мога да направя нещо на сървъра, за да направя това, освен да изхвърля сокета? - person Barguast; 12.02.2015
comment
@Barguast: Защо мислиш, че не можеш да използваш Shutdown() от страната на сървъра и след това да изчакаш Read() да получи 0 прочетени байта? - person jgauffin; 12.02.2015
comment
@jgauffin - Актуализирах отговора си, за да разработя подробно, но по принцип изглежда не прави нищо, ако извикам „tcp.Client.Shutdown“, докато ReadAsync чака. - person Barguast; 12.02.2015
comment
@Barguast, защото вие също затваряте канала Receive. Използвайте SocketShutdown.Send, за да можете да получите потвърждението за прекъсване на връзката. - person C.Evenhuis; 12.02.2015
comment
@C.Evenhuis - Промяна на метода Stop за извикване на 'this.tcp.Client.Shutdown(SocketShutdown.Send);' също така не изглежда да кара ReadAsync да се връща (или освен). - person Barguast; 12.02.2015
comment
Трябва да кажа на тези цикли (и техните чакащи ReadAsync-и) да спрат -- ако вашите клиенти се държат добре, тогава, когато извикате Shutdown(SocketShutdown.Send), те ще видят 0-байтовото получаване от своя страна и след като приключат с изпращането каквото и да е, те ще извикат Shutdown(SocketShutdown.Both) (или еквивалент на тяхната платформа) и вашият сървър след това ще види завършване на получаване от 0 байта. По желание можете да стартирате таймер и след това да затворите сокета, ако смятате, че клиентът не е отговорил навреме (т.е. не се държи добре). Но това не е нормалният сценарий, дори ако трябва да се поддържа - person Peter Duniho; 13.02.2015
comment
@PeterDuniho - Определено не мога да разчитам, че клиентите се държат добре. Моят сценарий в реалния свят е различни видове мобилно оборудване, свързано към моя сървър през GPRS, така че всички залози са изключени по отношение на свързаността и поведението. - person Barguast; 13.02.2015
comment
@Barguast Трудно ми е да определя каква информация все още смятате, че липсва. Сценарият с добро поведение се повтаря няколко пъти, като също така разгледахме възможността клиентите да не се държат добре. От вас зависи да решите да затворите връзката или да дадете на всеки клиент известно време, за да затвори правилно връзката. - person C.Evenhuis; 13.02.2015
comment
@C.Evenhuis - останах с впечатлението, че jgauffin ми казваше, че ако направя Socket.Shutdown на сървъра (т.е. по време на спиране, вместо да извикам Close), тогава ReadAsync ще излезе, като върне 0 незабавно, вместо да хвърли ObjectDisposedException както прави Клоуз. Изглежда, че това не е така. Нямам проблем с извикването на Close и плиткото изключение, ако приемем, че няма странични ефекти, но първоначалният ви отговор показа, че това е лош начин да го направите. - person Barguast; 13.02.2015
comment
@Barguast След Socket.Shutdown(Send) се изпраща 0-байтов пакет до отдалечената страна и когато отдалечената страна отговори правилно, ще прочетете 0 - така че това определено не е незабавно. Когато затваряте връзката чрез Socket.Close(), може да пропуснете последното входящо съобщение, ако е било изпратено по времето, когато сте затваряли връзката. - person C.Evenhuis; 13.02.2015
comment
Добре, мисля, че в този случай трябва да приема, че клиентите няма да се държат предвидимо (вижте коментара ми малко по-нагоре). Благодаря, че отделихте време да преминете през това с мен. - person Barguast; 13.02.2015
comment
@Barguast: това е добре, но все пак първо трябва да им дадете предимството на съмнението. Дори ако някои клиенти няма да се държат добре, трябва да започнете с предположението, че ще го направят, така че вашият код да е правилен. Използвайте Shutdown(SocketShutdown.Send), за да посочите на отдалечената крайна точка, че сте приключили с изпращането, и след това не затваряйте гнездото, докато получаването ви не завърши с дължина 0 байта. Можете също така да добавите към това таймер, който използвате, за да определите кога да затворите сокета, ако и само ако отдалечената крайна точка не се изключи навреме. - person Peter Duniho; 13.02.2015