概要
SuperSocket实现服务端
SuperSocket实现客户端
Socket实现js客户端
互相通讯代码实现
技术实现
项目结构
Project.Application:项目实现,Socket实现
Project.Pack:项目包
SocketTest:项目服务端测试
WindowsSocket:项目客户端测试
Project.Application中代码实现
SocketDataFilter类
using SuperSocket.ProtoBase;
using System.Buffers;
namespace Project.Application.SupSocket
{
public class SocketDataFilter : IPipelineFilter<SocketDataPackage>
{
public IPackageDecoder<SocketDataPackage> Decoder { get; set; }
public object Context { get; set; }
public IPipelineFilter<SocketDataPackage> NextFilter => this;
public SocketDataPackage Filter(ref SequenceReader<byte> reader)
{
SocketDataPackage txtPackage = new SocketDataPackage { Datas = reader.Sequence.ToArray() };
while (reader.TryRead(out _)) ;
return txtPackage;
}
public void Reset() { }
}
}
SocketDataPackage类
namespace Project.Application.SupSocket
{
/// <summary>
/// 自定义数据包
/// </summary>
public class SocketDataPackage
{
/// <summary>
/// 功能码
/// </summary>
public int FCode { get; set; } = -1;
/// <summary>
/// 数据
/// </summary>
public byte[] Datas { get; set; }
}
}
SocketServerHostedService类
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Net;
using System.Text.RegularExpressions;
using System.Text;
using System.Security.Cryptography;
using SuperSocket;
using SuperSocket.Channel;
namespace Project.Application.SupSocket
{
/// <summary>
/// Socket承载服务
/// </summary>
public class SocketServerHostedService : IHostedService
{
/// <summary>
/// 使用自定义的数据包和过滤器创建的服务宿主
/// </summary>
private static ISuperSocketHostBuilder<SocketDataPackage> _host = SupSocketServer.SocketHost;
private ILogger<SocketServerHostedService> _logger;
private IHost _serverhost;
public SocketServerHostedService(
IHost serverhost,
ILogger<SocketServerHostedService> logger)
{
_serverhost = serverhost;
_logger = logger;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
//启动Socket服务
await SupSocketServer.StartAsync("0.0.0.0", 8552, OnConnectedAsync, OnClosedAsync, OnPackageAsync);
}
public async Task StopAsync(CancellationToken cancellationToken)
{
//关闭Socket链接
await SupSocketServer.DisConnects();
await Task.CompletedTask;
}
/// <summary>
/// 会话的连接事件
/// </summary>
/// <param name="session"></param>
/// <returns></returns>
private async ValueTask OnConnectedAsync(IAppSession session)
{
await Task.Run(async () =>
{
var endpoint = (IPEndPoint)session.RemoteEndPoint;
await SupSocketServer.AddSession(session);
#region 处理回话连接逻辑
#endregion
});
}
/// <summary>
/// 会话的断开事件
/// </summary>
/// <param name="session"></param>
/// <param name="e"></param>
/// <returns></returns>
private async ValueTask OnClosedAsync(IAppSession session, CloseEventArgs e)
{
var ses = SupSocketServer.ConnectSessions;
await Task.Run(async () =>
{
var endpoint = (IPEndPoint)session.RemoteEndPoint;
await SupSocketServer.RemoveSession(session);
#region 处理回话断开后逻辑
#endregion
});
}
/// <summary>
/// 数据接收事件
/// </summary>
/// <param name="session"></param>
/// <param name="package"></param>
/// <returns></returns>
private async ValueTask OnPackageAsync(IAppSession session, SocketDataPackage package)
{
await Task.Run(async () =>
{
var endpoint = (IPEndPoint)session.RemoteEndPoint;
#region 处理回话连接数据接受后逻辑
#endregion
//接受的请求(是否包含掩码)
string message = AnalyticDatass(package.Datas, package.Datas.Length);
if (message.Contains("GET"))
{
//是连接请求,返回牵手包
string returnmessage = GetSecKeyAccetp(package.Datas, package.Datas.Length);
//返回发送握手信息
await session.SendAsync(PackHandShakeData(returnmessage));
}
});
}
/// <summary>
/// 解析客户端数据包,防止乱码
/// </summary>
/// <param name="recBytes">服务器接收的数据包</param>
/// <param name="recByteLength">有效数据长度</param>
/// <returns></returns>
private static string AnalyticDatass(byte[] recBytes, int recByteLength)
{
if (recByteLength < 2) { return string.Empty; }
bool fin = (recBytes[0] & 0x80) == 0x80; // 1bit,1表示最后一帧
if (!fin)
{
return System.Text.Encoding.UTF8.GetString(recBytes);// 超过一帧暂
}
bool mask_flag = (recBytes[1] & 0x80) == 0x80; // 是否包含掩码
if (!mask_flag)
{
return System.Text.Encoding.UTF8.GetString(recBytes);// 不包含掩码
}
int payload_len = recBytes[1] & 0x7F; // 数据长度
byte[] masks = new byte[4];
byte[] payload_data;
if (payload_len == 126)
{
Array.Copy(recBytes, 4, masks, 0, 4);
payload_len = (UInt16)(recBytes[2] << 8 | recBytes[3]);
payload_data = new byte[payload_len];
Array.Copy(recBytes, 8, payload_data, 0, payload_len);
}
else if (payload_len == 127)
{
Array.Copy(recBytes, 10, masks, 0, 4);
byte[] uInt64Bytes = new byte[8];
for (int i = 0; i < 8; i++)
{
uInt64Bytes[i] = recBytes[9 - i];
}
UInt64 len = BitConverter.ToUInt64(uInt64Bytes, 0);
payload_data = new byte[len];
for (UInt64 i = 0; i < len; i++)
{
payload_data[i] = recBytes[i + 14];
}
}
else
{
Array.Copy(recBytes, 2, masks, 0, 4);
payload_data = new byte[payload_len];
Array.Copy(recBytes, 6, payload_data, 0, payload_len);
}
for (var i = 0; i < payload_len; i++)
{
payload_data[i] = (byte)(payload_data[i] ^ masks[i % 4]);
}
return Encoding.UTF8.GetString(payload_data);
}
/// <summary>
/// 打包握手信息
/// </summary>
/// <param name="secKeyAccept">Sec-WebSocket-Accept</param>
/// <returns>数据包</returns>
private static byte[] PackHandShakeData(string secKeyAccept)
{
var responseBuilder = new StringBuilder();
responseBuilder.Append("HTTP/1.1 101 Switching Protocols" + Environment.NewLine);
responseBuilder.Append("Upgrade: websocket" + Environment.NewLine);
responseBuilder.Append("Connection: Upgrade" + Environment.NewLine);
responseBuilder.Append("Sec-WebSocket-Accept: " + secKeyAccept + Environment.NewLine + Environment.NewLine);
//如果把上一行换成下面两行,才是thewebsocketprotocol-17协议,但居然握手不成功,目前仍没弄明白!
//responseBuilder.Append("Sec-WebSocket-Accept: " + secKeyAccept + Environment.NewLine);
//responseBuilder.Append("Sec-WebSocket-Protocol: chat" + Environment.NewLine);
return Encoding.UTF8.GetBytes(responseBuilder.ToString());
}
/// <summary>
/// 生成Sec-WebSocket-Accept
/// </summary>
/// <param name="handShakeBytes"></param>
/// <param name="bytesLength"></param>
/// <returns>Sec-WebSocket-Accept</returns>
private static string GetSecKeyAccetp(byte[] handShakeBytes, int bytesLength)
{
//客户端握手信息
string handShakeText = Encoding.UTF8.GetString(handShakeBytes, 0, bytesLength);
string key = string.Empty;
Regex r = new Regex(@"Sec\-WebSocket\-Key:(.*?)\r\n");
Match m = r.Match(handShakeText);
if (m.Groups.Count != 0)
{
key = Regex.Replace(m.Value, @"Sec\-WebSocket\-Key:(.*?)\r\n", "$1").Trim();
}
byte[] encryptionString = SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
return Convert.ToBase64String(encryptionString);
}
}
}
SupSocketServer类
using SuperSocket.Channel;
using SuperSocket;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
namespace Project.Application.SupSocket
{
public class SupSocketServer
{
/// <summary>
/// 使用自定义的数据包和过滤器创建的服务宿主
/// </summary>
public static readonly ISuperSocketHostBuilder<SocketDataPackage> SocketHost;
/// <summary>
/// 会话集合
/// </summary>
public static readonly ConcurrentDictionary<string, IAppSession> ConnectSessions;
static SupSocketServer()
{
if (SocketHost == null)
SocketHost = SuperSocketHostBuilder.Create<SocketDataPackage, SocketDataFilter>();
if (ConnectSessions == null)
ConnectSessions = new ConcurrentDictionary<string, IAppSession>();
}
/// <summary>
/// 启动
/// </summary>
/// <param name="onConnected"></param>
/// <param name="onClosed"></param>
/// <param name="packageHandler"></param>
/// <param name="errorHandler"></param>
/// <returns></returns>
public static async Task<bool> StartAsync(string ip, int port, Func<IAppSession, ValueTask> onConnected,
Func<IAppSession, CloseEventArgs, ValueTask> onClosed,
Func<IAppSession, SocketDataPackage, ValueTask> packageHandler,
Func<IAppSession, PackageHandlingException<SocketDataPackage>, ValueTask<bool>> errorHandler = null)
{
//配置参数
SocketHost.ConfigureSuperSocket(options =>
{
options.Name = "HosSocketServer";
options.MaxPackageLength = 1024 * 1024;
options.ReceiveBufferSize = 4 * 1024;
options.SendBufferSize = 4 * 1024;
options.ReceiveTimeout = 0;
options.SendTimeout = 500 * 1000;
options.Listeners = new[] { new ListenOptions { Ip = ip, Port = port, BackLog = 1024 } }.ToList();
});
//使用会话的连接和断开事件(建立和断开Socket连接时触发)
SocketHost.UseSessionHandler(onConnected, onClosed);
//使用会话的数据接收时间(接收到数据包时触发)
SocketHost.UsePackageHandler(packageHandler);
//复用已经闲置或者失去连接的资源
//_host.UseClearIdleSession();
//启动Socket
await SocketHost.StartAsync();
await Task.CompletedTask;
return true;
}
/// <summary>
/// 保存Session
/// </summary>
/// <param name="session"></param>
/// <returns></returns>
public static async Task<bool> AddSession(IAppSession session)
{
while (!ConnectSessions.ContainsKey(session.SessionID))
{
//添加不成功则重复添加
if (!ConnectSessions.TryAdd(session.SessionID, session))
Thread.Sleep(1);
}
return true;
}
/// <summary>
/// 移除Session
/// </summary>
/// <param name="session"></param>
/// <returns></returns>
public static async Task<bool> RemoveSession(IAppSession session)
{
return await Task.Run(() =>
{
while (ConnectSessions.ContainsKey(session.SessionID))
{
//移除不成功则重复移除
if (!ConnectSessions.TryRemove(session.SessionID, out _))
Thread.Sleep(1);
}
return true;
});
}
/// <summary>
/// 发送
/// </summary>
/// <param name="body"></param>
/// <returns></returns>
public static async Task<bool> SendAsync(string ip, string body)
{
if (await IsConnected(ip))
{
var session = await GetSession(ip);
if (session != null)
{
var data = EncSocketStr(body);
var byteBody = PackData(data);
await session.SendAsync(byteBody);
}
}
return true;
}
/// <summary>
/// 打包服务器数据,防止乱码
/// </summary>
/// <param name="message">数据</param>
/// <returns>数据包</returns>
private static byte[] PackData(string message)
{
byte[] contentBytes = null;
byte[] temp = Encoding.UTF8.GetBytes(message);
if (temp.Length < 126)
{
contentBytes = new byte[temp.Length + 2];
contentBytes[0] = 0x81;
contentBytes[1] = (byte)temp.Length;
Array.Copy(temp, 0, contentBytes, 2, temp.Length);
}
else if (temp.Length < 0xFFFF)
{
contentBytes = new byte[temp.Length + 4];
contentBytes[0] = 0x81;
contentBytes[1] = 126;
contentBytes[2] = (byte)(temp.Length & 0xFF);
contentBytes[3] = (byte)(temp.Length >> 8 & 0xFF);
Array.Copy(temp, 0, contentBytes, 4, temp.Length);
}
else
{
// 暂不处理超长内容
}
return contentBytes;
}
/// <summary>
/// 停止服务,关闭所有链接
/// </summary>
/// <returns></returns>
public static async Task<bool> DisConnects()
{
return await Task.Run(() =>
{
bool isSuccess = false;
foreach (var sessions in ConnectSessions)
{
sessions.Value.CloseAsync(CloseReason.ServerShutdown);
}
ConnectSessions.Clear();
return isSuccess;
});
}
/// <summary>
/// IP是否链接
/// </summary>
/// <param name="ip"></param>
/// <returns></returns>
private static async Task<bool> IsConnected(string ip)
{
return await Task.Run(() =>
{
foreach (var keyValuePair in ConnectSessions)
{
var endpoint = (IPEndPoint)keyValuePair.Value.RemoteEndPoint;
if (endpoint.Address.ToString() == ip)
return true;
}
return false;
});
}
/// <summary>
/// 获取链接对象
/// </summary>
/// <param name="deviceIp"></param>
/// <returns></returns>
private static async Task<IAppSession?> GetSession(string ip)
{
return await Task.Run(() =>
{
foreach (var keyValuePair in ConnectSessions)
{
var endpoint = (IPEndPoint)keyValuePair.Value.RemoteEndPoint;
if (endpoint.Address.ToString() == ip)
return keyValuePair.Value;
}
return null;
});
}
/// <summary>
/// 发送消息加标识
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string EncSocketStr(string str)
{
string returnstr = "";
int lenght = str.Length;
returnstr = lenght.ToString().PadLeft(5, '0');
returnstr = $"{returnstr}#{str}#";
return returnstr;
}
/// <summary>
/// 接收消息解除标识
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string DecSocketStr(string str)
{
string returnstr = "";
string[] sArray = str.Split('#');
int lenght = Convert.ToInt32(sArray[0]);
if (lenght == sArray[1].Length)
{
returnstr = sArray[1];
}
return returnstr;
}
}
}
Project.Pack中代码实现
引用公共包
SocketTest中代码实现
AutoFacExtensions类
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyModel;
using System.Reflection;
namespace SocketTest.Extentions
{
/// <summary>
/// AutoFacExtensions
/// </summary>
public static class AutoFacExtensions
{
/// <summary>
/// 初始化
/// </summary>
public static void AddAutoFac(this WebApplicationBuilder builder)
{
//Autofac
var assemblies = DependencyContext.Default.RuntimeLibraries
.Where(x => x.Name.StartsWith("Project.Application"))
.Select(o => Assembly.Load(new AssemblyName(o.Name))).ToArray();
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
}
}
}
Program 中添加
builder.Services.AddHostedService<SocketServerHostedService>();
WindowsSocket客户端代码实现
引用NuGet包
SocketExtension 类
using SuperSocket.ClientEngine;
using System;
using System.Net;
using System.Text;
namespace WindowsSocket
{
public class SocketExtension
{
static SocketExtension()
{
}
/// <summary>
/// Socket是否链接成功
/// </summary>
public static bool SocketStatus
{
get
{
return TcpSession != null && TcpSession.IsConnected;
}
}
/// <summary>
/// 启动屏Socket
/// </summary>
private static AsyncTcpSession TcpSession;
public static void InitSocket()
{
var socketIp = "192.168.31.71"; //服务器ip
var socketPort = "8552"; //服务器端口
if (TcpSession != null)
{
TcpSession.Close();
TcpSession = null;
}
try
{
IPAddress ip = IPAddress.Parse(socketIp.Trim());
IPEndPoint remotePoint = new IPEndPoint(ip, int.Parse(socketPort));
TcpSession = new AsyncTcpSession();
TcpSession.Connect(remotePoint);
TcpSession.Connected += TcpSession_Connected;
TcpSession.DataReceived += TcpSession_DataReceived;
TcpSession.Closed += TcpSession_Closed;
}
catch (System.StackOverflowException exception)
{
//Logger.Error("尝试连接服务,稍后将重新尝试链接。", exception);
}
catch (Exception exception)
{
//Logger.Error("尝试连接屏服务,稍后将重新尝试链接。", exception);
}
}
private static void TcpSession_Connected(object sender, EventArgs e)
{
//Logger.Info("连接Socket服务成功");
if (TcpSession != null && TcpSession.IsConnected)
{
Send("100101");//发送心跳
}
}
private static void TcpSession_DataReceived(object sender, DataEventArgs e)
{
try
{
//客户端接收:
string body = Encoding.UTF8.GetString(e.Data);
if (!string.IsNullOrEmpty(body))
{
//Logger.Info($"收到命令:{body}"); 处理逻辑代码
}
}
catch (Exception exception)
{
//Logger.Error(exception);
}
}
private static void TcpSession_Closed(object sender, EventArgs e)
{
//Logger.Info("Socket连接已关闭");
}
public static void Send(string body)
{
//Logger.Info($"Socket连接状态:{TcpSession?.IsConnected}");
if (TcpSession != null && TcpSession.IsConnected)
{
//Logger.Info("Socket发送命令:" + body);
var data = System.Text.Encoding.UTF8.GetBytes(body);
TcpSession.Send(data, 0, data.Length);
}
}
}
}
JS客户端代码实现
<html>
<head >
<meta charset="UTF-8" />
<title>测试Socket</title>
</head>
<body>
<h1>测试Socket</h1>
<div id="stock-price"></div>
<button onclick="handleButtonClick()">点击我</button>
<script>
// 创建WebSocket连接
const socket = new WebSocket('ws://192.168.31.71:8552');
// 监听连接建立事件
socket.onopen = function(event) {
console.log('WebSocket连接已建立');
console.log('连接状态:', socket.readyState );
//socket.send('1001');//发送消息给后台
console.log('连接状态:', socket.readyState );
};
// 监听消息接收事件
socket.onmessage = function(event) {
const data = event.data;
console.log('收到的服务端消息:', data );
};
// 监听连接关闭事件
socket.onclose = function(event) {
console.log('WebSocket连接已关闭');
};
// 监听连接错误事件
socket.onerror = function(event) {
console.error('WebSocket连接错误');
};
function handleButtonClick() {
alert('按钮被点击了!');
socket.send('1');//发送消息给后台
}
</script>
</body>
</html>