.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).客户端请求连接
2).服务端获取连接对象并存储到连接池中
|
1
|
CONNECT_POOL.Add(user, socket); |
3).连接对象开始监听(每个客户端与服务器保存长链接)
4).客户端A发送消息给B
|
1
|
ws.send($("#to").val() + "|" + $('#content').val()); |
5).服务端A的连接对象监听到来自A的消息
|
1
|
string userMsg = Encoding.UTF8.GetString(buffer.Array, 0, result.Count); |
6).解析消息体(B|你好我是A)得到接收者ID,根据接收者ID到连接池中查找B的服务端连接对象,并通过B的连接对象将消息推送给B客户端
|
1
2
3
|
WebSocket destSocket = CONNECT_POOL[descUser];await destSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None); |
7).服务端A连接对象继续监听
8).B客户端接收到推送过来的消息
|
1
2
3
4
5
|
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下即可)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
<!DOCTYPE html><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。记录当前离线消息的时间、内容)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
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服务端即时通信实例的更多相关文章
- WebSocket实现Web端即时通信
前言 WebSocket 是HTML5开始提供的一种在浏览器和服务器间进行全双工通信的协议.目前很多没有使用WebSocket进行客户端服务端实时通信的web应用,大多使用设置规则时间的轮询,或者使用 ...
- WebSocket实现web即时通信(后端nodejs实现)
WebSocket实现web即时通信 一.首先看一下,HTTP.ajax轮询.long poll和WebSocket的区别: 1.HTTP 协议(短连接):一个 Request 一个 Response ...
- C# WebSocket 服务端示例代码 + HTML5客户端示例代码
WebSocket服务端 C#示例代码 using System; using System.Collections.Generic; using System.Linq; using System. ...
- 用PHP的socket实现客户端到服务端的通信
服务端 <?php error_reporting(E_ALL); set_time_limit(0); ob_implicit_flush(); //本地IP $address = 'loca ...
- nodejs搭建简单的websocket服务端
创建websocket服务端使用了nodejs-websocket ,首先要安装nodejs-websocket,在项目的目录下: npm install nodejs-websocket 1.搭建w ...
- WebSocket服务端
http://blog.csdn.net/qq_20282263/article/details/54310737 C# 实现WebSocket服务端 原创 2017年01月10日 09:22:50 ...
- 用nodejs快速实现websocket服务端(带SSL证书生成)
有不少公司将nodejs的socket.io作为websocket的解决方案,很遗憾的是socket.io是对websocket的封装,并不支持html5原始的websocket协议,微信小程序使用的 ...
- linux网络编程之用socket实现简单客户端和服务端的通信(基于UDP)
单客户端和服务端的通信(基于UDP) 代码 服务端代码socket3.c #include<sys/types.h> #include<sys/socket.h> #inc ...
- asp.net网站作为websocket服务端的应用该如何写
最近被websocket的一个问题困扰了很久,有一个需求是在web网站中搭建websocket服务.客户端通过网页与服务器建立连接,然后服务器根据ip给客户端网页发送信息. 其实,这个需求并不难,只是 ...
随机推荐
- Migrating from Spring 3 to Spring 4 - org.springframework.scheduling.quartz.CronTriggerBean
I'm trying to migrate from spring 3.0.5 to spring 4.1.X . Spring 3 has Class named as "org.spri ...
- double、float等多字节数据处理
一.常规的多字节: 有2,4,8字节 float和double是具有自身算法的数据类型,和其他整型不一样[整型数据,可以直接通过移位来进行计算值的大小,float和double不行] 值 = 尾数x ...
- Maven .m2文件夹创建
settings.xml存在于两个地方: 1.安装的地方:$M2_HOME/conf/settings.xml 2.用户的目录:${user.home}/.m2/settings.xml 前者又被叫做 ...
- String StringBuilder StringBuffer 对比 总结得非常好
转自:http://www.iteye.com/topic/522167 作者:每次上网冲杯Java时,都能看到关于String无休无止的争论.还是觉得有必要让这个讨厌又很可爱的String美眉,赤裸 ...
- Coursera SDN M1.1 SDN History: Central Control
source Structure 1.讨论SDN的时间线,从1980s至今. 2.认识到SDN背后的原则和idea. 3.识别SDN起源的架构主题. NOTE Four Chapter in SDN ...
- Android 旋转、平移、缩放和透明度渐变的补间动画
补间动画就是通过对场景里的对象不断进行图像变化来产生动画效果.在实现补间动画时,只需要定义开始和结束的“关键帧”,其他过渡帧由系统自动计算并补齐.在Android中,提供了以下4种补间动画. **1. ...
- python 列表求和
def sum_list(items): sum_numbers = for x in items: sum_numbers += x return sum_numbers print(sum_lis ...
- 过了所有技术面,却倒在 HR 一个问题上
面试问离职原因,这是我们广大程序员朋友面试时逃不开的问题,如果答得不好,可能就影响了你整个的面试结果. 最近在群里,我也看到大家在讨论这个问题,其中有个朋友的回复很有感触,我分享给大家看一下. 如图, ...
- Python 编程核心知识体系-函数(二)
函数
- iOS笔记之文件读写
最近做的项目中要存储一组图片数据,图片带有name,date等属性,处理办法是讲image图片直接存在沙盒documents文件中,再使用plist文件存储图片属性和image路径. 存入图片: - ...