Создание примера "Hello World" WebSocket
Я не понимаю, почему я не могу заставить работать следующий код. Я хочу подключиться с помощью 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 не может отправлять данные. Всякий раз, когда я размещаю метод отправки, он не отправляет, даже если соединение установлено. Как я могу сделать эту работу?
Ответы
Ответ 1
WebSockets - это протокол, который использует потоковое соединение TCP. Хотя WebSockets - протокол на основе сообщений.
Если вы хотите реализовать свой собственный протокол, я рекомендую использовать последнюю и стабильную спецификацию (для 18/04/12) RFC 6455.
Эта спецификация содержит всю необходимую информацию о рукопожатии и обрамлении. Также большинство описаний сценариев поведения со стороны браузера, а также со стороны сервера.
Настоятельно рекомендуется следовать рекомендациям относительно серверной части во время реализации вашего кода.
В нескольких словах я бы описал работу с WebSockets следующим образом:
-
Создать сервер Socket (System.Net.Sockets) привязывает его к определенному порту и продолжит прослушивание с асинхронным принятием соединений. Что-то вроде этого:
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);
-
У вас должна быть принятая функция "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=
Но ключ принятия должен быть сгенерированным на основе предоставленного ключа от клиента и метода AcceptKey, который я представил ранее. Кроме того, убедитесь, что после последнего символа клавиши accept вы положили две новые строки "\ r\n\r\n".
- После того, как квитированный ответ отправляется с сервера, клиент должен запустить функцию onopen ", что означает, что вы можете отправлять сообщения после.
- Сообщения не отправляются в необработанном формате, но они имеют Data Framing. А от клиента к серверу также реализуется маскировка данных на основе предоставленных 4 байта в заголовке сообщения. Хотя от сервера к клиенту вам не нужно применять маскировку данных. Раздел 5. Data Framing в спецификации.
Вот скопировать-вставить из моей собственной реализации. Это не готовый к использованию код, и его нужно модифицировать, я публикую его только для того, чтобы дать представление и общую логику чтения/записи с кадрированием WebSocket. Перейдите в эту ссылку.
- После создания кадрирования убедитесь, что вы правильно передаете данные с помощью сокетов. Например, чтобы предотвратить объединение некоторых сообщений в один, поскольку протокол TCP по-прежнему основан на потоке. Это означает, что вы должны читать ТОЛЬКО определенное количество байтов. Длина сообщения всегда основана на заголовке и предоставляет данные длины данных в заголовке. Поэтому, когда вы получаете данные от Socket, сначала получите 2 байта, получите информацию из заголовка на основе спецификации Framing, затем, если маска предоставила еще 4 байта, а затем длину, которая может быть 1, 4 или 8 байтов на основе длины данных. И после данных он сам. После того, как вы прочтете это, примените demasking и ваши данные сообщения готовы к использованию.
- Возможно, вы захотите использовать некоторые Протокол данных, я рекомендую использовать JSON из-за экономии трафика и прост в использовании на стороне клиента в JavaScript. На стороне сервера вы можете проверить некоторые из парсеров. Их очень много, google может быть действительно полезен.
Реализация собственного протокола WebSockets определенно имеет некоторые преимущества и большой опыт, который вы получаете, а также контроль над протоколом. Но вы должны потратить некоторое время на это, и убедитесь, что реализация очень надежна.
В то же время вы можете взглянуть на готовые к использованию решения, для которых google (снова) достаточно.
Ответ 2
(Отправленный ответ от имени OP).
Теперь я могу отправлять данные. Это моя новая версия программы благодаря вашим ответам и коду @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>
Когда я запускаю этот код, я могу отправлять и получать данные как от клиента, так и от сервера. Единственная проблема заключается в том, что сообщения зашифровываются, когда они поступают на сервер. Ниже приведены действия, выполняемые программой:
![enter image description here]()
Обратите внимание, как шифруется сообщение от клиента.
Ответ 3
WebSockets реализован с протоколом, который включает рукопожатие между клиентом и сервером. Я не думаю, что они очень похожи на обычные сокеты. Прочитайте протокол и попросите приложение поговорить об этом. Кроме того, используйте существующую библиотеку WebSocket или .Net4.5beta, которая имеет API WebSocket.
Ответ 4
Проблема
Поскольку вы используете WebSocket, spender корректен. После получения исходных данных из WebSocket вам необходимо отправить сообщение подтверждения с сервера С#, прежде чем какая-либо дополнительная информация может протекать.
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
Список рабочих примеров:
Ответ 5
Я не мог найти простой рабочий пример нигде (по состоянию на 19 января), так что вот обновленная версия. У меня версия хрома 71.0.3578.98.
С# Websocket сервер:
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
namespace WebSocketServer
{
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(1); //just one socket
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);
Console.WriteLine("=====================");
}
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
;
client.Send(System.Text.Encoding.UTF8.GetBytes(response));
var i = client.Receive(buffer); // wait for client to send a message
string browserSent = GetDecodedData(buffer, i);
Console.WriteLine("BrowserSent: " + browserSent);
Console.WriteLine("=====================");
//now send message to client
client.Send(GetFrameFromString("This is message from server to client."));
System.Threading.Thread.Sleep(10000);//wait for message to be sent
}
}
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));
}
//Needed to decode frame
public static string GetDecodedData(byte[] buffer, int length)
{
byte b = buffer[1];
int dataLength = 0;
int totalLength = 0;
int keyIndex = 0;
if (b - 128 <= 125)
{
dataLength = b - 128;
keyIndex = 2;
totalLength = dataLength + 6;
}
if (b - 128 == 126)
{
dataLength = BitConverter.ToInt16(new byte[] { buffer[3], buffer[2] }, 0);
keyIndex = 4;
totalLength = dataLength + 8;
}
if (b - 128 == 127)
{
dataLength = (int)BitConverter.ToInt64(new byte[] { buffer[9], buffer[8], buffer[7], buffer[6], buffer[5], buffer[4], buffer[3], buffer[2] }, 0);
keyIndex = 10;
totalLength = dataLength + 14;
}
if (totalLength > length)
throw new Exception("The buffer length is small than the data length");
byte[] key = new byte[] { buffer[keyIndex], buffer[keyIndex + 1], buffer[keyIndex + 2], buffer[keyIndex + 3] };
int dataIndex = keyIndex + 4;
int count = 0;
for (int i = dataIndex; i < totalLength; i++)
{
buffer[i] = (byte)(buffer[i] ^ key[count % 4]);
count++;
}
return Encoding.ASCII.GetString(buffer, dataIndex, dataLength);
}
//function to create frames to send to client
/// <summary>
/// Enum for opcode types
/// </summary>
public enum EOpcodeType
{
/* Denotes a continuation code */
Fragment = 0,
/* Denotes a text code */
Text = 1,
/* Denotes a binary code */
Binary = 2,
/* Denotes a closed connection */
ClosedConnection = 8,
/* Denotes a ping*/
Ping = 9,
/* Denotes a pong */
Pong = 10
}
/// <summary>Gets an encoded websocket frame to send to a client from a string</summary>
/// <param name="Message">The message to encode into the frame</param>
/// <param name="Opcode">The opcode of the frame</param>
/// <returns>Byte array in form of a websocket frame</returns>
public static byte[] GetFrameFromString(string Message, EOpcodeType Opcode = EOpcodeType.Text)
{
byte[] response;
byte[] bytesRaw = Encoding.Default.GetBytes(Message);
byte[] frame = new byte[10];
int indexStartRawData = -1;
int length = bytesRaw.Length;
frame[0] = (byte)(128 + (int)Opcode);
if (length <= 125)
{
frame[1] = (byte)length;
indexStartRawData = 2;
}
else if (length >= 126 && length <= 65535)
{
frame[1] = (byte)126;
frame[2] = (byte)((length >> 8) & 255);
frame[3] = (byte)(length & 255);
indexStartRawData = 4;
}
else
{
frame[1] = (byte)127;
frame[2] = (byte)((length >> 56) & 255);
frame[3] = (byte)((length >> 48) & 255);
frame[4] = (byte)((length >> 40) & 255);
frame[5] = (byte)((length >> 32) & 255);
frame[6] = (byte)((length >> 24) & 255);
frame[7] = (byte)((length >> 16) & 255);
frame[8] = (byte)((length >> 8) & 255);
frame[9] = (byte)(length & 255);
indexStartRawData = 10;
}
response = new byte[indexStartRawData + length];
int i, reponseIdx = 0;
//Add the frame bytes to the reponse
for (i = 0; i < indexStartRawData; i++)
{
response[reponseIdx] = frame[i];
reponseIdx++;
}
//Add the data bytes to the response
for (i = 0; i < length; i++)
{
response[reponseIdx] = bytesRaw[i];
reponseIdx++;
}
return response;
}
}
}
Клиентский HTML и 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">
var socket = new WebSocket('ws://localhost:8080/websession');
socket.onopen = function() {
// alert('handshake successfully established. May send data now...');
socket.send("Hi there from browser.");
};
socket.onmessage = function (evt) {
//alert("About to receive data");
var received_msg = evt.data;
alert("Message received = "+received_msg);
};
socket.onclose = function() {
alert('connection closed');
};
</script>
</head>
<body>
</body>
</html>
Ответ 6
Вы можете попробовать эти простые примеры...