我一直在开发一个 hello-world 类型的 WebSocket 应用程序,看看是否可以在 VS 2010 中运行它。客户端代码非常简单,并且在 Safari 中运行得很好,因为 Safari 仍然运行在WebSocket 76 协议(protocol)。
另一方面,Google Chrome 运行较新的 Sec-WebSocket-Version 代码,该代码更加复杂。我从 code-plex 项目“借用”了一些代码作为基础,以使其适用于较旧的 76 和 75 协议(protocol):http://wsaspnet.codeplex.com/SourceControl/changeset/view/58121#1561340
然后,我继续升级在那里找到的代码,直到它可以执行新的握手(在下面的代码中)。我最终碰壁并且似乎找不到任何帮助的部分是与 Sec-WebSocket-Version 8+ 套接字的通信。过去你只需在开头附加一个 0x00,在末尾附加一个 0xFF 就可以了!有效!但现在显然还有更多工作要做来解决安全问题。当我通过调试代码发送消息时,我可以看出套接字正在关闭,并且我在几个地方读到,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 });
}
}
最佳答案
说实话,这看起来是对的;它与我的工作代码非常相似(仅处理 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);
关于C# WebSocket 版本 8+ 服务器通信,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9624427/