Соединение WebSocket обрывается перед onmessage

Я написал клиент WebSocket на Javascript, который подключается и пожимает руку C# моему серверу. В настоящее время, после «сброса» моего выходного потока (только после того, как я отправил заголовок BACK клиенту и подтвердил свое соединение), клиент дает сбой и генерирует исключение на стороне сервера, когда я пытаюсь написать в следующий раз. Последнее поведение ожидается, однако я не могу понять, почему при сбросе соединение прерывается. Я использую TcpListener и Socket с StreamWriter на стороне сервера и простой WebSocket на клиенте.

Что действительно сбивает с толку в этом сценарии, так это то, что во время передачи «рукопожатия» текст может передаваться в обе стороны, и сброс выполняется после отправки каждой строки, но как только рукопожатие завершено, сброс разрывает соединение.

Скажите, пожалуйста, если в этой ревизии недостаточно информации; так как он был изменен несколько раз сейчас.

Заранее спасибо.

Клиентский Javascript:

<!DOCTYPE html>  
<meta charset="utf-8" />
<html>
<head>
<script language="javascript" type="text/javascript">
    var wsUri = "ws://127.0.0.1:9002/cc";
    var output;
    var websocket = null;

    function init()
    {
        StartWebSocket();
    }

    function StartWebSocket()
    {
        output = document.getElementById("output");
        writeToScreen("#WebSocket Starting");
        websocket = new WebSocket(wsUri,"lorem.ipsum.com");
        writeToScreen("#WebSocket Instantiated");
        websocket.removeEventListener("open",onOpen,false);
        websocket.addEventListener("open",onOpen,false);

        websocket.removeEventListener("close",onClose,false);
        websocket.addEventListener("close",onClose,false);

        websocket.removeEventListener("message",onMessage,false);
        websocket.addEventListener("message",onMessage,false);

        websocket.removeEventListener("error",onError,false);
        websocket.addEventListener("error",onError,false);

        writeToScreen("#WebSocket Events Attached");
    }

    function onOpen(evt)
    {
        try
        {
            writeToScreen("#WebSocket Connection Established");
            writeToScreen("#WebSocket BinaryType: " + websocket.binaryType);
            writeToScreen("#WebSocket Protocol: " + websocket.protocol);
            writeToScreen("#WebSocket Extensions: " + websocket.extensions);
            doSend("TestOutput\r\n\r");
        }
        catch( e )
        {
            writeToScreen(e);   
        }
    }

    function onClose(evt)
    {
        writeToScreen("#WebSocket Connection Aborted:");
        writeToScreen("&nbsp;&nbsp;&nbsp;&nbsp;Reason: " + evt.code );
        writeToScreen("&nbsp;&nbsp;&nbsp;&nbsp;Reason: " + evt.reason );
        writeToScreen("&nbsp;&nbsp;&nbsp;&nbsp;Clean: " + evt.wasClean);
    }

    function onMessage(evt)
    {
        writeToScreen("#WebSocket Message Event");
        try
        {
            writeToScreen("<span style=\"color: blue;\">#WebSocket Server Message: " + evt.data+"</span>");
        }
        catch( e )
        {
            writeToScreen(e);
        }
    }

    function onError(evt)
    {
        writeToScreen("<span style=\"color: red;\">#WebSocket Error:</span> " + evt.data);
    }

    function doSend(message)
    {
        try
        {
            websocket.send(message);
            writeToScreen("#WebSocket Output Written to Server: " + message);
        }
        catch( e ) 
        {
            writeToScreen(e);
        }
    }

    function writeToScreen(message)
    {
        try
        {
            var pre = document.createElement("a");
            pre.style.wordWrap = "break-word";
            pre.innerHTML = message + "<br>";
            output.appendChild(pre);
        }
        catch( e )
        {
            writeToScreen(e);
        }
    }

    window.addEventListener("load", init, false);

</script>
</head>
<body>
<div id="output"></div>
</body>
</html>

Предложение рукопожатия, которое я получаю от своего клиента, выглядит следующим образом:

