(一)WebSocket(webform+一般处理程序ashx)
(一)wesocket能解决哪些问题,websocket实现原理,以及缺陷
1:实现后端主动向前端推送信息
2:一个客户端主动向其他一个或者多个客户端推送消息(避免了其他客户端轮询获取信息,减小了服务器端得压力)
3:底层得实现原理是客户端先和服务端建立一次长链接,之后服务端和客户端,一个客户端和其他服务端的消息相互推送都是通过呢个各自的长链接进行传递
4:服务端保存长链接的方式: 通常以用户id作为标识,socket长连接作为value进行存储
5:支持系统必须是windows8及windows8以上,windows server必须是2012以上,对低版本浏览器不是很兼容,浏览器版本不能太低
前端页面逻辑
<body>
<div>
<div><span>提示:</span><span id="j_notice"></span></div>
<div><span>提示:</span><span id="j_heart"></span></div>
<div><span>提示:</span><span id="j_wlyc"></span></div>
<div style="margin-top: 20px">
<input type="text" name="name" value="" placeholder="请输入登录标识" id="j_userKey" />
<button id="j_close">关闭连接</button>
</div>
<div style="margin-top: 20px">
<input type="text" value="" placeholder="请输入发送内容" id="j_content" />
<button id="j_send">群发</button>
</div>
<div style="margin-top: 20px">
<input type="text" value="" placeholder="请输入接收人标识" id="j_receiveUserKey" />
<button id="j_send2">单发</button>
</div>
<div>
<ul id="j_Msg"></ul>
</div>
</div>
</body>
前端js逻辑
(function (w) {
//(一)心跳检测,检测后端是否还可以链接
var heartCheck = {//心跳检测
timeout: 10000,
timeoutObj: null,
servetTimeoutObj: null,
start: function () {
var self = this;
this.timeoutObj && clearTimeout(this.timeoutObj);//如果不加this.timeoutObj &&,clearTimeout以后timeoutObj对象就不存在了,再次setTimeout时就会因找不到对象报错
this.servetTimeoutObj && clearTimeout(this.servetTimeoutObj);//同上
this.timeoutObj = setTimeout(function () {
//10秒后执行,执行成功后,在ws.onmessage里面调用heartCheck.start();继续心跳检测,此时clearTimeout(this.servetTimeoutObj)下面的servetTimeoutObj就被清掉了,不会执行了
//如果执行失败,就不会调用heartCheck.start();不会执行clearTimeout(this.servetTimeoutObj),就会打印 您暂时无法获取服务端推送的最新消息,请检查网络是否异常! 这句话
ws.send("heartcheck");
self.servetTimeoutObj = setTimeout(function () {
$("#j_heart").append("您暂时无法获取服务端推送的最新消息,请检查网络是否异常!")
ws.close();
//google(会进onclose函数),在onclose里面调用重连方法mainUitls.reconnect();,重连失败进入onerror函数,在onerror里面反复重连操作如果没连上
//ie
//直接进入onerror函数,在reconnect函数李设定的时间后再次从连
}, self.timeout)
}, this.timeout)
}
}
//声明全局对象
var ws;
var lockReconnect = false;
var tt = null;
var mainUitls = {
//(二)初始话基本事件
init: function (dom) {
this.createWebsocket(dom);
this.initClick();
},
//(三)重连操作
reconnect: function () {
if (lockReconnect) {//防止多次创建链接,只有当一个链接创建有了结果才允许下次创建,防止多次执行创建链接的操作(被多个事件执行调用)
return;
};
lockReconnect = true;
tt && clearTimeout(tt);//将tt设置为underfind,如果直接clearTimeout(tt) ,tt就被清掉不存在了,下次给tt赋值就会报错
tt = setTimeout(function () {//进入该方法20秒后进行重连,链接失败后在进入ws.onerror继续重连
mainUitls.createWebsocket($("#j_userKey").val());
lockReconnect = false;
}, 20000)
},
//(四)创建长链接,以及跟链接相关的事件
createWebsocket: function (dom) {
//1:建立长链接:
ws = new WebSocket('ws://' + window.location.hostname + ':49425/ashx/WebsocketAshx3.ashx?userKey=' + dom);//如果是本地IP就是window.location.hostname;
//2:链接成功后要做心跳检测
ws.onopen = function () {
$('#j_notice').html('已经连接');
heartCheck.start();//链接成功以后开始心跳检测
}
//3:接收服务端或者其他客户端推送过来的消息
ws.onmessage = function (evt) {
if (evt.data == "heartchecksuccess") {
$("#j_heart").append("<li>链接正常</li>");
heartCheck.start();//接受到服务器信息表示链接是通的,过一会儿再次进行心跳检测
} else {
$("#j_Msg").append("<li>" + evt.data + "</li>");
}
}
//4:网络异常后过一会需要重连操作,就是突然网络就好了
ws.onerror = function (evt) {
$('#j_wlyc').append("链接出现异常,请检查网络!");
mainUitls.reconnect();
}
//5:链接关闭后要执行的函数,比如很久没得到服务器的响应,可以暂时断开,过一会在进行链接reconnect();
ws.onclose = function () {
$('#j_notice').html("连接断开");
mainUitls.reconnect();
}
},
//(五)各种点击事件
initClick: function () {
//1:关闭链接
$("#j_close").on("click", function () {
ws.close();
});
//2:群发消息
$("#j_send").on("click", function () {
if ($("#j_userKey").val() == "") {
$('#j_notice').html("请输入用户标识");
return;
}
//表示与服务器已经建立好连接
if (ws.readyState == WebSocket.OPEN) {
var content = $("#j_userKey").val() + "--alluser" + $('#j_content').val();
ws.send(content);
}
//表示与服务器连接已经断开
else if (ws.readyState == WebSocket.CLOSED) {
$('#j_notice').html('与服务器连接已经断开');
}
//表示正在尝试与服务建立连接
else if (ws.readyState == WebSocket.CONNECTING) {
$('#j_notice').html('正在尝试与服务建立连接');
}
//正在关闭与服务器连接
else if (ws.readyState == WebSocket.CLOSING) {
$('#j_notice').html('正在关闭与服务器连接');
}
});
//3:单发消息
$("#j_send2").on("click", function () {
if ($("#j_userKey").val() == "") {
$('#j_notice').html("请输入登陆标识");
return;
}
var reciveiduser = $('#j_receiveUserKey').val();
if (reciveiduser == "") {
$('#j_notice').html('请输入接收人的标识');
return;
}
//下面对内容进行拼接
var loginuser = $("#j_userKey").val();
var finalMsg = loginuser + "--touserid" + reciveiduser + "--touserid" + $('#j_content').val();
//表示与服务器已经建立好连接
if (ws.readyState == WebSocket.OPEN) {
ws.send(finalMsg);
}
//表示与服务器连接已经断开
else if (ws.readyState == WebSocket.CLOSED) {
$('#j_notice').html('与服务器连接已经断开');
}
//表示正在尝试与服务建立连接
else if (ws.readyState == WebSocket.CONNECTING) {
$('#j_notice').html('正在尝试与服务建立连接');
}
//正在关闭与服务器连接
else if (ws.readyState == WebSocket.CLOSING) {
$('#j_notice').html('正在关闭与服务器连接');
}
});
}
};
w.mainUitls = mainUitls;
})(window)
//(六)初始化方法,并生成随机数据的用户标识
$(function () {
var dom1 = Math.ceil(Math.random() * 10);//6;
$("#j_userKey").val(dom1)
mainUitls.init(dom1);
});
后端逻辑
//用户登记标识
private string userKey = "";
//进程方法
public void ProcessRequest(HttpContext context)
{
if (context.IsWebSocketRequest)
{
this.userKey = context.Request.QueryString["userKey"];
context.AcceptWebSocketRequest(ProcessChat);
}
else
{
context.Response.Write("不是WebSocket请求");
}
}
//调用方法
private async Task ProcessChat(AspNetWebSocketContext context)
{
WebSocket socket = context.WebSocket;
CancellationToken cancellationToken = new CancellationToken();//设置超时操作之类的(比如设置120秒超时)
userKey.AddUser(socket);//保存用户的socket,userKey作为键,socket作为值
while (socket.State == WebSocketState.Open)//socket处于打开状态
{
ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]);
WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, cancellationToken);//接受指令处于等待中,一旦有消息过来就会往下走,否则就处于等待中
if (result.MessageType == WebSocketMessageType.Close)//如果传过来的是关闭指令
{
if (socket.CloseStatus == System.Net.WebSockets.WebSocketCloseStatus.EndpointUnavailable)//重新加载浏览器进行的关闭操作(只移除当前)
{
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, String.Empty, cancellationToken);
}
if (socket.CloseStatus == System.Net.WebSockets.WebSocketCloseStatus.Empty)//点击了关闭链接后的关闭(可以关闭当前用户所在的所有浏览器信息)
{
WebManager3.CloseWebsocket(userKey, cancellationToken);
}
}
else//传过来的是发送消息的指令,做消息分类
{
string userMsg = Encoding.UTF8.GetString(buffer.Array, 0, result.Count);
if (userMsg == "heartcheck")//心跳检测传过来的
{
ArraySegment<byte> buffer1 = new ArraySegment<byte>(new byte[2048]);
buffer1 = new ArraySegment<byte>(Encoding.UTF8.GetBytes("heartchecksuccess"));
await socket.SendAsync(buffer1, WebSocketMessageType.Text, true, cancellationToken);//给前端返回后端还处于能链接的状态
}
else if (userMsg.Contains("--touserid"))//单发
{
//截取内容和接受者的标记
var array = userMsg.Split(new string[] { "--touserid" }, StringSplitOptions.None);//发送过来的信息转数组
var receiveNotice = array[1] ;//接收者id:5
string content = $"用户【{userKey}】 发来消息:{array[2]},当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}";
WebManager3.SendSingleMessage(cancellationToken, content, receiveNotice);
}
else//群发
{
string content = $"用户【{userKey}】 群发消息:{userMsg},当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}";
WebManager3.SendAllMessage(cancellationToken, content, userKey);
}
}
}
}
后端底层类库
public static class WebManager{
public static List<WebsocketObj> websocketObjs = new List<WebsocketObj>();//存储websocket的集合,如果是分布式部署,还需要存到redis缓存里面
//存储当前用户的socket
public static void AddUser(this string userKey, WebSocket socket)
{
WebsocketObj websocketObj = new WebsocketObj();
websocketObj.userid = userKey;
websocketObj.webSocket = socket;
websocketObjs.Add(websocketObj);
}
//某个人登陆系统给其他所有人发送信息
public static async Task SendLoginSucesssNotice(CancellationToken cancellationToken, string content, string myuserkey)
{
ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]);
buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(content));
List<WebsocketObj> websocketObjs1 = websocketObjs.Where(m => m.userid!=myuserkey).ToList();
if (websocketObjs1.Count > 0)
{
foreach (var item in websocketObjs1)
{
try
{
await item.webSocket.SendAsync(buffer, WebSocketMessageType.Text, true, cancellationToken);
}
catch (Exception e)//防止当某个websocket断开,如果还在发送,就会导致其他的没断开的也发送不了
{
}
}
}
}
//某人退出后给所有用户发送一个消息,由于当前的退出的页面的socket已经断开,所以不会收到信息,就不用在集合排除自己
public static async Task SendOutNotice(CancellationToken cancellationToken, string content)
{
ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]);
buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(content));
//离开提醒
if (websocketObjs.Count > 0)
{
foreach (var socket in websocketObjs)
{
try
{
await socket.webSocket.SendAsync(buffer, WebSocketMessageType.Text, true, cancellationToken);
}
catch (Exception e)//防止当某个websocket断开,如果还在发送,就会导致其他的没断开的也发送不了
{
}
}
}
}
//群发信息,不包括自己
public static void SendAllMessage(CancellationToken cancellationToken, string content, string myUserKey)
{
ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]);
buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(content));
//群发消息,但不包括自己
List<WebsocketObj> websocketObjs1 = websocketObjs.Where(m => m.userid!= myUserKey).ToList();
if (websocketObjs1.Count > 0)
{
foreach (var item in websocketObjs1)
{
try
{
item.webSocket.SendAsync(buffer, WebSocketMessageType.Text, true, cancellationToken);
}
catch (Exception e)
{
}
}
}
}
//单发消息
public static void SendSingleMessage(CancellationToken cancellationToken, string content, string receiveKey)
{
ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]);
buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(content));
//单发消息
List<WebsocketObj> websocketObjs1 = websocketObjs.Where(m => m.userid==receiveKey).ToList();
foreach (var item in websocketObjs1)
{
try
{
item.webSocket.SendAsync(buffer, WebSocketMessageType.Text, true, cancellationToken);
}
catch (Exception e)
{
}
}
}
//给自己发送消息,(比如心跳检测的时候)
public static void SendToMySelf(CancellationToken cancellationToken, string content, string userKey)
{
ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]);
buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(content));
List<WebsocketObj> websocketObjs1 = websocketObjs.Where(m => m.userid==userKey).ToList();
foreach (var item in websocketObjs1)
{
try
{
item.webSocket.SendAsync(buffer, WebSocketMessageType.Text, true, cancellationToken);
}
catch (Exception e)
{
}
}
}
//关闭指定用户的所有的socket
public static void CloseWebsocket(string userid, CancellationToken cancellationToken)
{
List<WebsocketObj> websocketObjs1 = websocketObjs.Where(m => m.userid==userid).ToList();
if (websocketObjs1.Count > 0)
{
foreach (var item in websocketObjs1)
{
try
{
item.webSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait();
}
catch (Exception e)
{
}
}
}
}
}
//存储socket的类
public class WebsocketObj
{
public string userid { get; set; }
public WebSocket webSocket { get; set; }
}
非服务器iis对最大长连接的限制个数为10,修改呢个限制的方法是在iis的应用程序池-设置应用程序池默认设置-进程模型-最大工作进程数进行修改
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix