using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Threading;
using
System.Net.Sockets;
using
System.Net;
using
System.Text.RegularExpressions;
using
System.Security.Cryptography;
namespace
WebSocketServer
{
public
class
WebSocket
{
private
Dictionary<
string
, Session> SessionPool =
new
Dictionary<
string
, Session>();
private
Dictionary<
string
,
string
> MsgPool =
new
Dictionary<
string
,
string
>();
#region 启动WebSocket服务
/// <summary>
/// 启动WebSocket服务
/// </summary>
public
void
start(
int
port)
{
Socket SockeServer =
new
Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
SockeServer.Bind(
new
IPEndPoint(IPAddress.Any, port));
SockeServer.Listen(20);
SockeServer.BeginAccept(
new
AsyncCallback(Accept), SockeServer);
Console.WriteLine(
"服务已启动"
);
Console.WriteLine(
"按任意键关闭服务"
);
Console.ReadLine();
}
#endregion
#region 处理客户端连接请求
/// <summary>
/// 处理客户端连接请求
/// </summary>
/// <param name="result"></param>
private
void
Accept(IAsyncResult socket)
{
Socket SockeServer = (Socket)socket.AsyncState;
Socket SockeClient = SockeServer.EndAccept(socket);
byte
[] buffer =
new
byte
[4096];
try
{
SockeClient.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None,
new
AsyncCallback(Recieve), SockeClient);
Session session =
new
Session();
session.SockeClient = SockeClient;
session.IP = SockeClient.RemoteEndPoint.ToString();
session.buffer = buffer;
lock
(SessionPool)
{
if
(SessionPool.ContainsKey(session.IP))
{
this
.SessionPool.Remove(session.IP);
}
this
.SessionPool.Add(session.IP, session);
}
SockeServer.BeginAccept(
new
AsyncCallback(Accept), SockeServer);
Console.WriteLine(
string
.Format(
"Client {0} connected"
, SockeClient.RemoteEndPoint));
}
catch
(Exception ex)
{
Console.WriteLine(
"Error : "
+ ex.ToString());
}
}
#endregion
#region 处理接收的数据
/// <summary>
/// 处理接受的数据
/// </summary>
/// <param name="socket"></param>
private
void
Recieve(IAsyncResult socket)
{
Socket SockeClient = (Socket)socket.AsyncState;
string
IP = SockeClient.RemoteEndPoint.ToString();
if
(SockeClient ==
null
|| !SessionPool.ContainsKey(IP))
{
return
;
}
try
{
int
length = SockeClient.EndReceive(socket);
byte
[] buffer = SessionPool[IP].buffer;
SockeClient.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None,
new
AsyncCallback(Recieve), SockeClient);
string
msg = Encoding.UTF8.GetString(buffer, 0, length);
if
(msg.Contains(
"Sec-WebSocket-Key"
))
{
SockeClient.Send(PackageHandShakeData(buffer, length));
SessionPool[IP].isWeb =
true
;
return
;
}
if
(SessionPool[IP].isWeb)
{
msg = AnalyzeClientData(buffer, length);
}
byte
[] msgBuffer = PackageServerData(msg);
foreach
(Session se
in
SessionPool.Values)
{
se.SockeClient.Send(msgBuffer, msgBuffer.Length, SocketFlags.None);
}
}
catch
{
SockeClient.Disconnect(
true
);
Console.WriteLine(
"客户端 {0} 断开连接"
, IP);
SessionPool.Remove(IP);
}
}
#endregion
#region 客户端和服务端的响应
#endregion
#region 打包请求连接数据
/// <summary>
/// 打包请求连接数据
/// </summary>
/// <param name="handShakeBytes"></param>
/// <param name="length"></param>
/// <returns></returns>
private
byte
[] PackageHandShakeData(
byte
[] handShakeBytes,
int
length)
{
string
handShakeText = Encoding.UTF8.GetString(handShakeBytes, 0, length);
string
key =
string
.Empty;
Regex reg =
new
Regex(
@"Sec\-WebSocket\-Key:(.*?)\r\n"
);
Match m = reg.Match(handShakeText);
if
(m.Value !=
""
)
{
key = Regex.Replace(m.Value,
@"Sec\-WebSocket\-Key:(.*?)\r\n"
,
"$1"
).Trim();
}
byte
[] secKeyBytes = SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(key +
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
));
string
secKey = Convert.ToBase64String(secKeyBytes);
var
responseBuilder =
new
StringBuilder();
responseBuilder.Append(
"HTTP/1.1 101 Switching Protocols"
+
"\r\n"
);
responseBuilder.Append(
"Upgrade: websocket"
+
"\r\n"
);
responseBuilder.Append(
"Connection: Upgrade"
+
"\r\n"
);
responseBuilder.Append(
"Sec-WebSocket-Accept: "
+ secKey +
"\r\n\r\n"
);
return
Encoding.UTF8.GetBytes(responseBuilder.ToString());
}
#endregion
#region 处理接收的数据
/// <summary>
/// 处理接收的数据
/// 参考 http://www.cnblogs.com/smark/archive/2012/11/26/2789812.html
/// </summary>
/// <param name="recBytes"></param>
/// <param name="length"></param>
/// <returns></returns>
private
string
AnalyzeClientData(
byte
[] recBytes,
int
length)
{
int
start = 0;
if
(length < 2)
return
""
;
bool
IsEof = (recBytes[start] >> 7) > 0;
if
(!IsEof)
return
""
;
start++;
bool
hasMask = (recBytes[start] >> 7) > 0;
if
(!hasMask)
return
""
;
UInt64 mPackageLength = (UInt64)recBytes[start] & 0x7F;
start++;
byte
[] Masking_key =
new
byte
[4];
byte
[] mDataPackage;
if
(mPackageLength == 126)
{
mPackageLength = (UInt64)(recBytes[start] << 8 | recBytes[start + 1]);
start += 2;
}
if
(mPackageLength == 127)
{
mPackageLength = (UInt64)(recBytes[start] << (8 * 7) | recBytes[start] << (8 * 6) | recBytes[start] << (8 * 5) | recBytes[start] << (8 * 4) | recBytes[start] << (8 * 3) | recBytes[start] << (8 * 2) | recBytes[start] << 8 | recBytes[start + 1]);
start += 8;
}
mDataPackage =
new
byte
[mPackageLength];
for
(UInt64 i = 0; i < mPackageLength; i++)
{
mDataPackage[i] = recBytes[i + (UInt64)start + 4];
}
Buffer.BlockCopy(recBytes, start, Masking_key, 0, 4);
for
(UInt64 i = 0; i < mPackageLength; i++)
{
mDataPackage[i] = (
byte
)(mDataPackage[i] ^ Masking_key[i % 4]);
}
return
Encoding.UTF8.GetString(mDataPackage);
}
#endregion
#region 发送数据
/// <summary>
/// 把发送给客户端消息打包处理(拼接上谁什么时候发的什么消息)
/// </summary>
/// <returns>The data.</returns>
/// <param name="message">Message.</param>
private
byte
[] PackageServerData(
string
msg)
{
byte
[] content =
null
;
byte
[] temp = Encoding.UTF8.GetBytes(msg);
if
(temp.Length < 126)
{
content =
new
byte
[temp.Length + 2];
content[0] = 0x81;
content[1] = (
byte
)temp.Length;
Buffer.BlockCopy(temp, 0, content, 2, temp.Length);
}
else
if
(temp.Length < 0xFFFF)
{
content =
new
byte
[temp.Length + 4];
content[0] = 0x81;
content[1] = 126;
content[2] = (
byte
)(temp.Length & 0xFF);
content[3] = (
byte
)(temp.Length >> 8 & 0xFF);
Buffer.BlockCopy(temp, 0, content, 4, temp.Length);
}
return
content;
}
#endregion
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示