基于 WebSocket 的聊天和大文件上传有进度提示完美实现

         大家好,好久没有写文章了,当然不是不想写,主要是工作太忙,公司有没有网络环境,不让上网,所以写的就少了。今天是2019年的最后一天,明天就要开始新的一年,当然也希望自己有一个新的开始。在2019年的最后一天,写点东西,作为这一年的总结吧!写点啥呢?最近有时间,由于公司的需要,需要实现一个自己的、Web版本的聊天工具,当然也要能传输文件。经过两个星期的无网络、艰苦的学习,终于写出了一个最初的版本。在公司里面里面已经生成正式版本了,很多类型都进行了抽象化,支持注册,头像,私信,群聊,传输大文件,类似 Web 版本的QQ。那是公司的东西,这个版本是我又重新写的,没有做过多的设计,但是功能都实现了,这个版本还比较粗糙,有时间在写第二个版本。

          别的先不说,先上一个截图,让大家看一下效果,版本虽然粗糙,但是该有的功能都有了,大家可以根据自己的需要改成自己的东西。效果图如下:

        好了,以上就是效果图,挺实用的,大家只要稍加修改就可以使用,所有代码都是可以正常使用的。

        代码挺多的,一步一步的来。我先对我的项目做个截图,让大家做到心里有数。项目分为两个部分,一个部分是类库,主要实现代码再次,还有一个就是MVC的前端的项目。

        第一步:项目截图:(VS2017)
          


          第二步:前端代码

  1 <!DOCTYPE html>
  2 <html  3 head  4     Meta name="viewport" content="width=device-width" />
  5     title>Index</  6     style type="text/css"  7         html,body {
  8             font-size: 12px;
  9             height 100% 10             width 11             overflow-y auto 12         }
 13 
 14         textarea  15  16             color black 17          18 
 19         #messageContent  20             background-color cadetblue 21             border 1px solid black 22  500px 23  24  14px 25  26          27 
 28         .chatLeft  29             display block 30  chocolate 31  32             margin-top 5px 33             margin-left 10px 34             padding 3px 35             float left 36             clear both 37          38 
 39         .chatRight  40  41  white 42  43  44             margin-right 45  46             text-align right 47  48  49          50 
 51         .chatTitleSingle  52             white-space Nowrap 53  54             border-radius 55  56  57  #267b8a 58  59  60          61 
 62         .chatTitleGroup  63  64  65  66  67  #b61111 68  69  70             max-width 250px 71             min-width 200px 72 left 73          74 
 75         .chatSelfContent  76  77  78  79  80  81  #51d870 82  83  84  85          86 
 87         .chatContent  88  89  90  91  92  93  94  95  96  97          98 
 99         .loginContent 100 101  center102 103  gold104             font-weight bold105 106         107 
108         .logoutContent 109 110 111 112  darkslateblue113 114 115         116 
117         .fileUploadedFinished 118 119 120 121 122 123         124 
125         .offlineUser 126 127 128  dimgrey129 130 131         132 
133         #chatandFileContainer 134             margin135         136 
137         #spnNoticeText 138  red139         140 
141         .noticeMessageInContainer 142 143 144 145 146         147 
148         a:link 149  #03516f150             text-decoration none151         152 
153         a:visited 154  #1ea73c155  wavy156         157 
158         a:hover 159  #0597d0160  underline161         162 
163         a:active 164  #0bbd33165 166         167     style168 169 body170     div171         ><span ="padding-left:5px;color:red;">提示spanid="spnNoticeText">暂无连接!></172         div ="margin:5px 4px"173             链接服务:input ="text" name="name" placeholder="请输入登录标识" id="txtUserKey"174             button ="btnConnected" style="margin-right:10px;margin-left:10px;">建立连接button175             ="btnClose">关闭连接176         177         ="chatandFileContainer"="display:none"178             ="margin:5px 0px;"179                 消息内容textarea ="txtContent"="消息内容" cols="35" rows="5"textarea180             181             182                 接受人名:="txtPrivateUserKey"="群聊无需填写接收人" 183             184             185                 文件上传="file"="border:1px solid black;margin:0px;padding:0px;width:300px;" multiple 186             187             ="uploadProgress"188             ="margin:10px 60px;"189                 ="btnSendGroup"="margin-right:10px">群 聊> ="btnSendPrivate">私 聊190             191         192         ="messageContent"193     194     br 195     script ="text/javascript" src="~/Scripts/jquery-1.10.2.min.js"script196     ="~/Scripts/MyScripts/ChatandUploadFilesProcessHandler.js"197 198 >


          第三步:JavaScript 代码文件名:ChatandUploadFilesProcessHandler.js