GET /cc HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: 127.0.0.1:9002
Origin: http://localhost
Sec-WebSocket-Key: icajBpkAfgA+YbVheBpDsQ==
Sec-WebSocket-Version: 13

Я интерпретирую рукопожатие так:

public override void Interpret(string Argument)
{
    if (String.IsNullOrEmpty(Argument))
    {
        return;
    }
    else
    {
        if( !HeaderFinished )
        {
            if (!HeaderStarted)
            {
                if (Argument.StartsWith("GET /"))
                {
                    this.Role = "client";
                    HeaderStarted = true;
                    this.Server.Print("Connection at " + this.Address + " set to client.");
                }
                else
                {
                    return;
                }
            }
            else
            {
                if (Argument.StartsWith("Sec-WebSocket-Key:"))
                {
                    this.Key = Argument.Split(' ')[1].TrimEnd().TrimStart();
                    return;
                }
                else if (Argument.StartsWith("Sec-WebSocket-Version:"))
                {
                    this.HeaderFinished = true;
                    this.WriteHeaderResponse();
                    HeaderSent = true;
                    return;
                }
            }
        }
        else
        {
            this.InterpretMessage(DecodeMessage(Argument));
            return;
        }
    }
}

Отправить мой ответ заголовка:

public void WriteHeaderResponse()
{
    this.WriteLine("HTTP/1.1 101 Switching Protocols");
    this.WriteLine("Upgrade: websocket");
    this.WriteLine("Connection: Upgrade");
    String NewKey = ComputeResponseKey(this.Key);
    this.WriteLine("Sec-WebSocket-Accept: " + NewKey);
    this.WriteLine("Sec-WebSocket-Protocol: lorem.ipsum.com");
    this.WriteLine("\r\n");
}

И получите следующий вывод от клиента (на данный момент):

#WebSocket Starting
#WebSocket Instantiated
#WebSocket Events Attached
#WebSocket Connection Established
#WebSocket BinaryType: blob
#WebSocket Protocol: lorem.ipsum.com
#WebSocket Extensions: 
#WebSocket Output Written to Server: TestOutput

В этот момент, если я попытаюсь выполнить следующий метод на стороне сервера, клиент отключится следующим образом:

#WebSocket Connection Aborted:
    Reason: 1006
    Reason: 
    Clean: false

Код сообщения: -Взято из чего-то, что я нашел в сети, немного изменен...

public void WriteMessage(byte[] Payload)
{
    byte[] Message;
    int Length = Payload.Length;
    int MaskLength = 4;

    if (Length < 126)
    {
        Message = new byte[2 + MaskLength + Length];
        Message[1] = (byte)Length;
    }
    else if (Length < 65536)
    {
        Message = new byte[4 + MaskLength + Length];
        Message[1] = (byte)126;
        Message[2] = (byte)(Length / 256);
        Message[3] = (byte)(Length % 256);
    }
    else
    {
        Message = new byte[10 + MaskLength + Length];
        Message[1] = (byte)127;

        int left = Length;
        int unit = 256;

        for (int i = 9; i > 1; i--)
        {
            Message[i] = (byte)(left % unit);
            left = left / unit;

            if (left == 0)
                break;
        }
    }

    //Set FIN
    Message[0] = (byte)129;// (0 | 0x80);

    //Set mask bit
    //Message[1] = (byte)(Message[1] | 0x80);

    //GenerateMask(Message, Message.Length - MaskLength - Length);

    //if (Length > 0)
        //MaskData(Payload, 0, Length, Message, Message.Length - Length, Message, Message.Length - MaskLength - Length);

    char[] output = new char[Message.Length-4];

    for( int i = 0, y = 0, z = 0; i < Message.Length; i++ )
    {
        if (Message[z] == '\0')
        {
            if (Payload.Length > i-z)
                output[i] = (char)Payload[y++];
        }
        else
        {
            output[i] = (char)Message[z++];
        }
    }

    this.OutputWriter.Write(output, 0, output.Length);
    this.OutputWriter.Flush();
}

ОБНОВЛЕНИЕ: я только что заменил весь код в этом документе своим самым последним.

Подвести итоги:

