[英]Creating a “Hello World” WebSocket example
我不明白为什么我不能让下面的代码工作。 我想用 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 无法发送数据。 每当我放置发送方法时,即使建立了连接,它也不会发送。 我怎样才能使这项工作?
WebSockets 是依赖 TCP 流连接的协议。 虽然 WebSockets 是基于消息的协议。
如果您想实现自己的协议,那么我建议使用最新且稳定的规范(18/04/12) RFC 6455 。 该规范包含有关握手和帧的所有必要信息。 以及大部分关于浏览器端和服务器端行为场景的描述。 强烈建议在实现代码期间遵循有关服务器端的建议。
简而言之,我会像这样描述使用 WebSockets 的情况:
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);
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); } } }
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 的版本。请记住,旧版本有一些差异,但大多数最新版本是交叉兼容的。 不同的浏览器可能会向您发送一些额外的数据。 例如浏览器和操作系统详细信息、缓存等。
根据提供的握手详细信息,您必须生成答案行,它们大多相同,但将包含 Accept-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 方法生成的密钥。 同样,确保在接受键的最后一个字符后放置两行新行“\\r\\n\\r\\n”。
从服务器发送握手应答后,客户端应触发“ onopen ”功能,这意味着您可以发送消息。
消息不是以原始格式发送的,但它们具有Data Framing 。 并且从客户端到服务器也基于消息头中提供的 4 个字节实现数据屏蔽。 尽管从服务器到客户端,您不需要对数据应用屏蔽。 阅读第5节。规范中的数据帧。 这是我自己实现的复制粘贴。 它不是现成的代码,必须进行修改,我发布它只是为了提供使用 WebSocket 框架进行读/写的想法和整体逻辑。 转到此链接。
实现成帧后,请确保使用套接字以正确的方式接收数据。 例如为了防止某些消息合并为一个,因为 TCP 仍然是基于流的协议。 这意味着您必须仅读取特定数量的字节。 消息的长度始终基于标头,并在标头本身中提供数据长度详细信息。 因此,当您从 Socket 接收数据时,首先接收 2 个字节,根据帧规范从标头中获取详细信息,然后如果掩码提供另外 4 个字节,然后根据数据长度可能为 1、4 或 8 个字节的长度。 在数据之后它自己。 阅读后,应用去掩码,您的消息数据就可以使用了。
您可能想要使用一些数据协议,我建议使用 JSON 由于流量经济且易于在 JavaScript 客户端使用。 对于服务器端,您可能需要检查一些解析器。 有很多,谷歌真的很有帮助。
实现自己的 WebSockets 协议肯定有一些好处和丰富的经验,以及对协议本身的控制。 但是你必须花一些时间去做,并确保实现是高度可靠的。
同时,您可能会查看谷歌(再次)拥有足够的现成可用的解决方案。
(代表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));
}
}
}
<!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 库,或具有WebSocket API 的.Net4.5beta。
我在任何地方都找不到一个简单的工作示例(截至 1 月 19 日),所以这里是一个更新版本。 我有 chrome 版本 71.0.3578.98。
C# 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];
long indexStartRawData = -1;
long length = (long)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];
long 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>
由于您使用的是 WebSocket,因此支出者是正确的。 从 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
工作示例列表:
你可以尝试这些简单的例子......
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.