写在开始
上面一篇写了一篇使用 webSocket 做客户端, 然后服务端是 socke 代码实现的. 传送门: webSocket 和 Socket 实现聊天群发
本来我是打算写到一章上的, 毕竟实现的都是一样的功能, 后来想了想就没写在一起, 主要是两个方面,
一个原因是这是另一种实现服务方式, 放在一起看着有点乱. 单独写也方便查阅. 二是写是分开写的回家晚上写一点, 不能直接在原文上写, 就重新起来一个草稿, 但是写完就感觉有点懒, 不想整合到一块了. 嘿嘿,,,
所以对开头说的不明白的同学可以先看一下前面的东西. 看一下基础, 事半功倍哦.
这一篇不做功能的更改, 既然我们使用了 WebSocket 为什么不使用到底哪, 我不喜欢 socket 的里面出现的打包请求连接数据处理和发送数据处理. 可以没有问题啊. 那你继续往下看吧.
首先 WebSocket 服务器这篇我们还是实现的 6 个功能:
单聊: 可以指定人进行聊天.
群发: 这个的意思就是当前服务器内的所有人包含自己, 这个就跟一个推送效果一样.
开启连接 (客户端): 通知除自己以外的所有用户
关闭连接 (客户端): 通知除自己以外的所有用户
群组 A: 实现一个群组名字为 A
群组 B: 实现一个群组名字为 B
技术点
前端写法都是一样的我就不做过多的叙述了, 这里只要是针对 socket 协议的方法进行修改成 WebSocket 形式.
首先我这次是把服务写成了一般处理程序进行挂载的.(有些有强迫症的小伙伴想改访问路由路径可以参考一下: mvc 中路由的映射和实现 IHttpHandler 挂载
我在本示例就是把放在 model 下的一个一般处理程序, 改写成了 socket 路径.
原来访问是: http"//http://localhost: 端口号 / 文件夹位置 / SocketServer.ashx
改完之后是: http"//http://localhost: 端口号 / socket/SocketServer.ashx
在实际项目中可以不暴露文件的真是路径位置, 还是有点用处的.
不得不说 WebSocket 确实不错, 比如接受发送数据解析方法都给封装好了.
接受方式
既然使用 WebSocket 做协议当然接受就不用用 socket 而是使用 WebSocket 啦. 通过在接受到请求后获取上下文中的 WebSocket.
- // 创建新 WebSocket 实例
- WebSocket myClientSocket = context.WebSocket;
- string userId = context.QueryString["userId"];
在这里我们有一点变化就是 socke 用户是通过 socket 随机获取的, 这里我修改成了页面传输. 前台代码:
- var userId = parseInt(Math.random() * (999999 - 100000 + 1) + 100000, 10);
- console.log(userId)
- ws = new WebSocket('ws://' + window.location.hostname + ':' + window.location.port + '/socket?userId=' + userId);
判读在线方式
WebSocket 有单独的状态来进行在线的判断, 不用我们自己写判断处理还是比较好的.
- #region 关闭 Socket 处理, 删除连接池
- if (myClientSocket.State != WebSocketState.Open)// 连接关闭
- {
- if (ListUser.ContainsKey(userId)) ListUser.Remove(userId);// 删除连接池
- break;
- }
- #endregion
接受数据
WebSocket 也没有辜负我们的期望, 接受数据的处理也不需要我们处理的, 使用 ReceiveAsync 方法可以得到消息字节, 我们只需要定义一个字节数组段用来接受即可, 例如:
- ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]);// 定义字节数组
- WebSocketReceiveResult result = await myClientSocket.ReceiveAsync(buffer, CancellationToken.None);// 获得字节
- string userMsg = Encoding.UTF8.GetString(buffer.Array, 0, result.Count);// 发送过来的消息
是不是感觉特别的方便, 没有了那些乱七八糟的处理了. 看着还是挺舒心的.
发送数据
既然接受数据都有单独的方法封装, 发送消息没有道理没有的, 是的发送使用 SendAsync 方法, 使用形式和 ReceiveAsync 类似, 首先定义一个字节数组段用来存放内容, 例如:
- ArraySegment<byte> buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes($"用户 ({userIdA}=>{userIdB}):{msg}"));
- socket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
这样子就是一个发送过程, 先把要发送的字符串转换成字节数组段, 然后把这个数组段使用 SendAsync 发送出去就可以了.
注: 在上面的两个方法中我们都看到了 ArraySegment 这个东西, 他到底是个什么哪, 他是一个命名在 System 命名空间下的一个结构体. 类似与 Array 数组但是他又不是数组, 为什么这么说, 因为他可以接受数组段, 他可以只保存内容中的一部分而不是全部. 就像别人说的小抽屉一样. 我是把他理解成先把他放到这里当作数据缓存区, 等真实发送的时候进行发送数据. WebSocketMessageType 是一个枚举类型, 通过 F12 可以看到:
- // 摘要:
- // 指示消息类型:
- public enum WebSocketMessageType
- {
- //
- // 摘要:
- // 该消息是明文形式.
- Text = 0,
- //
- // 摘要:
- // 消息采用二进制格式.
- Binary = 1,
- //
- // 摘要:
- // 因为收到关闭的消息, 接受已完成.
- Close = 2
- }
敬上代码
入口函数
一般处理程序中判断只接受 WebSocket 协议连接进入的运行:
- if (context.IsWebSocketRequest)
- {
- context.AcceptWebSocketRequest(Accept);
- }
- else
- {
- }
消息处理
下面就是同意连接后的主要方法, 类似上一篇写的 ReceiveMessage 方法 (接受消息), 这里的处理存在一些改动, 所以我就把所有代码贴上来了.
- #region 处理客户端连接请求
- /// <summary>
- /// 处理客户端连接请求
- /// </summary>
- /// <param name="result"></param>
- private async Task Accept(AspNetWebSocketContext context)
- {
- // 创建新 WebSocket 实例
- WebSocket myClientSocket = context.WebSocket;
- string userId = context.QueryString["userId"];
- try
- {
- string descUser = string.Empty;// 目的用户
- while (true)
- {
- if (myClientSocket.State == WebSocketState.Open)
- {
- ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]);
- WebSocketReceiveResult result = await myClientSocket.ReceiveAsync(buffer, CancellationToken.None);
- #region 消息处理 (字符截取, 消息转发)
- try
- {
- #region 关闭 Socket 处理, 删除连接池
- if (myClientSocket.State != WebSocketState.Open)// 连接关闭
- {
- if (ListUser.ContainsKey(userId))
- {
- // 退出
- SignOut(userId);
- ListUser.Remove(userId);// 删除连接池
- Debug.WriteLine("当前退出用户:" + userId);
- }
- break;
- }
- #endregion
- string userMsg = Encoding.UTF8.GetString(buffer.Array, 0, result.Count);// 发送过来的消息
- string[] resultList = userMsg.Split(',');
- if (resultList[0] == "login")
- {
- // 登录
- Login(userId);
- #region 用户添加连接池
- // 第一次 open 时, 添加到连接池中
- if (!ListUser.ContainsKey(userId))
- ListUser.Add(userId, myClientSocket);// 不存在, 添加
- else
- if (myClientSocket != ListUser[userId])// 当前对象不一致, 更新
- ListUser[userId] = myClientSocket;
- #endregion
- Debug.WriteLine("当前登录用户:" + userId);
- }
- else if (resultList[0] == "all")
- {
- // 群发所有用户
- GroupChat(userId, resultList[1]);
- }
- else if (resultList[0] == "groupA")
- {
- // 群组发送
- GroupChatA("groupA", userId, resultList[1]);
- }
- else if (resultList[0] == "groupB")
- {
- // 群组发送
- GroupChatA("groupB", userId, resultList[1]);
- }
- else
- {
- // 单聊
- SingleChat(userId, resultList[0], resultList[1]);
- }
- }
- catch (Exception exs)
- {
- // 消息转发异常处理, 本次消息忽略 继续监听接下来的消息
- }
- #endregion
- }
- else
- {
- break;
- }
- }//while end
- }
- catch (Exception ex)
- {
- Console.WriteLine("Error :" + ex.ToString());
- }
- }
- #endregion
单聊实现
这里我就不把写的所有单聊, 群里, 实现群组方法贴上来了, 实现的思路还是和以前一样, 只是写法不同, 我就写一个单聊作为代表示例贴上来. 想看全部在下面下载源码就好了.
- #region 单聊
- public void SingleChat(string userIdA, string userIdB, string msg)
- {
- WebSocket socket = ListUser[userIdB];
- if (socket != null)
- {
- if (socket != null && socket.State == WebSocketState.Open)
- {
- ArraySegment<byte> buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes($"用户 ({userIdA}=>{userIdB}):{msg}"));
- socket.SendAsync(buffer, WebSocketMessageType.Binary, true, CancellationToken.None);
- }
- }
- }
- #endregion
传送门:
基础版本实现简单的 websocket: 实现服务端 webSocket 连接通讯
完善 websocket 实现聊天示例: WebSocket 和 Socket 实现聊天群发
最后在送上 github 源码: https://github.com/Yanbigfeng/WebSocketToSocket
来源: https://www.cnblogs.com/yanbigfeg/p/9330613.html