C# WebSocket 版本 8+ 服务器通信
Posted
技术标签:
【中文标题】C# WebSocket 版本 8+ 服务器通信【英文标题】:C# WebSocket Version 8+ Server Communication 【发布时间】:2012-03-08 20:28:00 【问题描述】:我一直在开发一个 hello-world 类型的 WebSocket 应用程序,看看我是否可以让它在 VS 2010 中运行。客户端代码非常简单,它在 Safari 中运行良好,因为 Safari 仍然在WebSocket 76 协议。
另一方面,Google Chrome 运行更新的 Sec-WebSocket-Version 代码,它更复杂。我从 code-plex 项目中“借用”了一些代码作为基础,以使其适用于较旧的 76 和 75 协议:http://wsaspnet.codeplex.com/SourceControl/changeset/view/58121#1561340
然后我继续升级我在那里找到的代码,直到它可以执行新的握手(在下面的代码中)。我终于碰壁并且似乎找不到任何帮助的部分是与 Sec-WebSocket-Version 8+ 套接字的通信。过去你可以只在开头附加一个 0x00,在结尾附加一个 0xFF,然后 tada!有效!但现在显然还有更多工作要做来解决安全问题。当我通过调试代码发送消息时,我可以知道套接字正在关闭,并且我在几个地方看到 chrome 在收到它不理解的消息时喜欢关闭套接字。
然后我发现了这个帖子:How can I send and receive WebSocket messages on the server side?,这家伙和我有同样的问题,但我似乎无法将他的回答者发布的伪代码翻译成 C#。谁能帮帮我?
客户端代码:
<div id="ComLog" style="height:600px;">
</div>
<input type="button" onclick="javascript:connectToServer();" value='connect' />
<script type="text/javascript">
var sock;
function connectToServer()
try
sock = new WebSocket("ws://localhost:8181/websock");
//sock = new WebSocket("ws://192.168.0.100:8181/websock");
//sock = new WebSocket("ws://websockets.org:8787");
sock.onopen = sockOpen;
sock.onerror = sockError;
sock.onclose = sockClosed;
sock.onmessage = sockMessage;
catch (e)
log("error:" + e);
function sockOpen()
log("connected");
function sockError(error, p2)
log("socket error!");
function sockClosed()
log("socket closed");
function sockMessage(event)
log("<b>Server:</b> " + event.data);
function log(msg)
var txtLog = document.getElementById("ComLog");
txtLog.innerhtml += msg + "</br>";
</script>
服务器代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.IO;
using System.Web;
using System.Collections.Specialized;
using System.Text.RegularExpressions;
using System.Threading;
using System.Security.Cryptography;
namespace WebSocks
public class WebSockServer
/// <summary>
/// Port number to listen on
/// </summary>
private const int PortNumber = 8181;
/// <summary>
/// Socket which awaits connections
/// </summary>
private static Socket ListenerSocket;
/// <summary>
/// Thread in which we await for incomming connections.
/// </summary>
static System.Threading.Thread _serverThread;
static WebSockServer()
/// <summary>
/// Starts thread with listening socket.
/// </summary>
public static void Start()
System.Threading.ThreadStart ts = new System.Threading.ThreadStart(Listen);
_serverThread = new System.Threading.Thread(ts);
_serverThread.Start();
/// <summary>
/// Stops listening for connections.
/// </summary>
public static void End()
_serverThread.Abort();
ListenerSocket.Dispose();
public static void Listen()
//Start listening
ListenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
EndPoint ep = new IPEndPoint(IPAddress.Parse("0.0.0.0"), PortNumber);
ListenerSocket.Bind(ep);
ListenerSocket.Listen(5);
while (true)
//New client
using (Socket client = ListenerSocket.Accept())
//Receiving clientHandshake
string clientHandshake = String.Empty;
byte[] buffer = null;
int readBytes = 0;
do
buffer = new byte[client.Available];
readBytes = client.Receive(buffer);
clientHandshake += Encoding.UTF8.GetString(buffer);
while (client.Available > 0);
//Last eight bytes are body of requets (we should include it in response)
byte[] secKey3 = buffer.Skip(readBytes - 8).Take(8).ToArray();
//Variables we can extract from clientHandshake
string clientOrigin = String.Empty;
string secKey1 = String.Empty;
string secKey2 = String.Empty;
string WebSocketVersion = String.Empty;
int WSV = 0;
string WebSocketKey = String.Empty;
//Extracting values from headers (key:value)
string[] clientHandshakeLines = Regex.Split(clientHandshake, Environment.NewLine);
foreach (string hline in clientHandshakeLines)
int valueStartIndex = hline.IndexOf(':') + 2;
if (valueStartIndex > 0)
if (hline.StartsWith("Origin"))
clientOrigin = hline.Substring(valueStartIndex, hline.Length - valueStartIndex);
else if (hline.StartsWith("Sec-WebSocket-Key2"))
secKey2 = hline.Substring(valueStartIndex, hline.Length - valueStartIndex);
else if (hline.StartsWith("Sec-WebSocket-Key1"))
secKey1 = hline.Substring(valueStartIndex, hline.Length - valueStartIndex);
if (hline.StartsWith("Sec-WebSocket-Version"))
WebSocketVersion = hline.Replace("Sec-WebSocket-Version: ", "");
WSV = Convert.ToInt32(WebSocketVersion);
if (hline.StartsWith("Sec-WebSocket-Key"))
WebSocketKey = hline.Replace("Sec-WebSocket-Key: ", "");
if (!String.IsNullOrEmpty(WebSocketVersion)) //WebSocketVersion 8 and up handshake check
//New WebSocketVersion number, included after Version 8
StringBuilder mResponse = new StringBuilder();
mResponse.AppendLine("HTTP/1.1 101 Switching Protocols");
mResponse.AppendLine("Upgrade: WebSocket");
mResponse.AppendLine("Connection: Upgrade");
mResponse.AppendLine(String.Format("Sec-WebSocket-Accept: 0", ComputeWebSocketHandshakeSecurityHash09(WebSocketKey)) + Environment.NewLine);
byte[] HSText = Encoding.UTF8.GetBytes(mResponse.ToString());
client.Send(HSText, 0, HSText.Length, 0);
else
//This part is common for all websockets editions (v. 75 & v.76)
client.Send(Encoding.UTF8.GetBytes("HTTP/1.1 101 Web Socket Protocol Handshake" + Environment.NewLine));
client.Send(Encoding.UTF8.GetBytes("Upgrade: WebSocket" + Environment.NewLine));
client.Send(Encoding.UTF8.GetBytes("Connection: Upgrade" + Environment.NewLine));
if (String.IsNullOrEmpty(secKey1) && String.IsNullOrEmpty(secKey2)) //75 or less handshake check
client.Send(Encoding.UTF8.GetBytes(String.Format("WebSocket-Origin: 0", clientOrigin) + Environment.NewLine));
client.Send(Encoding.UTF8.GetBytes("WebSocket-Location: ws://localhost:8181/websock" + Environment.NewLine));
client.Send(Encoding.UTF8.GetBytes(Environment.NewLine));
else //76 handshake check
//Keys present, this means 76 version is used. Writing Sec-* headers
client.Send(Encoding.UTF8.GetBytes(String.Format("Sec-WebSocket-Origin: 0", clientOrigin) + Environment.NewLine));
client.Send(Encoding.UTF8.GetBytes("Sec-WebSocket-Location: ws://localhost:8181/websock" + Environment.NewLine));
client.Send(Encoding.UTF8.GetBytes(Environment.NewLine));
//Calculating response body
byte[] secret = CalculateSecurityBody(secKey1, secKey2, secKey3);
client.Send(secret);
Thread.Sleep(1000);
SendMessage("This message will terminate in 5 seconds...", client, WSV);
Thread.Sleep(1000);
SendMessage("4", client, WSV);
Thread.Sleep(1000);
SendMessage("3", client, WSV);
Thread.Sleep(1000);
SendMessage("2", client, WSV);
Thread.Sleep(1000);
SendMessage("1", client, WSV);
Thread.Sleep(1000);
private static void SendMessage(string Msg, Socket client, int WebSockVersion)
if (WebSockVersion >= 8)
//This is the section that doesn't work
client.Send(Encoding.UTF8.GetBytes(Msg));
else
client.Send(new byte[] 0x00 );
client.Send(Encoding.UTF8.GetBytes(Msg));
client.Send(new byte[] 0xFF );
public static byte[] CalculateSecurityBody(string secKey1, string secKey2, byte[] secKey3)
//Remove all symbols that are not numbers
string k1 = Regex.Replace(secKey1, "[^0-9]", String.Empty);
string k2 = Regex.Replace(secKey2, "[^0-9]", String.Empty);
//Convert received string to 64 bit integer.
Int64 intK1 = Int64.Parse(k1);
Int64 intK2 = Int64.Parse(k2);
//Dividing on number of spaces
int k1Spaces = secKey1.Count(c => c == ' ');
int k2Spaces = secKey2.Count(c => c == ' ');
int k1FinalNum = (int)(intK1 / k1Spaces);
int k2FinalNum = (int)(intK2 / k2Spaces);
//Getting byte parts
byte[] b1 = BitConverter.GetBytes(k1FinalNum).Reverse().ToArray();
byte[] b2 = BitConverter.GetBytes(k2FinalNum).Reverse().ToArray();
//byte[] b3 = Encoding.UTF8.GetBytes(secKey3);
byte[] b3 = secKey3;
//Concatenating everything into 1 byte array for hashing.
List<byte> bChallenge = new List<byte>();
bChallenge.AddRange(b1);
bChallenge.AddRange(b2);
bChallenge.AddRange(b3);
//Hash and return
byte[] hash = MD5.Create().ComputeHash(bChallenge.ToArray());
return hash;
public static String ComputeWebSocketHandshakeSecurityHash09(String secWebSocketKey)
const String MagicKEY = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
String secWebSocketAccept = String.Empty;
// 1. Combine the request Sec-WebSocket-Key with magic key.
String ret = secWebSocketKey + MagicKEY;
// 2. Compute the SHA1 hash
SHA1 sha = new SHA1CryptoServiceProvider();
byte[] sha1Hash = sha.ComputeHash(Encoding.UTF8.GetBytes(ret));
// 3. Base64 encode the hash
secWebSocketAccept = Convert.ToBase64String(sha1Hash);
return secWebSocketAccept;
我所有问题的明显未翻译“答案”:
bytesFormatted[0] = 129
indexStartRawData = -1 // don't matter what value is here; it will be set now:
if bytesRaw.length <= 125
bytesFormatted[1] = bytesRaw.length
indexStartRawData = 2
else if bytesRaw.length >= 126 and bytesRaw.length <= 65535
bytesFormatted[1] = 126
bytesFormatted[2] = ( bytesRaw.length >> 8 ) AND 255
bytesFormatted[3] = ( bytesRaw.length ) AND 255
indexStartRawData = 4
else
bytesFormatted[1] = 127
bytesFormatted[2] = ( bytesRaw.length >> 56 ) AND 255
bytesFormatted[3] = ( bytesRaw.length >> 48 ) AND 255
bytesFormatted[4] = ( bytesRaw.length >> 40 ) AND 255
bytesFormatted[5] = ( bytesRaw.length >> 32 ) AND 255
bytesFormatted[6] = ( bytesRaw.length >> 24 ) AND 255
bytesFormatted[7] = ( bytesRaw.length >> 16 ) AND 255
bytesFormatted[8] = ( bytesRaw.length >> 8 ) AND 255
bytesFormatted[9] = ( bytesRaw.length ) AND 255
indexStartRawData = 10
// put raw data at the correct index
bytesFormatted.put(bytesRaw, indexStartRawData)
// now send bytesFormatted (e.g. write it to the socket stream)
~~~~~~~~~~~编辑~~~~~~~~~~~~
发送函数的当前版本:
private static void SendMessage(string Msg, Socket client, int WebSockVersion)
if (WebSockVersion >= 8)
bool IsFinal = true;
int OpCode = 1;
int? Mask = null;
int PayloadLength = Encoding.UTF8.GetBytes(Msg).Length;
byte[] buffer = Encoding.UTF8.GetBytes(Msg);
int offset = 0;
buffer[offset++] = (byte)((IsFinal ? 128 : 0) | ((int)OpCode & 15));
if (PayloadLength > ushort.MaxValue)
// write as a 64-bit length
buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | 127);
buffer[offset++] = 0;
buffer[offset++] = 0;
buffer[offset++] = 0;
buffer[offset++] = 0;
buffer[offset++] = (byte)(PayloadLength >> 24);
buffer[offset++] = (byte)(PayloadLength >> 16);
buffer[offset++] = (byte)(PayloadLength >> 8);
buffer[offset++] = (byte)(PayloadLength);
else if (PayloadLength > 125)
// write as a 16-bit length
buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | 126);
buffer[offset++] = (byte)(PayloadLength >> 8);
buffer[offset++] = (byte)(PayloadLength);
else
// write in the header
buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | PayloadLength);
if (Mask.HasValue)
int mask = Mask.Value;
buffer[offset++] = (byte)(mask >> 24);
buffer[offset++] = (byte)(mask >> 16);
buffer[offset++] = (byte)(mask >> 8);
buffer[offset++] = (byte)(mask);
//stream.Write(buffer, 0, offset);
client.Send(buffer, 0, PayloadLength, SocketFlags.None);
else
client.Send(new byte[] 0x00 );
client.Send(Encoding.UTF8.GetBytes(Msg));
client.Send(new byte[] 0xFF );
【问题讨论】:
已将我的答案重新编辑为您的编辑 【参考方案1】:老实说,这看起来是正确的;它与我的工作代码非常相似(仅处理 32 位长度,因此缺少 >> 32 及更高版本):
int offset = 0;
buffer[offset++] = (byte)((IsFinal ? 128 : 0) | ((int)OpCode & 15));
if (PayloadLength > ushort.MaxValue)
// write as a 64-bit length
buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | 127);
buffer[offset++] = 0;
buffer[offset++] = 0;
buffer[offset++] = 0;
buffer[offset++] = 0;
buffer[offset++] = (byte)(PayloadLength >> 24);
buffer[offset++] = (byte)(PayloadLength >> 16);
buffer[offset++] = (byte)(PayloadLength >> 8);
buffer[offset++] = (byte)(PayloadLength);
else if (PayloadLength > 125)
// write as a 16-bit length
buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | 126);
buffer[offset++] = (byte)(PayloadLength >> 8);
buffer[offset++] = (byte)(PayloadLength);
else
// write in the header
buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | PayloadLength);
if (Mask.HasValue)
int mask = Mask.Value;
buffer[offset++] = (byte)(mask >> 24);
buffer[offset++] = (byte)(mask >> 16);
buffer[offset++] = (byte)(mask >> 8);
buffer[offset++] = (byte)(mask);
stream.Write(buffer, 0, offset);
之后,它只写有效载荷(如果有的话)。由于您是从服务器写入客户端,因此您实际上不需要担心出站掩码,因此它始终只是 127
。但是,如果您正在接受传入消息,那么您非常确实需要担心掩码。
注意:Windows 8 上的 .NET 4.5 在HttpListener
和 ASP.NET 中内置了 WebSocket 支持;在那之前,另一种方法可能是查看 SuperWebSocket。
重新编辑:您已经选择了自己的数据!试试吧:
bool IsFinal = true;
int OpCode = 1;
int? Mask = null;
byte[] payload = Encoding.UTF8.GetBytes(Msg);
int PayloadLength = payload.Length;
byte[] buffer = new byte[64]; // for working out the header
int offset = 0;
buffer[offset++] = (byte)((IsFinal ? 128 : 0) | ((int)OpCode & 15));
if (PayloadLength > ushort.MaxValue)
// write as a 64-bit length
buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | 127);
buffer[offset++] = 0;
buffer[offset++] = 0;
buffer[offset++] = 0;
buffer[offset++] = 0;
buffer[offset++] = (byte)(PayloadLength >> 24);
buffer[offset++] = (byte)(PayloadLength >> 16);
buffer[offset++] = (byte)(PayloadLength >> 8);
buffer[offset++] = (byte)(PayloadLength);
else if (PayloadLength > 125)
// write as a 16-bit length
buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | 126);
buffer[offset++] = (byte)(PayloadLength >> 8);
buffer[offset++] = (byte)(PayloadLength);
else
// write in the header
buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | PayloadLength);
if (Mask.HasValue)
int mask = Mask.Value;
buffer[offset++] = (byte)(mask >> 24);
buffer[offset++] = (byte)(mask >> 16);
buffer[offset++] = (byte)(mask >> 8);
buffer[offset++] = (byte)(mask);
// you might want to manually combine these into 1 packet
client.Send(buffer, 0, offset, SocketFlags.None);
client.Send(payload, 0, payload.Length, SocketFlags.None);
【讨论】:
我可能应该注意到(byte)(expression)
转换显式只采用最低有效字节,因此与(byte)(expression & 255)
相同
我真的不明白这些东西。我知道将数据从服务器移动到客户端需要序列化、加密、发送、接收、未加密和非序列化,但为什么它必须用十六进制代码和废话如此复杂?我的问题是我不了解真正低级别的协议,我不想,我只是希望它能够正常工作,继续我的生活,而不必担心它会破裂或以后变得疯狂低效,因为我选择了错误的第 3 方 DLL 来使用。
变量:PayloadLength、buffer、IsFinal、OpCode 和 Mask 有什么作用,在代码运行之前需要将它们设置为什么?
PayloadLength
是我要发送的消息的大小,以字节为单位;所以如果我发送一个字符串消息,它是 UTF-8 编码的大块的长度。 buffer
是 byte[]
,我正在使用暂存缓冲区; IsFinal
应该是 true
如果您不知道它是什么...可以使用 WebSocket 将单个消息拆分为多个“帧”;这控制了它的工作方式; OpCode
是 1
用于文本消息,2
用于二进制消息,8
用于关闭,0
用于继续,9
用于 ping,10
用于 pong; Mask
是安全的东西;因为您正在谈论服务器到客户端
它基本上应该是int? Mask = null;
,以便该代码“按原样”工作,因为服务器从不使用掩码(客户端始终使用掩码);这样做,@Jrud?以上是关于C# WebSocket 版本 8+ 服务器通信的主要内容,如果未能解决你的问题,请参考以下文章
Bing Speech to Text API - 在 c# 中通过 websocket 进行通信
C#实现WebSocket协议客户端和服务器Websocket-Sharp组件解析
Unity使用webSocket与服务器通信——C#服务端(Fleck)与Unity客户端( NativeWebSocket)传输多种数据数据