即时通信常用手段

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).服务端获取连接对象并存储到连接池中

1
CONNECT_POOL.Add(user, socket);

3).连接对象开始监听(每个客户端与服务器保存长链接)

复制代码 代码如下:
WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, CancellationToken.None);

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连接对象继续监听

复制代码 代码如下:
WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, CancellationToken.None);

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服务端即时通信实例的更多相关文章

  1. WebSocket实现Web端即时通信

    前言 WebSocket 是HTML5开始提供的一种在浏览器和服务器间进行全双工通信的协议.目前很多没有使用WebSocket进行客户端服务端实时通信的web应用,大多使用设置规则时间的轮询,或者使用 ...

  2. WebSocket实现web即时通信(后端nodejs实现)

    WebSocket实现web即时通信 一.首先看一下,HTTP.ajax轮询.long poll和WebSocket的区别: 1.HTTP 协议(短连接):一个 Request 一个 Response ...

  3. C# WebSocket 服务端示例代码 + HTML5客户端示例代码

    WebSocket服务端 C#示例代码 using System; using System.Collections.Generic; using System.Linq; using System. ...

  4. 用PHP的socket实现客户端到服务端的通信

    服务端 <?php error_reporting(E_ALL); set_time_limit(0); ob_implicit_flush(); //本地IP $address = 'loca ...

  5. nodejs搭建简单的websocket服务端

    创建websocket服务端使用了nodejs-websocket ,首先要安装nodejs-websocket,在项目的目录下: npm install nodejs-websocket 1.搭建w ...

  6. WebSocket服务端

    http://blog.csdn.net/qq_20282263/article/details/54310737 C# 实现WebSocket服务端 原创 2017年01月10日 09:22:50 ...

  7. 用nodejs快速实现websocket服务端(带SSL证书生成)

    有不少公司将nodejs的socket.io作为websocket的解决方案,很遗憾的是socket.io是对websocket的封装,并不支持html5原始的websocket协议,微信小程序使用的 ...

  8. linux网络编程之用socket实现简单客户端和服务端的通信(基于UDP)

    单客户端和服务端的通信(基于UDP)   代码 服务端代码socket3.c #include<sys/types.h> #include<sys/socket.h> #inc ...

  9. asp.net网站作为websocket服务端的应用该如何写

    最近被websocket的一个问题困扰了很久,有一个需求是在web网站中搭建websocket服务.客户端通过网页与服务器建立连接,然后服务器根据ip给客户端网页发送信息. 其实,这个需求并不难,只是 ...

随机推荐

  1. windows 10占用cpu和内存过高

    自从安装了windows 10,开机之后cpu和内存一直占用很高,尤其是system进程,一直占cpu在13%左右,上网查到一个解决方式,如下: cpu瞬间变为1%

  2. net.sf.json和 com.fasterxml.jackson中对象转json的区别

    近期做项目的时候,发现使用net.sf.json包中的JSONObject或JSONArray将对象转为json数据结构存在一个坑.当对String类型的属性赋值为null情况下,转为json结构为& ...

  3. 实时刷新winform中的某一个控件上的文字

    需要注意的是,必须从UI线程,另外启动一个线程才可以. 在新线程调用异步刷新就OK了 Thread thread; private void button1_Click(object sender, ...

  4. Redis之字符串

    Redis 字符串 String 是redis最基本的类型,value 不仅可以是 String,也可以是数字. 使用 Strings 类型,可以完全实现目前 Memcached 的功能,并且效率更高 ...

  5. 一种不太合规的PreparedStatement使用方式

    这是一种不太合规的PreparedStatement调用使用方式 , 没有让Dao单独执行它单纯的任务. AccountDao.java package heartl_jdbc; /** * 银行操作 ...

  6. IDEA 搭建授权服务器

    引用地址  http://blog.lanyus.com/archives/317.html 今天突出发现基本公开的激活地址都被屏蔽了(IDEA 2017.3 版本) 记录一下 docker pull ...

  7. BZOJ 1042: [HAOI2008]硬币购物(容斥原理)

    http://www.lydsy.com/JudgeOnline/problem.php?id=1042 题意: 思路: 如果不考虑硬币个数的话,这就是一道完全背包的题目. 直接求的话行不通,于是这里 ...

  8. Notepad++ 管理工程--转载

    http://blog.csdn.net/cashey1991/article/details/7001385 @1.首先从下面这个菜单打开工程panel @2.在工程panel的“Workspace ...

  9. 利用Minhash和LSH寻找相似的集合(转)

    问题背景 给出N个集合,找到相似的集合对,如何实现呢?直观的方法是比较任意两个集合.那么可以十分精确的找到每一对相似的集合,但是时间复杂度是O(n2).当N比较小时,比如K级,此算法可以在接受的时间范 ...

  10. Spring MVC 实现跨域资源 CORS 请求

    说到 AJAX 跨域,很多人最先想到的是 JSONP.的确,JSONP 我们已经十分熟悉,也使用了多年,从本质上讲,JSONP 的原理是给页面注入一个 <script>,把远程 JavaS ...