//封装文件上传和聊天。
  2 (function () {
  3     生命全局变量
var webSocketInstance;
var chatUrl = "ws://localhost:62073/HttpHandlers/WebChatHandler.ashx";
var isSendFileGroup = false;是否是群发文件认状态不是群发。
  7     var isOnline = false  8     var mainProcess = {
  9         1、初始化基本事件
 10         init:  11             this.initClick();
 12         }, 13         2、建立通讯事件。
 14         initConnect:  15             if (isOnline == ) {
 16                 var newUrl = chatUrl + "?userKey=" + $("#txtUserKey").val();
 17 
 18                 webSocketInstance = new WebSocket(newUrl);
 19 
 20                 2.1、建立网络连接的时候触发该事件
 21                 webSocketInstance.onopen =  22                     $("#spnNoticeText").html("已经连接!");
 23                     $("#chatandFileContainer").attr("style","display:block" 24                 }
 25 
 26                 2.2、接受服务器发来的消息触发该事件。
 27                 webSocketInstance.onmessage =  (evt) {
 28                     $("#messageContent").append(evt.data);
 29  30 
 31                 2.3、网络错误的时候触发该事件。
 32                 webSocketInstance.onerror =  33                     $("#spnNoticeText").html(JSON.stringify(evt));
 34  35 
 36                 2.4、当连接关闭的时候触发该事件。
 37                 webSocketInstance.onclose =  38                     这里可以根据实际场景编写,比如重连机制。
 39                     $("#spnNoticeText").html("断开连接!" 40                     $("#chatandFileContainer").attr("style","display:none" 41  42                 isOnline = true 43             }
 44             else 45                 $("#spnNoticeText").html($("#txtUserKey").val()+"用户已经在线了!" 46  47  48         3、初始化各种点击事件。
 49         initClick:  50             3.1、网络连接事件
 51             $("#btnConnected").on("click", 52                 if (document.getElementById("txtUserKey") && document.getElementById("txtUserKey").value == "" 53                     $("#spnNoticeText").html("请输入登录用户的标识!" 54                     return 55  56                 mainProcess.initConnect();
 57             });
 58 
 59             3.2、网络连接事件
 60             $("#btnClose").on("click",1)"> 61                 if (webSocketInstance && webSocketInstance.readyState == WebSocket.OPEN) {
 62                     webSocketInstance.close();
 63                     isOnline =  64  65  66 
 67             3.3、群发消息
 68             $("#btnSendGroup").on("click",1)"> 69                 if (webSocketInstance) {
 70                     if (webSocketInstance.readyState == 71                         clearUploadProgress();
 72                         var message = $("#txtContent" 73 
 74                         if (message && message.length > 0 75                             webSocketInstance.send(message);
 76                         }
 77 
 78                         if (document.getElementById("file").files.length > 0 79                             isSendFileGroup =  80                             uploadFiles();
 81 
 82                             clearFilesuploader();
 83  84                     }
 85                     else  WebSocket.CLOSED) {
 86                         $("#spnNoticeText").html("已经与服务器断开连接!" 87  88                      WebSocket.CONNECTING) {
 89                         $("#spnNoticeText").html("正在尝试与服务器建立连接!" 90  91                      WebSocket.CLOSING) {
 92                         $("#spnNoticeText").html("正在关闭与服务器的连接!" 93  94                 }                
 95  96 
 97             3.4、私聊发消息
 98             $("#btnSendPrivate").on("click",1)"> 99                 var userKey = $("#txtPrivateUserKey"100                 if (userKey == null || userKey == "" || userKey.length <= 0101                     $("#spnNoticeText").html("请输入接收用户的标识!"102                     103 104 
105                 106                     107 108                         109 
110                         对消息进行拼接 "$--$--**"+ userKey +"$--$--**"+"要发送消息的内容";
111                         112                             var finalMessage = "$--$--**" + userKey + "$--$--**" + message;
113                             webSocketInstance.send(finalMessage);
114 115 
116                         117                             isSendFileGroup = 118 119 
120 121 122 123                     124                         $("#spnNoticeText").html("已经与服务器断开连接!"125 126                     127                         $("#spnNoticeText").html("正在尝试与服务器建立连接!"128 129                     130                         $("#spnNoticeText").html("正在关闭与服务器的连接!"131 132 133 134         }
135     };
137     开始上传文件部分集成。
138     var filesUrl = "ws://localhost:62073/HttpHandlers/UploadFilesHandler.ashx"139      uploadOperate(file) {
140          (file) {
141             var _this = 142             this.reader = new FileReader();读取文件对象。
143             this.step = 1024 * 256; 每次读取文件的大小
144             this.curLoaded = 0; 当前读取位置
145             this.file = file; 当前文件对象。
146             this.enableRead = true;指示是否可以继续读取。
147             this.total = file.size;文件的总大小。
148             this.startTime = new Date();开始读取时间。
149             .createItem();
150             this.initWebSocket(151                 _this.bindReader();
152 153 154         155             156             this.step = 1024 * 256157             this.curLoaded = 0158             159             this.total = 0160 161     }
162     uploadOperate.prototype =163         绑定读取事件
164         bindReader: 165             166             var reader = .reader;
167             var webSocketFileInstance = .webSocketFileInstance;
168             reader.onload =  (e) {
169                 判断是否能再次读取
170                 if (_this.enableRead == 171                     172 173                 根据当前缓冲区控制读取速度
174                 if (webSocketFileInstance.bufferedamount >= _this.step * 20175                     setTimeout(176                         _this.loadSuccess(e.loaded);
177                     },5178                 } 179                     _this.loadSuccess(e.loaded);
180 181 182             开始读取
183             _this.readBlob();
184 185         成功读取,继续处理
186         loadSuccess:  (loaded) {
var webSocketFileInstance = _this.webSocketFileInstance;
189             使用 WebSocket 将二进制输出上传到服务器。
var blob = _this.reader.result;
191             if (_this.curLoaded <= 0192                 webSocketFileInstance.send(_this.file.name);
193 194             webSocketFileInstance.send(blob);
195             当前发送完成,继续读取。
196             _this.curLoaded += loaded;
197             if (_this.curLoaded < _this.total) {
                _this.readBlob();
199 200             201                 发送读取完成
202                 webSocketFileInstance.send("[file:{(:finished:)}200]"203                 this.showInfo('<div class=\"fileUploadedFinished\">文件名:' + fileNameTrim(_this.file.name,6) + ',文件大小:【' + (_this.curLoaded / (1024 * 1024)).toFixed(3) + '】M,上传时间:【' + ((new Date().getTime() - _this.startTime.getTime()) / 1000) + '】秒!</div>'204 205             显示进度
206             _this.showProgress();
207 208         创建显示
209         createItem: 210             211             var blockquote = document.createElement("blockquote"212             var abort = document.createElement("input"213             abort.type = 'button'214             abort.value = '暂停'215             abort.onclick = 216                 _this.stop();
217             };
218             blockquote.appendChild(abort);
219 
220             var containue = document.createElement("input"221             containue.type = 'button'222             containue.value = '继续'223             containue.onclick = 224                 _this.containue();
225 226             blockquote.appendChild(containue);
227 
228             var progress = document.createElement('progress'229             progress.style.width = '300px'230             progress.max = 100231             progress.value = 0232             blockquote.appendChild(progress);
233             _this.progressBox = progress;
234 
235             var status = document.createElement('span'236             status.id = 'Status'237             blockquote.appendChild(status);
238             _this.statusBox = status;
239 
240             document.getElementById('uploadProgress').appendChild(blockquote);
241 242         243         showProgress: 244             245             var percent = ((_this.curLoaded / _this.total) * 100).toFixed();
246             _this.progressBox.value = percent;
247             _this.statusBox.innerHTML =248 249         读取文件
250         readBlob: 251             var blob = this.file.slice(this.curLoaded,1)">this.curLoaded + .step);
252             .reader.readAsArrayBuffer(blob);
253 254         暂停读取
255         stop: 256             257             var percentValue = this.percent(this.curLoaded / .total);
258             if (percentValue != '100%'259                 this.showInfo("<div class=\"noticeMessageInContainer\">读取终止,已读取:" + percentValue + "</div>"260 261             .reader.abort();
262 263         继续读取
264         containue: 265             266             267                 268                 .readBlob();
269                 this.showInfo("<div class=\"noticeMessageInContainer\">读取继续,已读取:" + percentValue + "</div>"270 271             272                 273 274 275         计算百分比
276         percent:  (data) {
277             if (data == 0) { return 0; }
278             var valuePercent = Number(data * 100279             valuePercent += "%"280              valuePercent;
281 282         显示日志
283         showInfo: 284             var html = ""285             html += data;
286             document.getElementById("messageContent").innerHTML = document.getElementById("messageContent").innerHTML + html;
287             var divLogContainer = document.getElementById("messageContent"288             divLogContainer.scrollTop = divLogContainer.scrollHeight;
289 290         初始化 WebSocket 
291         initWebSocket:  (onSuccess) {
292             293             this.webSocketFileInstance =  WebSocket(filesUrl);
294 
295             webSocketFileInstance.onopen = 296                 console.log("connect 链接创建成功"297                 298                     onSuccess();
299 300 301             webSocketFileInstance.onmessage = 302                 var data = e.data;
303                 if (isNaN(data) == 304                     showInfo('后台接受成功:' + data);
305 306                 307                     console.info(data);
308 309 310             webSocketFileInstance.onclose = 311                 终止读取
312 313                 showInfo("WebSocket 连接已经断开!"314                 console.log("WebSocket 连接已断开。"315 316             webSocketFileInstance.onerror = 317 318                 showInfo("发生异常:" + e.message);
319                 console.log("发生异常:" +320 321 322 323     window.uploadOperate = uploadOperate;
324     window.mainProcess = mainProcess;
325 })();
326 
327 $(328     mainProcess.init();
329 });
330 
331 上传文件的速度取决于每次 send() 的数据的大小。Google 之所以会慢,是因为他每次 send 的数据很小。
332  uploadFiles() {
333     var fileController = document.getElementById("file"334     checkAndUploadCore(fileController,1)">335 }
336 
337 检查文件
338 var fileController2 = document.getElementById("file"339 fileController2.onchange = 340     clearUploadProgress();
341     document.getElementById("txtContent").value = ""342     checkAndUploadCore(fileController2,1)">343 344 
345 如果文件名太长,就会修剪。
346 fileName:文件
347 length:要截取文件名的长度。
348  fileNameTrim(fileName,length) {
349     if (fileName && fileName.length > 0 && fileName != ""350         if (length > 0 && length >= fileName.length) {
351              fileName;
352 353         354             return fileName.substring(0,length) + "..."355 356 357 358 
359 清除文件上传的进度条显示。为下一次做准备。
360  clearUploadProgress() {
361     uploadOperate();
362     document.getElementById("uploadProgress").innerHTML = ""363 364 
365 文件上传后将控件置为初始状态。
366  clearFilesuploader() {
367     document.getElementById("file").value = ""368 369 
370 核心的上传文件方法
371 uploader:上传文件的控件。
372 isupload:是否开始上传文件
373  checkAndUploadCore(uploader,isupload) {
374     if (uploader && uploader.files.length > 0375         var maxTotalSize = 5000;单位:M
376         var files = uploader.files;
377         var filetotalSize = 0378         var fileCount = 5379         var fileTypes = [".jpg",".gif",".bmp",".png","jpeg",".rar",".zip",".txt",".doc",".ppt",".xls",".pdf",".csv",".docx",".xlsx"];
380 
381         1、验证上传文件的格式。
382         var isValid = 383         var fileEnd = ''384         if (fileTypes && fileTypes.length > 0385             for (var m = 0; m < files.length; m++386                 fileEnd = files[m].name.substring(files[m].name.lastIndexOf("."));
387                 isValid = 388                 var i = 0; i < fileTypes.length; i++389                     if (fileEnd.toLowerCase() == fileTypes[i].toLowerCase()) {
390                         isValid = 391                         continue392 393 394                 if (!isValid) {
395                     break396 397 398             399                 alert("不支持文件类型"400                 uploader.value = ''401                 return 402 403 404 
405         2、检查文件上传的个数。
406         if (files.length > 0 && files.length > fileCount) {
407             alert("最多只能上传【" + fileCount + "】个文件!"408             uploader.value = ''409             410 411 
412         3、检查文件的总大小。
413         var i = 0; i < files.length; i++414             filetotalSize += files[i].size;
415 416         filetotalSize = filetotalSize / (1024 * 1024417         filetotalSize = filetotalSize.toFixed(3418         if (filetotalSize > maxTotalSize) {
419             alert("上传文件总自己额大小不能大于【" + (maxTotalSize / 1024).toFixed() + "】G!"420             uploader.value = ''421             422 423 
424         4、检查文件名是否有效。
425         var isFileNameValid = 426         var fileName = ''427         var containSpecial = RegExp(/[(\ )(\~)(\!)(\@)(\#)(\$)(\%)(\^)(\&)(\*)(\()(\))(\+)(\=)(\[)(\])(\{)(\})(\|)(\:)(\;)(\')(\")(\,)(\<)(\.)(\>)(\/)(\?)]+/428         429             fileName = files[m].name.substring(0,files[m].name.lastIndexOf("."430              (containSpecial.test(fileName)) {
431                 isFileNameValid = 432                 433 434 435         isFileNameValid) {
436             alert("文件名包含特殊字符,不可以上传!"437             uploader.value = ''438             439 440 441     442         443 444 
445      (isupload) {
446         447             var file = files[i];
448             var operate =  uploadOperate(file);
449 450 451     452         var fileNameList = ""453         454             455             if (i == files.length - 1456                 fileNameList += file.name;
457             } 458                 fileNameList += file.name + "\n"459 460 461         document.getElementById("txtContent").value = fileNameList;
462 463 }

 


          第四步:前端 文件上传代码文件名:UploadFilesHandler.ashx

 1 using ChatandUploadBaseWebSocket;
 2  System.Web;
 3 
 4 namespace WebApplicationForChat.HttpHandlers
 5 {
 6     /// <summary>
 7     /// UploadFilesHandler 的摘要说明
 8     </summary>
 9     public class UploadFilesHandler : IHttpHandler
10     {
11         private WebSocketUploadFilesHandler uploadFileHandler;
12 
13         14          处理来之客户端 WebSocket 请求。
15         16         <param name="context"></param>
17         void ProcessRequest(HttpContext context)
18         {
19              (context.IsWebSocketRequest)
20             {
21                 if (uploadFileHandler == null)
22                 {
23                     uploadFileHandler =  WebSocketUploadFilesHandler();
24 25                 context.AcceptWebSocketRequest(uploadFileHandler.ProcessFile);
26             }            
27 28 
29         30          指示该处理器是否可以重用。认不重用。
31         32         bool IsReusable
33 34             get
35 36                 37 38 39 40 }

 

          第五步:前端聊天处理器代码文件名:WebChatHandler.ashx

 基于 HttpHandler 实现的聊天功能 WebChatHandler : IHttpHandler
 WebSocketChatHandler chatHandler;
12         private string userKey = 13 
 处理来至客户端的 WebSocket请求。
17         <param name="context">WebSocket 请求的上下文。</param>
18         19 20             21 22                 userKey = context.Request.QueryString["userKey"23                 string.IsNullOrEmpty(userKey) && !string.IsNullOrWhiteSpace(userKey))
25                     if (chatHandler ==                     {
27                         chatHandler =  WebSocketChatHandler(userKey);
28 29                     else
30 31                         chatHandler.CurrentUserKey = userKey;
32                     context.AcceptWebSocketRequest(chatHandler.ProcessChat);
34 36 37 
38         39          指示该处理器是否可以重用,认不可以重用。
40         41         42 43             44 45                 46 47 48 49 }

 

          第六步:后端类库代码文件名:IOnlineUserManager.cs

 System;
 System.Collections.Generic;
 3  System.Linq;
 System.Net.WebSockets;
 System.Text;
 6  System.Threading;
 7  System.Threading.Tasks;
 8 
 9  ChatandUploadBaseWebSocket
11     12      该类型定义在线用户的管理器的抽象接口。
13     14     interface IOnlineUserManager
15  增加新用户18         19         <param name="userKey">增加用户的标识符名称20         <param name="webSocket">增加用户标识符对应的 WebSocket 对象实例。21         <returns>返回布尔类型的值,true 表示增加用户成功,false 表示增加用户失败。</returns>
22         void Add( userKey,WebSocket webSocket);
23 
24         25          移除指定用户标识符名称用户实例。
26         27         要移除用户的标识符。28         返回布尔类型的值,true 表示移除用户成功,false 表示移除用户失败。29         Task Remove( userKey);
30 
32         获取指定名称名称用户实例。
33         34         获取用户实例的标识符名称35         如果获取到就返回其实,没有就返回 Null 值。36         WebSocket Get( 根据指定用户标识符名称判断相应用户实例是否存在。
41         要判断用户实例是否存在的标识符名称42         返回布尔类型的值,true 表示指定标识符名称用户实例存在,false 表示不存在指定标识符名称用户实例。43         bool IsExists(44 
45         46          清空所有在线的用户实例。
47                 Task Clear();
49 
50         51          获取所有在线用户的人数。
52         53         int Count { get54 
55         56          向所有在线用户发送消息,当然也包括自己在内。
57         58         <param name="content">具体要发送消息的内容59         <param name="cancellationToken">取消发送的标识对象。</param>        
60         该操作是异步完成的。61         Task Send(string content,CancellationToken? cancellationToken = 62 
63 
64         65          如果没有指定接受消息人员的列表,认就是向所有人发送消息。如果指定了接收消息的人员列表,只有指定的人才会接受到消息。
66         67         具体要发送消息的内容68         69         <param name="includedUsers">具体接收消息的用户列表。70         71         Task Send(params [] includedUsers);
72 
73         74          如果没有指定哪些在线人员不需要接受信息,就向所用的在线用户发送消息,如果指定了不接受消息人员的列表,就去掉这些在线用户,向其他向所有在线用户发送消息。
75         76         77         78         <param name="excludedUsers">具体不需要接收消息的用户列表。79         80         Task SendUn(null,1)">[] excludedUsers);
81 82 }

 

 

        第七步:后端类库代码文件名:OnlineUsersManager.cs

 System.Collections.Concurrent;
  4   5   6   7 
  8   9  10      11      该类型定义在线用户的管理器,该类型不可以被继承。
 12      13     sealed  OnlineUsersManager:IOnlineUserManager
 14  15         private ConcurrentDictionary<string,WebSocket> _userContainer;
 16 
 17         #region 获取单件对象
 19         static readonly IOnlineUserManager Current =  OnlineUsersManager();
 20 
 21         #endregion
 22 
 23          24          初始化 OnlineUsersManager 类型的新实例。
 25          26          OnlineUsersManager()
 27  28             _userContainer = new ConcurrentDictionary<();
        }        
 31          32          33          34          35          36          37          38  39             if (string.IsNullOrEmpty(userKey) || string.IsNullOrWhiteSpace(userKey) || webSocket ==  40  41                  42  43             _userContainer.ContainsKey(userKey))
 44  45                 _userContainer.TryAdd(userKey,webSocket);
 48 
 49          50          51          52          53          54         async Task Remove( userKey)
 56              58                  (_userContainer.ContainsKey(userKey))
 59  60                     WebSocket temp;
 61                     if (_userContainer.TryRemove(userKey,1)">out temp))
 63                         await temp.CloseAsync(WebSocketCloseStatus.normalClosure,Close,CancellationToken.None);
 66  67  68 
 69          70          71          72          73          74         public WebSocket Get( 76              77  78                  79  80                      _userContainer[userKey];
 81  83              85 
 86          87          88          89          90          91          92  93             bool result =  94             string.IsNullOrWhiteSpace(userKey) && !.IsNullOrEmpty(userKey))
 96                  _userContainer.ContainsKey(userKey);
 97  98              result;
 99 100 
101         102         103         104         async Task Clear()
105 106             foreach (var item in _userContainer.Keys)
108                 WebSocket socket;
109                 if (_userContainer.TryRemove(item,1)"> socket))
110 111                     await socket.CloseAsync(WebSocketCloseStatus.normalClosure,1)">112 116         117         118         119         int Count
121             get {  _userContainer.Count; }
123 
124         125         126         127         128         129         130         async Task Send(132             string.IsNullOrEmpty(content) && !.IsNullOrWhiteSpace(content))
134                 if (cancellationToken == 136                     cancellationToken = CancellationToken.None;
137 138                 ArraySegment<byte> buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(content));
139                  _userContainer.Values)
140 141                     await item.SendAsync(buffer,WebSocketMessageType.Text,cancellationToken.Value);
142 143 144 145 
146         147         148         149         150         151         152         153         [] includedUsers)
154 156 157                 if (includedUsers == null || includedUsers.Length <= 0158 159                     await Send(content,cancellationToken);
161                 162 163                     164 165                         cancellationToken =166 167                     ArraySegment<168                      _userContainer)
170                         var name  includedUsers)
171                         {
172                             string.Compare(name,item.Key,1)">true) == 173                             {
174                                 await item.Value.SendAsync(buffer,1)">175                             }
177 178 181 
182         183         184         185         186         187         188         189         async Task SendUn([] excludedUsers)
190 193                 if (excludedUsers == null || excludedUsers.Length <= 195                     await Send(content,1)">196 197                 199                     cancellationToken =200 201                 ArraySegment<202                 203 204                     var userName  excludedUsers)
205 206                         string.Compare(item.Key,userName,1)">true) != 208                             209 210                     }                    
211 212 213 214 215 }

 

 

        第九步:后端类库代码文件名:UploadFileExtensionValidator.cs

 System.Text.RegularExpressions;
 2 
 5      该类型定义上传文件扩展名是否有效的验证器。
 8      UploadFileExtensionValidator
10         11          验证上传文件的格式是否是有效的。true 表示是有效的文件格式,false 表示不是有效的文件格式。
12         <param name="value">要验证的文件名。返回布尔类型的值,true 表示是有效的文件格式,false 表示不是有效的文件格式。15         bool ValidateFiles( value)
16 17             string.IsNullOrEmpty(value) && !string.IsNullOrWhiteSpace(value) && value.IndexOf(.") != -119                 bool result = Regex.IsMatch(value,1)">@"^.+\.(jpg|png|gif|bmp|rar|txt|zip|doc|ppt|xls|pdf|docx|xlsx|jpeg|xml|csv)(\?.+)?$noreCase);
20                 22             23 24 
 验证图片的格式是否正确,true 表示是有效的图品格式,false 表示不是有效的图片格式。
要验证的图片名称返回布尔类型的值,true 表示是有效的图品格式,false 表示不是有效的图片格式。30         bool ValidateImages(31 32             34                 ^.+\.(jpg|png|gif|bmp|jpeg)(\?.+)?$35                 37             40 }

 

        第十步:后端类库代码文件名:WebSocketChatHandler.cs

 

 System.IO;
  7  System.Web.WebSockets;
  9 
 10  11  13      14      15      WebSocketChatHandler
 16 #region 私有字段
 _userKey;
 23         #region 构造函数
 24 
 26          以指定的认值初始化该类型的新实例。认值:无界
 27          28         public WebSocketChatHandler():this(无界){}
 29 
 30          以指定的用户标识符初始化该类型的新实例。
用户的标识符。<exception cref="ArgumentNullException">userKey is null.</exception>
 35         public WebSocketChatHandler( 36  37              39                 _userKey = 41              43                 throw new ArgumentNullException( 46 
 47          49         #region 实例属性
 51          CurrentUserKey
 52  53              _userKey; }
 54             set
 56                 .IsNullOrWhiteSpace(value))
 58                     _userKey = value;
 61  62 
 63          64 
 65         #region 核心方法
 67          68          处理客户端发送过来的文本信息。
返回异步操作的实例对象 Task 。 72          Task ProcessChat(AspNetWebSocketContext context)
 73  74             #region 局部变量
 75 
string messageNotice =  77             string messageBody =  78             string content =  79             string selfContent =  80             string receiveUser =  81             string[] arrays =  82             string messageMain = string offlineContent =  84             ArraySegment< echor;
 85             ArraySegment< buffer;
 86             WebSocketReceiveResult result;
 87 
 88              89 
 90             1、获取 WebSocket 实例对象。
 91             WebSocket webSocket = context.WebSocket;
 92             bool isExists = OnlineUsersManager.Current.IsExists(CurrentUserKey);
 (isExists)
 95                 表示该用户在线。
await OnlineUsersManager.Current.Send($<div class=\"onlineUser\">用户【{CurrentUserKey}】已经在线!</div>100                 OnlineUsersManager.Current.Add(CurrentUserKey,1)">101                 表示登陆成功
102                 某人成功登陆后,可以给群里其他人发送登陆成功的提示消息(本人除外)
103                 messageNotice = $<div class=\"loginContent\">用户【{CurrentUserKey}】进入聊天室,登录时间:{DateTime.Now.ToString("yyyy-M-dd HH:mm)}</div> OnlineUsersManager.Current.Send(messageNotice);
106 
107                 2、开始监听来至客户端的 WebSocket 请求。
108                 while (webSocket.State == WebSocketState.Open)
109 110                     每次读取客户端发送来的消息的大小。
111                     buffer = byte>(new byte[1024*256]);
112 
113                     result =  webSocket.ReceiveAsync(buffer,1)">114                     关闭 WebSocket 请求
115                     if (result.MessageType == WebSocketMessageType.Close)
116 117                          OnlineUsersManager.Current.Remove(CurrentUserKey);
118 
119                         发送离开提醒
120                         messageNotice = $<div class=\"logoutContent\">用户【{CurrentUserKey}】离开聊天室,退出时间:{DateTime.Now.ToString(121                         122                         await webSocket.CloseAsync(WebSocketCloseStatus.normalClosure,1)">.Empty,1)">123 124                     126                         用于发送聊天内容
127                          WebSocketMessageType.Text)
129                             messageBody = Encoding.UTF8.GetString(buffer.Array,result.Count);
130                             判断是群聊还是私聊
131                             if (messageBody.Length > 8 && messageBody.Substring(0,1)">8) == $--$--**133                                 此处表示私聊
134                                 arrays = messageBody.Split(string[] {  },StringSplitOptions.RemoveEmptyEntries);
135                                 receiveUser = arrays[137                                 messageMain = UbbAndHtmlConverter.UBBToHTML(arrays[138                                 messageMain = TextToAnchor(messageMain);
139 
140                                 var isExistsUser = OnlineUsersManager.Current.IsExists(receiveUser);
141                                  (isExistsUser)
                                {
143                                     string.IsNullOrEmpty(messageMain) && !.IsNullOrWhiteSpace(messageMain))
                                    {
145                                         私聊给对方
146                                         content = $<div class=\"chatLeft\"><span class=\"chatTitleSingle\">{CurrentUserKey} &nbsp;&nbsp;&nbsp;&nbsp;{DateTime.Now.ToString("yyyy-MM-dd HH:mm)}</span><span class=\"chatContent\">{messageMain}</span></div>147                                          OnlineUsersManager.Current.Send(content,receiveUser);
148 
149                                         私聊给自己
150                                         selfContent = $<div class=\"chatRight\"><span class=\"chatTitleSingle\">{CurrentUserKey}&nbsp;&nbsp;&nbsp;&nbsp;{DateTime.Now.ToString()}</span><span class=\"chatSelfContent\">{messageMain}</span></div>151                                         echor = (Encoding.UTF8.GetBytes(selfContent));
152                                         await webSocket.SendAsync(echor,1)">                                    }
                                }
155                                 157                                     offlineContent = $<div class=\"offlineUser\">用户【{receiveUser}】不在线!</div>158                                     echor = (Encoding.UTF8.GetBytes(offlineContent));
159                                     162                             163 164                                 messageBody = UbbAndHtmlConverter.UBBToHTML(messageBody);
165                                 messageBody = TextToAnchor(messageBody);
166 
167                                 这里表示群聊
168                                 if (OnlineUsersManager.Current.Count > 170                                     群发给他人,不包含自己
171                                     content = $<div class=\"chatLeft\"><span class=\"chatTitleGroup\">{CurrentUserKey} &nbsp;&nbsp;&nbsp;&nbsp;{DateTime.Now.ToString()}</span><span class=\"chatContent\">{messageBody}</span></div>172                                      OnlineUsersManager.Current.SendUn(content,1)">173 
174                                     单独在给自己发送一份
175                                     selfContent = $<div class=\"chatRight\"><span class=\"chatTitleGroup\">{CurrentUserKey} &nbsp;&nbsp;&nbsp;&nbsp;{DateTime.Now.ToString()}</span><span class=\"chatSelfContent\">{messageBody}</span></div>176                                     echor = 177                                     182 185 
 如果文本中包含文件名,就将文件名转换带链接文件名,便于下载。
189         要转换的内容190         返回成功转换的值。string TextToAnchor(193             string.IsNullOrWhiteSpace(value) && UploadFileExtensionValidator.ValidateFiles(value))
195                 string[] values = value.Split(<br/>},1)">196                 StringBuilder fileLinkBuilder = new StringBuilder(1024197 
198                 int i = 0; i < values.Length; i++200                      (UploadFileExtensionValidator.ValidateFiles(values[i]))
201 202                          (IsExists(values[i]))
204                              (UploadFileExtensionValidator.ValidateImages(values[i]))
206                                 if (i == values.Length - 208                                     fileLinkBuilder.AppendFormat(<img src=\"{0}\" title=\"上传时间:{1},右键点击保存\" alt=\"{2}\" width=\"200px\">",1)">/UploadFiles/"+values[i],DateTime.Now.ToString(),values[i]);
210                                 212                                     fileLinkBuilder.AppendFormat(<img src=\"{0}\" title=\"上传时间:{1},右键点击保存\" alt=\"{2}\" width=\"200px\"><br/>" + values[i],1)">215                             217                                 219                                     fileLinkBuilder.AppendFormat(<a href=\"{0}\" target=\"_blank\" title=\"右键单击保存\">{1}</a>220 221                                 222 223                                     fileLinkBuilder.AppendFormat(<a href=\"{0}\" target=\"_blank\" title=\"右键单击保存\">{1}</a><br/>227 228                     229 230                         fileLinkBuilder.Append(values[i]+231 233                  fileLinkBuilder.ToString();
234 236 237 
238         239          判断指定文件名的文件是否存在,true 表示存在,false 表示不存在。
240         241         <param name="fileName">要判断是否存在的文件名。242         返回布尔类型的值,true 表示文件存在,false 表示文件不存在。243          fileName)
244 245             Thread.Sleep(300246             247             string.IsNullOrEmpty(fileName) && !.IsNullOrWhiteSpace(fileName))
249                 if (File.Exists(HttpContext.Current.Server.MapPath(") + fileName))
250 251                     result = 252 254             255 256 
257         258 259 }

 

        第十一步:后端类库代码文件名:WebSocketUploadFilesHandler.cs

 基于 HttpHandler 实现的文件上传功能 WebSocketUploadFilesHandler
 17          18          初始化类型的新实例。
 19          20         public WebSocketUploadFilesHandler() { }
 21 
 22          处理从客户端上传文件返回异步操作的实例对象 Task。 27          Task ProcessFile(AspNetWebSocketContext context)
 28  29             ArraySegment< everyTimeBufferSize;
 30  31              32 
 33             1、获取当前的 WebSocket 对象。
 34             WebSocket webSocket = 35             string fileName =  36             byte[] bufferAllSize = 1024 * 256 * 2];缓存文件总的大小,用于暂时缓存
int loaded = 0; 当前缓存的位置。
2、监听来至客户端的 WebSocket 请求
 40             while ( 42                 此处的值是控制读取客户端数据的长度,如果客户端发送的数据长度超过当前缓存长度,则读取多次。
 43                 everyTimeBufferSize =  44 
 45                 接受客户端发送来的消息。
 46                 result =  webSocket.ReceiveAsync(everyTimeBufferSize,1)"> 47                 if (webSocket.State == 48  49                     判断发送的数据是否已经结束。
 50                     int currentLength = Math.Min(everyTimeBufferSize.Array.Length,1)"> 51 
 52                     try
 53  54                         判断客户端发送的消息的类型
 55                          57                             message = Encoding.UTF8.GetString(everyTimeBufferSize.Array,currentLength);
 58                             bool isValid = UploadFileExtensionValidator.ValidateFiles(message);
 59                             if (!isValid && string.Compare(message,1)">[file:{(:finished:)}200] 61                                  63                                                             SaveFile(fileName,bufferAllSize,loaded);
 66                                 loaded =  68                              69  70                                 fileName = 72  73                          WebSocketMessageType.Binary)
 74  75                             var temp = loaded + currentLength;
 76                             if (temp > bufferAllSize.Length)
 78  79                                 添加到缓存区
 80                                 Array.copy(everyTimeBufferSize.Array,1)"> 81                                 loaded = 83                              85                                 添加到缓冲区
 86                                 Array.copy(everyTimeBufferSize.Array,loaded,1)"> 87                                 loaded = temp;
 88  89 catch (Exception)
 93                         throw 96  99         100         文件以追加的形式保存在物理磁盘上。
要保存的文件名称<param name="buffer">每次要保存的二进制文件数据。104         <param name="loaded">要追加文件的数据长度。105         void SaveFile(string fileName,1)">byte[] buffer,1)"> length)
106 107             string.IsNullOrEmpty(fileName) || 111             if (buffer == null || buffer.Length <= 113                 115             if (length < 117                 120             string currentDirectory = HttpContext.Current.Server.MapPath(string filePathFullName = currentDirectory +122             124                 Directory.Exists(currentDirectory))
126                     Directory.CreateDirectory(currentDirectory);
127 128                 using (FileStream fileStream =  FileStream(filePathFullName,FileMode.Append,FileAccess.Write))
129 130                     fileStream.Write(buffer,length);
133              (Exception ex)
135                 可以写入日志
136                 138 139 140 }

               好了,全部代码都贴出去了。希望对大家有帮助。类库里面的类型还可以继续升级和优化,有时间了我写第二个版本,今天就到这里了,祝福大家元旦快乐,也祝自己和家人元旦快乐。
              
               新年新气象,也希望自己的2020年有一个优秀的成绩。

            

              

              

相关文章

### 创建一个gRPC服务项目(grpc服务端)和一个 webapi项目(...
一、SiganlR 使用的协议类型 1.websocket即时通讯协议 2.Ser...
.Net 6 WebApi 项目 在Linux系统上 打包成Docker镜像,发布为...
一、 PD简介PowerDesigner 是一个集所有现代建模技术于一身的...
一、存储过程 存储过程就像数据库中运行的方法(函数) 优点:...
一、Ueditor的下载 1、百度编辑器下载地址:http://ueditor....