- The client-server handshake has been matched on both sides.
- A path has been defined in the URI for the WebSocket.
- Data packets are now being 'properly' framed.

Один момент, который я заметил только при редактировании этого, — это последние несколько строк моего метода WriteMessage. Выполнив все свои кадры, я преобразовываю свой массив байтов в массив символов и использую StreamReader.Write для его отправки. Я не уверен, является ли это жизнеспособным решением или нет, поэтому, пожалуйста, проверьте меня, если это не так.

В противном случае я сбит с толку. Это, кажется, соответствует всем стандартам, о которых я что-либо читал, но все же с треском меня подводит. Это довольно выгодно, если я смогу заставить его работать, поэтому я действительно ценю чью-либо помощь.

Спасибо. -DigitalJedi фейспалм


person DigitalJedi805    schedule 06.04.2012    source источник
comment
Итак, вы написали клиентскую и серверную часть? можете ли вы предоставить код или какое рукопожатие вы выполняете и как вы это делаете?   -  person Legy    schedule 07.04.2012
comment
Абсолютно - я отредактирую это прямо сейчас.   -  person DigitalJedi805    schedule 07.04.2012
comment
Если есть что-то внутри этого, что вам нужно уточнить, пожалуйста, дайте мне знать.   -  person DigitalJedi805    schedule 07.04.2012
comment
как насчет TcpListener? вы ловите новое входящее соединение, получаете новый tcpclient... Вы делаете это в новом потоке? а как насчет того, чтобы попытаться поймать там, вы запрашиваете данные у потока... Как вы справились с этим, когда клиент отказывается от соединения? а где вы вычисляете и ду рукопожатие на стороне клиента?   -  person Legy    schedule 07.04.2012
comment
Я могу вставить код соединителя, если хотите, но слушатель получает свой собственный поток, и каждое новое соединение получает свой собственный поток. Я пытаюсь/ловлю внутри кода принятия, и мне еще не приходилось сталкиваться с исключением. Что касается клиента, рукопожатие отправляется при создании экземпляра объекта, и как только я получаю последнюю строку рукопожатия, я возвращаю свой ответ. Я опубликую другое редактирование.   -  person DigitalJedi805    schedule 07.04.2012
comment
можете ли вы предоставить дополнительную информацию о том, какие ошибки или исключения вы получаете с обеих сторон?   -  person Legy    schedule 07.04.2012
comment
Соединение вызывает исключение IOException со стороны сервера, как только я выполняю сброс. «Соединение было сброшено удаленным хостом» или что-то в этом роде. Клиент возвращает код 1006, для которого я не могу найти толкового определения. Еще раз спасибо, и если этого все еще недостаточно, я уверен, что смогу немного глубже изучить исключение сервера.   -  person DigitalJedi805    schedule 11.04.2012
comment
Нет больше мыслей @Kerwindena?   -  person DigitalJedi805    schedule 25.04.2012


Ответы (1)


Проблема вызвана использованием Sec-WebSocket-Protocol в ответе на рукопожатие. Клиент не запрашивал подпротокол, поэтому единственным допустимым ответом сервера является завершение рукопожатия без указания подпротокола. Если сервер отвечает неожиданным подпротоколом, клиент должен закрыть соединение. См. раздел /subprotocol/ в разделе 4.2.2 RFC 6455. для деталей.

Самое простое решение — удалить заголовок Sec-WebSocket-Protocol из вашего ответа. Если вы хотите сохранить его, вам нужно передать имя подпротокола в качестве второго аргумента конструктору WebSocket клиента и использовать этот подпротокол в ответе вашего сервера. Дополнительные сведения см. в документации по client API.

РЕДАКТИРОВАТЬ:
После того, как вы завершили рукопожатие, сервер, вполне возможно, не сможет прочитать сообщение «TestOutput» из клиентского onOpen. Сообщения WebSocket не являются обычным текстом и не используют HTTP, поэтому крайне маловероятно, что строка this.ReadLine() найдет \r\n для завершения. Дополнительные сведения см. в разделе спецификации фрейминга данных. Этот вики-пост содержит некоторый полезный псевдокод для чтения/записи через веб-сокет. Или вы можете попробовать мой сервер C++. См. WsProtocol80::Read(), как читать сообщения. Или посмотрите на один из серверов C# с открытым исходным кодом, например Fleck (код для чтения/записи сообщений связан).

