Создание примера WebSocket" Hello World"
Я не понимаю, почему я не могу сделать следующий код работать. Я хочу подключиться с помощью JavaScript к моему консольному приложению сервера. А затем отправить данные на сервер.
вот код сервера:
static void Main(string[] args)
{
TcpListener server = new TcpListener(IPAddress.Parse("127.0.0.1"), 9998);
server.Start();
var client = server.AcceptTcpClient();
var stream = client.GetStream();
while (true)
{
var buffer = new byte[1024];
// wait for data to be received
var bytesRead = stream.Read(buffer, 0, buffer.Length);
var r = System.Text.Encoding.UTF8.GetString(buffer);
// write received data to the console
Console.WriteLine(r.Substring(0, bytesRead));
}
}
и вот JavaScript:
var ws = new WebSocket("ws://localhost:9998/service");
ws.onopen = function () {
ws.send("Hello World"); // I WANT TO SEND THIS MESSAGE TO THE SERVER!!!!!!!!
};
ws.onmessage = function (evt) {
var received_msg = evt.data;
alert("Message is received...");
};
ws.onclose = function () {
// websocket is closed.
alert("Connection is closed...");
};
когда я запускаю этот код вот что получается:
обратите внимание, что при запуске JavaScript сервер принимает и успешно устанавливает соединение. JavaScript не может отправлять данные. Всякий раз, когда я размещаю метод отправки, он не будет отправлять, даже если соединение установлено. Как я могу это сделать?
4 ответов
WebSockets-это протокол, основанный на потоковом соединении TCP. Хотя WebSockets-это протокол на основе сообщений.
если вы хотите реализовать свой собственный протокол, то я рекомендую использовать последнюю и стабильную спецификацию (для 18/04/12) RFC 6455. Эта спецификация содержит всю необходимую информацию относительно рукопожатия и кадрирование. Как и большинство описаний сценариев поведения со стороны браузера, а также со стороны сервера. Настоятельно рекомендуется следовать какие рекомендации говорят о стороне сервера во время реализации вашего кода.
в нескольких словах я бы описал работу с WebSockets следующим образом:
-
создать сервер-сокет (система.Сеть.Сокеты) свяжите его с определенным портом и продолжайте слушать с асинхронным принятием соединений. Что-то вроде этого:--6-->
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP); serverSocket.Bind(new IPEndPoint(IPAddress.Any, 8080)); serverSocket.Listen(128); serverSocket.BeginAccept(null, 0, OnAccept, null);
-
вы должны есть!--16-->принимать функция "OnAccept", которая будет реализовать рукопожатие. В будущем он должен быть в другой ветке, если система предназначена для обработки огромного количества соединений в секунду.
private void OnAccept(IAsyncResult result) { try { Socket client = null; if (serverSocket != null && serverSocket.IsBound) { client = serverSocket.EndAccept(result); } if (client != null) { /* Handshaking and managing ClientSocket */ } } catch(SocketException exception) { } finally { if (serverSocket != null && serverSocket.IsBound) { serverSocket.BeginAccept(null, 0, OnAccept, null); } } }
-
после установления соединения, вы должны сделать рукопожатие. На основе спецификации 1.3 Открытие Рукопожатие, после установления соединения вы получите базовый HTTP-запрос с некоторой информацией. Пример:
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13
этот пример основан на версии протокола 13. Иметь в виду что более старые версии имеют некоторые различия, но в основном последние версии являются кросс-совместимыми. Различные браузеры могут отправлять вам дополнительные данные. Например, детали браузера и ОС, кэш и другие.
на основе предоставленных деталей рукопожатия вы должны генерировать строки ответа, они в основном одинаковы, но будут содержать Accpet-Key, который основан на предоставленном Sec-WebSocket-Key. В спецификации 1.3 четко описано, как генерировать ключ ответа. Вот моя функция, которую я использовал для Версии V13:
static private string guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; private string AcceptKey(ref string key) { string longKey = key + guid; SHA1 sha1 = SHA1CryptoServiceProvider.Create(); byte[] hashBytes = sha1.ComputeHash(System.Text.Encoding.ASCII.GetBytes(longKey)); return Convert.ToBase64String(hashBytes); }
рукопожатие ответ выглядит так:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
но ключ accept должен быть сгенерирован на основе предоставленного ключа от клиента и метода AcceptKey, который я предоставил ранее. Также убедитесь, что после последнего символа ключа accept вы поставили две новые строки "\r\n\r\n".
- после того, как ответ на рукопожатие отправляется с сервера, клиент должен вызвать"onopen функция", что означает, что вы можете отправлять сообщения после.
- сообщения не отправляются в формате RAW, но у них есть Обрамление Данных. И от клиента к серверу также реализуйте маскировку данных на основе предоставленных 4 байтов в заголовке сообщения. Хотя от сервера к клиенту вам не нужно применять маскировку данных. Читать раздел 5. Кадрирование Данных в спецификации. Вот copy-paste из моей собственной реализации. Он не готов к использованию кода и должен быть изменен, я публикую его только для того, чтобы дать представление и общую логику чтения/записи с помощью WebSocket обрамление. Перейти к этой ссылке.
- после реализации кадрирования убедитесь, что вы правильно получаете данные с помощью сокетов. Например, чтобы предотвратить слияние некоторых сообщений в одно, поскольку TCP по-прежнему является потоковым протоколом. Это означает, что вам нужно прочитать только определенное количество байтов. Длина сообщения всегда основана на заголовке и предоставленных деталях длины данных в заголовке. Поэтому, когда вы получаете данные из сокета, сначала получите 2 байта, получите детали из заголовка, основанного на спецификации кадрирования, затем, если маска предоставила еще 4 байта, а затем длину, которая может быть 1, 4 или 8 байтов на основе длины данных. И после данных оно само. После прочтения примените демаскировку, и ваши данные сообщения будут готовы к использованию.
- вы можете использовать некоторые Протокол, я рекомендую использовать JSON из-за экономии трафика и прост в использовании на стороне клиента в JavaScript. Для серверной части вы можете проверить некоторые Парсеры. Есть много им google может быть очень полезен.
реализация собственного протокола WebSockets определенно имеет некоторые преимущества и большой опыт, который вы получаете, а также контроль над протоколом. Но вы должны потратить некоторое время на это и убедиться, что реализация очень надежна.
в то же время вы можете посмотреть в готовые к использованию решения, которые google (снова) имеют достаточно.
(размещен ответ от имени ОП).
теперь я могу отправлять данные. Это моя новая версия программы благодаря вашим ответам и коду @Maksims Mihejevs.
сервер
using System;
using System.Net.Sockets;
using System.Net;
using System.Security.Cryptography;
using System.Threading;
namespace ConsoleApplication1
{
class Program
{
static Socket serverSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.IP);
static private string guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
static void Main(string[] args)
{
serverSocket.Bind(new IPEndPoint(IPAddress.Any, 8080));
serverSocket.Listen(128);
serverSocket.BeginAccept(null, 0, OnAccept, null);
Console.Read();
}
private static void OnAccept(IAsyncResult result)
{
byte[] buffer = new byte[1024];
try
{
Socket client = null;
string headerResponse = "";
if (serverSocket != null && serverSocket.IsBound)
{
client = serverSocket.EndAccept(result);
var i = client.Receive(buffer);
headerResponse = (System.Text.Encoding.UTF8.GetString(buffer)).Substring(0,i);
// write received data to the console
Console.WriteLine(headerResponse);
}
if (client != null)
{
/* Handshaking and managing ClientSocket */
var key = headerResponse.Replace("ey:", "`")
.Split('`')[1] // dGhlIHNhbXBsZSBub25jZQ== \r\n .......
.Replace("\r", "").Split('\n')[0] // dGhlIHNhbXBsZSBub25jZQ==
.Trim();
// key should now equal dGhlIHNhbXBsZSBub25jZQ==
var test1 = AcceptKey(ref key);
var newLine = "\r\n";
var response = "HTTP/1.1 101 Switching Protocols" + newLine
+ "Upgrade: websocket" + newLine
+ "Connection: Upgrade" + newLine
+ "Sec-WebSocket-Accept: " + test1 + newLine + newLine
//+ "Sec-WebSocket-Protocol: chat, superchat" + newLine
//+ "Sec-WebSocket-Version: 13" + newLine
;
// which one should I use? none of them fires the onopen method
client.Send(System.Text.Encoding.UTF8.GetBytes(response));
var i = client.Receive(buffer); // wait for client to send a message
// once the message is received decode it in different formats
Console.WriteLine(Convert.ToBase64String(buffer).Substring(0, i));
Console.WriteLine("\n\nPress enter to send data to client");
Console.Read();
var subA = SubArray<byte>(buffer, 0, i);
client.Send(subA);
Thread.Sleep(10000);//wait for message to be send
}
}
catch (SocketException exception)
{
throw exception;
}
finally
{
if (serverSocket != null && serverSocket.IsBound)
{
serverSocket.BeginAccept(null, 0, OnAccept, null);
}
}
}
public static T[] SubArray<T>(T[] data, int index, int length)
{
T[] result = new T[length];
Array.Copy(data, index, result, 0, length);
return result;
}
private static string AcceptKey(ref string key)
{
string longKey = key + guid;
byte[] hashBytes = ComputeHash(longKey);
return Convert.ToBase64String(hashBytes);
}
static SHA1 sha1 = SHA1CryptoServiceProvider.Create();
private static byte[] ComputeHash(string str)
{
return sha1.ComputeHash(System.Text.Encoding.ASCII.GetBytes(str));
}
}
}
JavaScript:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script type="text/javascript">
function connect() {
var ws = new WebSocket("ws://localhost:8080/service");
ws.onopen = function () {
alert("About to send data");
ws.send("Hello World"); // I WANT TO SEND THIS MESSAGE TO THE SERVER!!!!!!!!
alert("Message sent!");
};
ws.onmessage = function (evt) {
alert("About to receive data");
var received_msg = evt.data;
alert("Message received = "+received_msg);
};
ws.onclose = function () {
// websocket is closed.
alert("Connection is closed...");
};
};
</script>
</head>
<body style="font-size:xx-large" >
<div>
<a href="#" onclick="connect()">Click here to start</a></div>
</body>
</html>
когда я запускаю этот код, я могу отправлять и получать данные как от клиента, так и от сервера. Единственная проблема заключается в том, что сообщения шифруются, когда они приходят на сервер. Вот шаги того, как программа работает:
обратите внимание, как зашифровано сообщение от клиента.
WebSockets являются реализовано с протоколом, которая включает рукопожатия между клиентом и сервером. Не думаю, что они работают как обычные розетки. Прочтите протокол и попросите ваше заявление об этом. Кроме того, использовать существующую библиотеку с WebSocket, или .Серии net4.5beta, который имеет WebSocket API.
вопрос
поскольку вы используете WebSocket, spender является правильным. После получения исходных данных из WebSocket, вам необходимо отправить сообщение рукопожатия с сервера C#, прежде чем любая дополнительная информация может течь.
HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: websocket
Connection: Upgrade
WebSocket-Origin: example
WebSocket-Location: something.here
WebSocket-Protocol: 13
что-то в этом роде.
вы можете сделать еще несколько исследований о том, как WebSocket работает на w3 или google.
ссылки и ресурсы
вот спецификация протокола: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76#section-1.3
список рабочих примеры: