仕事中の新しいプロジェクトの 1 つで Web ソケット通信のデモンストレーションを作成しようとしています。また、かなり前に書いた古い Web ソケット サーバー プログラムを修正しようとしています。
それは適切なハンドシェイクを行い、クライアントに適切にデータを送信できます (これが本当に必要なすべてです) が、戻ってくるクライアント データは特別な Web ソケット通信プロトコルにあり、バイナリの操作はあまり得意ではありません。または16進数または暗号化アルゴリズム。
調査の結果、受信したテキストにはフレームが含まれており、sha1 で暗号化されていることがわかっていますが、このフレームを読み取ったり、削除したり、暗号化を解除したりする方法がわからないことが問題です。
これまでの Web サーバーの完全なコードは次のとおりです。
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 Socket ListenerSocket;
/// <summary>
/// Thread in which we await for incomming connections.
/// </summary>
private System.Threading.Thread _serverThread;
public delegate void ClientConnectedHandler (Socket Sock);
public delegate void ReceivedDataHandler(Socket Sock, string Message);
public event ClientConnectedHandler ClientConnected;
public event ReceivedDataHandler ReceivedData;
static WebSockServer() { }
/// <summary>
/// Starts thread with listening socket.
/// </summary>
public 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 void End()
{
_serverThread.Abort();
ListenerSocket.Dispose();
}
public 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
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);
}
}
if (ClientConnected != null)
{
ClientConnected(client);
}
Thread t = new Thread(new ParameterizedThreadStart(WaitForMessages));
t.Start(client);
}
}
private static void SendMessage(string Msg, Socket client, int WebSockVersion)
{
if (WebSockVersion >= 8)
{
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);
}
else
{
client.Send(new byte[] { 0x00 });
client.Send(Encoding.UTF8.GetBytes(Msg));
client.Send(new byte[] { 0xFF });
}
}
private void WaitForMessages(object client)
{
Socket sock = (Socket)client;
byte[] buffer = new byte[1024];
while (true)
{
sock.Receive(buffer);
ReceivedData(sock, Encoding.UTF8.GetString(buffer));
}
}
public 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 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;
}
}
}
私はそれが非常に粗雑で、それ自体がクリーンアップされず、さまざまな接続を適切に分離してリストなどに保持しないことを知っています。これは純粋にデモプロジェクト用です。
したがって、私が助けを必要としている主なセクションは、WaitForMessages 関数です。
private void WaitForMessages(object client)
{
Socket sock = (Socket)client;
byte[] buffer = new byte[1024];
while (true)
{
sock.Receive(buffer);
//Remove Frame and decrypt here
ReceivedData(sock, Encoding.UTF8.GetString(buffer));
}
}
私は本当にここにいくつかのコードをドロップしたいのですが、「あなたのコードのようなもので、これからそれを理解できるはずです」というデモを教えてくれたら、私は本当にそれを理解するつもりはありません、ほぼ保証できます。私はアプリ開発者であり、API 開発者ではありません。Microsoft が最終的に API を .NET 6.0 などに書き込んでこの機能を実現するまで待ちたくありません。