Есть некоторые другие небольшие изменения, которые вы могли бы рассмотреть, чтобы сделать ваш код более надежным, но не будет иметь значения между немедленным прохождением и неудачей:

  • Любой указанный вами подпротокол в идеале должен включать ваше доменное имя, чтобы свести к минимуму вероятность случайного совпадения с любым запросом несовместимого протокола. В раннем разделе RFC 6455 объясняется, почему.
  • Было бы целесообразно проверить наличие и значение заголовка Sec-WebSocket-Protocol в запросе, прежде чем отвечать поддерживаемым подпротоколом.
  • Порядок заголовков в клиентском запросе не гарантируется, поэтому вы можете отложить ответ до тех пор, пока не прочитаете пустую строку.
person simonc    schedule 17.04.2012
comment
Может быть, мое конечное решение. Я собираюсь использовать это и передать это как ответ. Спасибо. Это, возможно, только что избавило меня от головной боли. - person DigitalJedi805; 18.04.2012
comment
К сожалению, даже после комментирования этой строки моего ответа я все равно заканчиваю с тем же результатом. Если у вас нет дополнительных советов, я, скорее всего, заменю это ответом из-за того, что он кажется настолько близким, насколько я собираюсь получить, но, к сожалению, я все еще в той же лодке. - person DigitalJedi805; 18.04.2012
comment
@ DigitalJedi805 А, я только что заметил еще одну проблему. Я обновлю свой ответ через секунду - person simonc; 18.04.2012
comment
Очень ценил Саймона. Я посмотрю на это и обязательно дам вам знать, как это происходит. Я не уверен, что у меня будет шанс до того, как истечет награда за это, так что это твое; просто оставайтесь со мной на линии еще пару дней, на случай, если я не уложусь; если вы не возражаете. - person DigitalJedi805; 18.04.2012
comment
Привет, Саймон, я внес пару изменений, как это было предписано, но до сих пор не удалось это исправить. Любой совет в этот момент был бы замечательным. Вы уже оказали большую помощь, в любом случае. Я изменил большую часть моего исходного сообщения, чтобы включить изменения. - person DigitalJedi805; 25.04.2012
comment
Какие изменения вы внесли и в какой момент теперь происходит сбой? Если изменения достаточно обширны, чтобы оправдать новый вопрос, я замечу его, если в нем есть ключевое слово websocket. - person simonc; 25.04.2012
comment
Ну, я удалил ответ протокола с сервера, обрамил свои данные и включил путь в свой uri веб-сокета, но он все еще ломается в том же месте. - person DigitalJedi805; 25.04.2012
comment
Я уже отредактировал весь свой оригинал. Я только что сделал еще одну модификацию и получил соответствующие протоколы; клиент-сервер и обратно - все равно получаю соединение и вылетаю, как только пишу. - person DigitalJedi805; 25.04.2012
comment
Сообщения от клиента (javascript) на ваш сервер будут обрамлены/замаскированы. Ваша функция InterpretMessage справляется с этим? (Я не могу найти код для этого.) Кроме того, сообщения от сервера к клиенту не должны быть замаскированы, поэтому над вашей функцией WriteMessage нужно немного поработать. (Однако фрейминг здесь по-прежнему актуален; не выбрасывайте весь код!) - person simonc; 25.04.2012
comment
У меня есть метод «DecodeMessage» на стороне сервера, который я еще не прибил, но в данном случае это относительно неважно, поскольку я даже не собираюсь иметь поле ввода на стороне клиента. Я украл из сети метод WriteMessage вместе с двумя сопутствующими методами. Я извлеку маскировку и посмотрю, что произойдет. Спасибо, что остаетесь со мной. - person DigitalJedi805; 26.04.2012
comment
давайте продолжим это обсуждение в чате - person simonc; 26.04.2012