.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).客户端请求连接

1
ws = new WebSocket('ws://' + window.location.hostname + ':' + window.location.port + '/Handler1.ashx?user=' + $("#user").val());

2).服务端获取连接对象并存储到连接池中

1
CONNECT_POOL.Add(user, socket);

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

1
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
WebSocket destSocket = CONNECT_POOL[descUser];<br>...<br>...<br>await destSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);

7).服务端A连接对象继续监听

1
WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, CancellationToken.None);

8).B客户端接收到推送过来的消息

1
2
3
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;
}
}
}
}

PS:对Dictionary的操作可以写两个方案Add和Remove,并用lock,这样子并发会稳定点

.NET 即时通信,WebSocket的更多相关文章

  1. 即时通信WebSocket 和Socket.IO

    WebSocket HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯. 在2008年诞生,2011年成为国际标准. 现在基本所有浏览器都已经支持了. We ...

  2. .NET 即时通信,WebSocket服务端实例

    即时通信常用手段 1.第三方平台 谷歌.腾讯 环信等多如牛毛,其中谷歌即时通信是免费的,但免费就是免费的并不好用.其他的一些第三方一般收费的,使用要则限流(1s/限制x条消息)要么则限制用户数. 但稳 ...

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

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

  4. .NET实现WebSocket服务端即时通信实例

    即时通信常用手段 1.第三方平台 谷歌.腾讯 环信等多如牛毛,其中谷歌即时通信是免费的,但免费就是免费的并不好用.其他的一些第三方一般收费的,使用要则限流(1s/限制x条消息)要么则限制用户数. 但稳 ...

  5. WebSocket实现Web端即时通信

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

  6. WEB即时通信

    问题 传统的浏览器通信方式:基于HTTP协议的请求/响应模式. 早期:通过刷新浏览器来更新服务器端的数据 后来Ajax(XMLHttpRequest是核心):可以不用刷新浏览器更新服务器端数据.但是这 ...

  7. 即时通信之 SignalR

    即时通信在日常的web开发场景中经常使用,本篇主要回顾一下SignalR的实现原理和通过例子说明如何在.NET Core 中使用. SingnalR 应用 需要从服务器进行高频更新的应用程序.例如游戏 ...

  8. java即时通信小例子

    学习java一段时间了,今天写来一个即时通信的小例子练手在其过程中也学到了一些知识拿出来和大家分享,请路过的各位大神多多赐教... 好了下面讲一下基本的思路: 首先,编写服务器端的程序,简单点说吧就是 ...

  9. iOS开发之使用XMPPFramework实现即时通信(三)

    你看今天是(三)对吧,前面肯定有(一)和(二),在发表完iOS开发之使用XMPPFramework实现即时通信(一)和iOS开发之使用XMPPFramework实现即时通信(二)后有好多的小伙伴加我Q ...

随机推荐

  1. [D3] Make D3 v4 Charts Responsive with the viewBox attribute

    Making SVGs responsive is unfortunately not as simple as adding some media queries. This lesson intr ...

  2. Seal Report开放数据库报表工具(.Net)

    Seal Report_20160923 概述:开放数据库报表工具(.Net) 简介:Seal-Report提供了一个完整的框架,用于从任何数据库生成日常报告和仪表板.Seal-Report是Micr ...

  3. jedis连接sentinel示例程序

    1.添加依赖pom.xml <dependency> <groupId>redis.clients</groupId> <artifactId>jedi ...

  4. Android Java Mail与Apache Mail发送邮件对比

    原文链接: 一.邮件简介  一封邮件由很多信息构成,主要的信息如下,其他的暂时不考虑,例如抄送等:  1.收件人:收件人的邮箱地址,例如xxx@xx.com  2.收件人姓名:大部分的邮件显示时都会显 ...

  5. 《机器学习实战》---NumPy

    NumPy库函数基础: 机器学习算法涉及很多线性代数知识. NumPy库中有很多线性代数计算. 之所以用到线性代数只是为了简化不同的数据点上执行的相同数学运算.将数据表示为矩阵形式, 只需要执行简单的 ...

  6. 【习题5-1 UVA - 1593】Alignment of Code

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 模拟题,每一列都选最长的那个字符串,然后后面加一个空格就好. 这个作为场宽. 模拟输出就好. [代码] #include <b ...

  7. POJ 1511 Invitation Cards (ZOJ 2008) 使用优先队列的dijkstra

    传送门: http://poj.org/problem?id=1511 http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1008 ...

  8. UVALive - 4960 Sensor network(生成树+LCA)

    题目大意:给出N个点.M条边.问这N个点形成的生成树的最大权值边-最小权值边的最小值 解题思路:先排序,然后按生成树的kruscal算法进行加边,再维护一个最小权值边 加边的时候要考虑一下加下去的边是 ...

  9. C#的Timer(很多相关文章)

    再C#里现在有3个Timer类: System.Windows.Forms.Timer System.Threading.Timer System.Timers.Timer 这三个Timer我想大家对 ...

  10. [Angular] Using InjectionToken

    Previously we have 'OpaqueToken', but it is DEPRECATED. The new one is called 'InjectionToken'. The ...