.NET实现WebSocket服务端即时通信实例
即时通信常用手段
1.第三方平台 谷歌、腾讯 环信等多如牛毛,其中谷歌即时通信是免费的,但免费就是免费的并不好用。其他的一些第三方一般收费的,使用要则限流(1s/限制x条消息)要么则限制用户数。
但稳定性什么都还不错,又能将服务压力甩出
2.system.net.sockets.socket,也能写一套较好的服务器端。在.net 4.5之前用较多,使用起来麻烦。需要对数据包进行解析等操作(但貌似网上有对超长包的处理方法)
3.system.net.websockets.websocket,这个,是.net 4.5出来的东西,对服务器环境也有所要求,iis8及以上。意味着windows server2008r2自带的iis不支持,windows8及server2012以上自带的iis可以。本文主要将这种方式的实例
完整流程
1).客户端请求连接
ws = new websocket('ws://' + window.location.hostname + ':' + window.location.port + '/handler1.ashx?user=' + $("#user").val());
2).服务端获取连接对象并存储到连接池中
connect_pool.add(user, socket);
3).连接对象开始监听(每个客户端与服务器保存长链接)
websocketreceiveresult result = await socket.receiveasync(buffer, cancellationtoken.none);
4).客户端a发送消息给b
ws.send($("#to").val() + "|" + $('#content').val());
5).服务端a的连接对象监听到来自a的消息
string usermsg = encoding.utf8.getstring(buffer.array, 0, result.count);
6).解析消息体(b|你好我是a)得到接收者id,根据接收者id到连接池中查找b的服务端连接对象,并通过b的连接对象将消息推送给b客户端
websocket destsocket = connect_pool[descuser]; await destsocket.sendasync(buffer, websocketmessagetype.text, true, cancellationtoken.none);
7).服务端a连接对象继续监听
websocketreceiveresult result = await socket.receiveasync(buffer, cancellationtoken.none);
8).b客户端接收到推送过来的消息
ws.onmessage = function (evt) { $('#msg').append('<p>' + evt.data + '</p>'); }
下面则是完整代码
客户端部分
客户端异常简单,正常情况直接用websocket,然后监听websocket的几个事件就ok。连接的时候可将当前连接者的id传入(用户编号),发送消息的时候 采用 “接收者id|我是消息内容” 这种方式,如“a|a你好,我是b!”
但如用移动端使用还是有一些常见的场景需要处理下的
1:手机关屏幕,ios关掉屏幕的时候websocket会立即失去连接,android则会等待一段时间才会失去连接。服务器端能检测到失去连接
2:网络不稳定,断网情况websocket也不会立即失去连接,服务器端不能知道。(可以服务端设计心跳机制,定时给连接池中的用户发送消息,来检测用户是否保持连接)
3:其他等等...(突然关机、后台结束应用)
无论哪种,客户端在发送消息(或者网络恢复连接、亮屏)的时候可以先判断ws的状态,如果不是连接状态则需要重连(new下即可)
<!doctype html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"/> <title></title> <script src="jquery-1.11.3.min.js"></script> <script> var ws; $().ready(function () { $('#conn').click(function () { ws = new websocket('ws://' + window.location.hostname + ':' + window.location.port + '/handler1.ashx?user=' + $("#user").val()); $('#msg').append('<p>正在连接</p>'); ws.onopen = function () { $('#msg').append('<p>已经连接</p>'); } ws.onmessage = function (evt) { $('#msg').append('<p>' + evt.data + '</p>'); } ws.onerror = function (evt) { $('#msg').append('<p>' + json.stringify(evt) + '</p>'); } ws.onclose = function () { $('#msg').append('<p>已经关闭</p>'); } }); $('#close').click(function () { ws.close(); }); $('#send').click(function () { if (ws.readystate == websocket.open) { ws.send($("#to").val() + "|" + $('#content').val()); } else { $('#tips').text('连接已经关闭'); } }); }); </script> </head> <body> <div> <input id="user" type="text" /> <input id="conn" type="button" value="连接" /> <input id="close" type="button" value="关闭"/><br /> <span id="tips"></span> <input id="content" type="text" /> <input id="send" type="button" value="发送"/><br /> <input id="to" type="text" />目的用户 <div id="msg"> </div> </div> </body> </html>
服务器端部分
服务器端使用handler(也可用webapi)来做,主要用websocket的类来实现。代码中都有相对详细的注释,这边只说一些需要注意的问题
1:dictionary<string,websocket> connect_pool:用户连接池。请求handler的时候会将当前连接者的用户id传入,服务器端维护着所有已连接的用户id和当前用户的websocket连接对象
2:dictionary<string,list<messageinfo>> message_pool:离线消息池。如果a->b发送消息,b当前因为某种原因没在线(突然断网/黑屏等原因),会将这条消息先保存起来(2天),待b连接后立马将b的离线消息推送给他。(2:messageinfo:离线entity。记录当前离线消息的时间、内容)
using system; using system.collections; using system.collections.generic; using system.linq; using system.net.websockets; using system.text; using system.threading; using system.threading.tasks; using system.web; using system.web.websockets; namespace webapplication1 { /// <summary> /// 离线消息 /// </summary> public class messageinfo { public messageinfo(datetime _msgtime, arraysegment<byte> _msgcontent) { msgtime = _msgtime; msgcontent = _msgcontent; } public datetime msgtime { get; set; } public arraysegment<byte> msgcontent { get; set; } } /// <summary> /// handler1 的摘要说明 /// </summary> public class handler1 : ihttphandler { private static dictionary<string, websocket> connect_pool = new dictionary<string, websocket>();//用户连接池 private static dictionary<string, list<messageinfo>> message_pool = new dictionary<string, list<messageinfo>>();//离线消息池 public void processrequest(httpcontext context) { if (context.iswebsocketrequest) { context.acceptwebsocketrequest(processchat); } } private async task processchat(aspnetwebsocketcontext context) { websocket socket = context.websocket; string user = context.querystring["user"].tostring(); try { #region 用户添加连接池 //第一次open时,添加到连接池中 if (!connect_pool.containskey(user)) connect_pool.add(user, socket);//不存在,添加 else if (socket != connect_pool[user])//当前对象不一致,更新 connect_pool[user] = socket; #endregion #region 离线消息处理 if (message_pool.containskey(user)) { list<messageinfo> msgs = message_pool[user]; foreach (messageinfo item in msgs) { await socket.sendasync(item.msgcontent, websocketmessagetype.text, true, cancellationtoken.none); } message_pool.remove(user);//移除离线消息 } #endregion string descuser = string.empty;//目的用户 while (true) { if (socket.state == websocketstate.open) { arraysegment<byte> buffer = new arraysegment<byte>(new byte[2048]); websocketreceiveresult result = await socket.receiveasync(buffer, cancellationtoken.none); #region 消息处理(字符截取、消息转发) try { #region 关闭socket处理,删除连接池 if (socket.state != websocketstate.open)//连接关闭 { if (connect_pool.containskey(user)) connect_pool.remove(user);//删除连接池 break; } #endregion string usermsg = encoding.utf8.getstring(buffer.array, 0, result.count);//发送过来的消息 string[] msglist = usermsg.split('|'); if (msglist.length == 2) { if (msglist[0].trim().length > 0) descuser = msglist[0].trim();//记录消息目的用户 buffer = new arraysegment<byte>(encoding.utf8.getbytes(msglist[1])); } else buffer = new arraysegment<byte>(encoding.utf8.getbytes(usermsg)); if (connect_pool.containskey(descuser))//判断客户端是否在线 { websocket destsocket = connect_pool[descuser];//目的客户端 if (destsocket != null && destsocket.state == websocketstate.open) await destsocket.sendasync(buffer, websocketmessagetype.text, true, cancellationtoken.none); } else { task.run(() => { if (!message_pool.containskey(descuser))//将用户添加至离线消息池中 message_pool.add(descuser, new list<messageinfo>()); message_pool[descuser].add(new messageinfo(datetime.now, buffer));//添加离线消息 }); } } catch (exception exs) { //消息转发异常处理,本次消息忽略 继续监听接下来的消息 } #endregion } else { break; } }//while end } catch (exception ex) { //整体异常处理 if (connect_pool.containskey(user)) connect_pool.remove(user); } } public bool isreusable { get { return false; } } } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读
-
.NET实现WebSocket服务端即时通信实例
-
Python警察与小偷的实现之一客户端与服务端通信实例
-
基于websocket的网页即时通讯(可传附件图片涂鸦、最小化状态通知).NET,winform客户端、服务端
-
微信小程序实现即时通信聊天功能的实例代码
-
nodejs+expressjs+ws实现了websocket即时通讯,服务器和客户端互相通信
-
使用websocket实现浏览器与服务端进行双向通信(Vue springboot)
-
.NET实现WebSocket服务端即时通信实例
-
asp.net mvc 通过api来实现websocket通信
-
.NET实现WebSocket服务端即时通信的实例详解
-
Python警察与小偷的实现之一客户端与服务端通信实例