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

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

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

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

 1 using ChatAndUploadBaseWebSocket;
 2 using 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         /// <summary>
14         /// 处理来之客户端 WebSocket 请求。
15         /// </summary>
16         /// <param name="context"></param>
17         public void ProcessRequest(HttpContext context)
18         {
19             if (context.IsWebSocketRequest)
20             {
21                 if (uploadFileHandler == null)
22                 {
23                     uploadFileHandler = new WebSocketUploadFilesHandler();
24                 }
25                 context.AcceptWebSocketRequest(uploadFileHandler.ProcessFile);
26             }
27         }
28
29         /// <summary>
30         /// 指示该处理器是否可以重用。默认不重用。
31         /// </summary>
32         public bool IsReusable
33         {
34             get
35             {
36                 return false;
37             }
38         }
39     }
40 }

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

 1 using ChatAndUploadBaseWebSocket;
 2 using System.Web;
 3
 4 namespace WebApplicationForChat.HttpHandlers
 5 {
 6     /// <summary>
 7     /// 基于 HttpHandler 实现的聊天功能。
 8     /// </summary>
 9     public class WebChatHandler : IHttpHandler
10     {
11         private WebSocketChatHandler chatHandler;
12         private string userKey = null;
13
14         /// <summary>
15         /// 处理来至客户端的 WebSocket请求。
16         /// </summary>
17         /// <param name="context">WebSocket 请求的上下文。</param>
18         public void ProcessRequest(HttpContext context)
19         {
20             if (context.IsWebSocketRequest)
21             {
22                 userKey = context.Request.QueryString["userKey"];
23                 if (!string.IsNullOrEmpty(userKey) && !string.IsNullOrWhiteSpace(userKey))
24                 {
25                     if (chatHandler == null)
26                     {
27                         chatHandler = new WebSocketChatHandler(userKey);
28                     }
29                     else
30                     {
31                         chatHandler.CurrentUserKey = userKey;
32                     }
33                     context.AcceptWebSocketRequest(chatHandler.ProcessChat);
34                 }
35             }
36         }
37
38         /// <summary>
39         /// 指示该处理器是否可以重用,默认不可以重用。
40         /// </summary>
41         public bool IsReusable
42         {
43             get
44             {
45                 return false;
46             }
47         }
48     }
49 }

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

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

 

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

  1 using System;
  2 using System.Collections.Concurrent;
  3 using System.Net.WebSockets;
  4 using System.Text;
  5 using System.Threading;
  6 using System.Threading.Tasks;
  7
  8 namespace ChatAndUploadBaseWebSocket
  9 {
 10     /// <summary>
 11     /// 该类型定义在线用户的管理器,该类型不可以被继承。
 12     /// </summary>
 13     public sealed class OnlineUsersManager:IOnlineUserManager
 14     {
 15         private ConcurrentDictionary<string, WebSocket> _userContainer;
 16
 17         #region 获取单件对象
 18
 19         public static readonly IOnlineUserManager Current = new OnlineUsersManager();
 20
 21         #endregion
 22
 23         /// <summary>
 24         /// 初始化 OnlineUsersManager 类型的新实例。
 25         /// </summary>
 26         private OnlineUsersManager()
 27         {
 28             _userContainer = new ConcurrentDictionary<string, WebSocket>();
 29         }
 30
 31         /// <summary>
 32         /// 增加新用户。
 33         /// </summary>
 34         /// <param name="userKey">增加用户的标识符名称。</param>
 35         /// <param name="webSocket">增加的用户标识符对应的 WebSocket 对象实例。</param>
 36         /// <returns>返回布尔类型的值,true 表示增加用户成功,false 表示增加用户失败。</returns>
 37         public void Add(string userKey, WebSocket webSocket)
 38         {
 39             if (string.IsNullOrEmpty(userKey) || string.IsNullOrWhiteSpace(userKey) || webSocket == null)
 40             {
 41                 return;
 42             }
 43             if (!_userContainer.ContainsKey(userKey))
 44             {
 45                 _userContainer.TryAdd(userKey, webSocket);
 46             }
 47         }
 48
 49         /// <summary>
 50         /// 移除指定用户标识符名称的用户实例。
 51         /// </summary>
 52         /// <param name="userKey">要移除用户的标识符。</param>
 53         /// <returns>返回布尔类型的值,true 表示移除用户成功,false 表示移除用户失败。</returns>
 54         public async Task Remove(string userKey)
 55         {
 56             if (!string.IsNullOrEmpty(userKey) && !string.IsNullOrWhiteSpace(userKey))
 57             {
 58                 if (_userContainer.ContainsKey(userKey))
 59                 {
 60                     WebSocket temp;
 61                     if (_userContainer.TryRemove(userKey, out temp))
 62                     {
 63                         await temp.CloseAsync(WebSocketCloseStatus.NormalClosure,"Close",CancellationToken.None);
 64                     }
 65                 }
 66             }
 67         }
 68
 69         /// <summary>
 70         /// 要获取指定名称名称的用户实例。
 71         /// </summary>
 72         /// <param name="userKey">要获取用户实例的标识符名称。</param>
 73         /// <returns>如果获取到就返回其实,没有就返回 Null 值。</returns>
 74         public WebSocket Get(string userKey)
 75         {
 76             if (!string.IsNullOrEmpty(userKey) && !string.IsNullOrWhiteSpace(userKey))
 77             {
 78                 if (_userContainer.ContainsKey(userKey))
 79                 {
 80                     return _userContainer[userKey];
 81                 }
 82             }
 83             return null;
 84         }
 85
 86         /// <summary>
 87         /// 根据指定用户标识符名称判断相应用户实例是否存在。
 88         /// </summary>
 89         /// <param name="userKey">要判断用户实例是否存在的标识符名称。</param>
 90         /// <returns>返回布尔类型的值,true 表示指定标识符名称的用户实例存在,false 表示不存在指定标识符名称的用户实例。</returns>
 91         public bool IsExists(string userKey)
 92         {
 93             bool result = false;
 94             if (!string.IsNullOrWhiteSpace(userKey) && !string.IsNullOrEmpty(userKey))
 95             {
 96                 return _userContainer.ContainsKey(userKey);
 97             }
 98             return result;
 99         }
100
101         /// <summary>
102         /// 清空所有在线的用户实例。
103         /// </summary>
104         public async Task Clear()
105         {
106             foreach (var item in _userContainer.Keys)
107             {
108                 WebSocket socket;
109                 if (_userContainer.TryRemove(item, out socket))
110                 {
111                     await socket.CloseAsync(WebSocketCloseStatus.NormalClosure,"Close",CancellationToken.None);
112                 }
113             }
114         }
115
116         /// <summary>
117         /// 获取所有在线用户的人数。
118         /// </summary>
119         public int Count
120         {
121             get { return _userContainer.Count; }
122         }
123
124         /// <summary>
125         /// 向所有在线用户发送消息,当然也包括自己在内。
126         /// </summary>
127         /// <param name="content">具体要发送消息的内容。</param>
128         /// <param name="cancellationToken">取消发送的标识对象。</param>
129         /// <returns>该操作是异步完成的。</returns>
130         public async Task Send(string content, CancellationToken? cancellationToken = null)
131         {
132             if (!string.IsNullOrEmpty(content) && !string.IsNullOrWhiteSpace(content))
133             {
134                 if (cancellationToken == null)
135                 {
136                     cancellationToken = CancellationToken.None;
137                 }
138                 ArraySegment<byte> buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(content));
139                 foreach (var item in _userContainer.Values)
140                 {
141                     await item.SendAsync(buffer, WebSocketMessageType.Text, true, cancellationToken.Value);
142                 }
143             }
144         }
145
146         /// <summary>
147         /// 如果没有指定接受消息人员的列表,默认就是向所有人发送消息。如果指定了接收消息的人员列表,只有指定的人才会接受到消息。
148         /// </summary>
149         /// <param name="content">具体要发送消息的内容</param>
150         /// <param name="cancellationToken">取消发送的标识对象。</param>
151         /// <param name="includedUsers">具体接收消息的用户列表。</param>
152         /// <returns>该操作是异步完成的。</returns>
153         public async Task Send(string content, CancellationToken? cancellationToken, params string[] includedUsers)
154         {
155             if (!string.IsNullOrEmpty(content) && !string.IsNullOrWhiteSpace(content))
156             {
157                 if (includedUsers == null || includedUsers.Length <= 0)
158                 {
159                     await Send(content, cancellationToken);
160                 }
161                 else
162                 {
163                     if (cancellationToken == null)
164                     {
165                         cancellationToken = CancellationToken.None;
166                     }
167                     ArraySegment<byte> buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(content));
168                     foreach (var item in _userContainer)
169                     {
170                         foreach (var name in includedUsers)
171                         {
172                             if (string.Compare(name, item.Key, true) == 0)
173                             {
174                                 await item.Value.SendAsync(buffer, WebSocketMessageType.Text, true, cancellationToken.Value);
175                             }
176                         }
177                     }
178                 }
179             }
180         }
181
182         /// <summary>
183         /// 如果没有指定哪些在线人员不需要接受信息,就向所用的在线用户发送消息,如果指定了不接受消息人员的列表,就去掉这些在线用户,向其他向所有在线用户发送消息。
184         /// </summary>
185         /// <param name="content">具体要发送消息的内容。</param>
186         /// <param name="cancellationToken">取消发送的标识对象。</param>
187         /// <param name="excludedUsers">具体不需要接收消息的用户列表。</param>
188         /// <returns>该操作是异步完成的。</returns>
189         public async Task SendUn(string content, CancellationToken? cancellationToken = null, params string[] excludedUsers)
190         {
191             if (!string.IsNullOrEmpty(content) && !string.IsNullOrWhiteSpace(content))
192             {
193                 if (excludedUsers == null || excludedUsers.Length <= 0)
194                 {
195                     await Send(content,null);
196                 }
197                 if (cancellationToken == null)
198                 {
199                     cancellationToken = CancellationToken.None;
200                 }
201                 ArraySegment<byte> buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(content));
202                 foreach (var item in _userContainer)
203                 {
204                     foreach (var userName in excludedUsers)
205                     {
206                         if (string.Compare(item.Key, userName, true) != 0)
207                         {
208                             await item.Value.SendAsync(buffer, WebSocketMessageType.Text, true, cancellationToken.Value);
209                         }
210                     }
211                 }
212             }
213         }
214     }
215 }

 

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

 1 using System.Text.RegularExpressions;
 2
 3 namespace ChatAndUploadBaseWebSocket
 4 {
 5     /// <summary>
 6     /// 该类型定义上传文件扩展名是否有效的验证器。
 7     /// </summary>
 8     public sealed class UploadFileExtensionValidator
 9     {
10         /// <summary>
11         /// 验证上传的文件的格式是否是有效的。true 表示是有效的文件格式,false 表示不是有效的文件格式。
12         /// </summary>
13         /// <param name="value">要验证的文件名。</param>
14         /// <returns>返回布尔类型的值,true 表示是有效的文件格式,false 表示不是有效的文件格式。</returns>
15         public static bool ValidateFiles(string value)
16         {
17             if (!string.IsNullOrEmpty(value) && !string.IsNullOrWhiteSpace(value) && value.IndexOf(".") != -1)
18             {
19                 bool result = Regex.IsMatch(value, @"^.+\.(jpg|png|gif|bmp|rar|txt|zip|doc|ppt|xls|pdf|docx|xlsx|jpeg|xml|csv)(\?.+)?$",RegexOptions.IgnoreCase);
20                 return result;
21             }
22             return false;
23         }
24
25         /// <summary>
26         /// 验证图片的格式是否正确,true 表示是有效的图品格式,false 表示不是有效的图片格式。
27         /// </summary>
28         /// <param name="value">要验证的图片名称。</param>
29         /// <returns>返回布尔类型的值,true 表示是有效的图品格式,false 表示不是有效的图片格式。</returns>
30         public static bool ValidateImages(string value)
31         {
32             if (!string.IsNullOrEmpty(value) && !string.IsNullOrWhiteSpace(value) && value.IndexOf(".") != -1)
33             {
34                 bool result = Regex.IsMatch(value, @"^.+\.(jpg|png|gif|bmp|jpeg)(\?.+)?$", RegexOptions.IgnoreCase);
35                 return result;
36             }
37             return false;
38         }
39     }
40 }

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

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

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

  1 using System;
  2 using System.IO;
  3 using System.Net.WebSockets;
  4 using System.Text;
  5 using System.Threading;
  6 using System.Threading.Tasks;
  7 using System.Web;
  8 using System.Web.WebSockets;
  9
 10 namespace ChatAndUploadBaseWebSocket
 11 {
 12     /// <summary>
 13     /// 基于 HttpHandler 实现的文件上传的功能。
 14     /// </summary>
 15     public sealed class WebSocketUploadFilesHandler
 16     {
 17         /// <summary>
 18         /// 初始化类型的新实例。
 19         /// </summary>
 20         public WebSocketUploadFilesHandler() { }
 21
 22         /// <summary>
 23         /// 处理从客户端上传的文件。
 24         /// </summary>
 25         /// <param name="context">WebSocket 请求的上下文。</param>
 26         /// <returns>返回异步操作的实例对象 Task。</returns>
 27         public async Task ProcessFile(AspNetWebSocketContext context)
 28         {
 29             ArraySegment<byte> everyTimeBufferSize;
 30             WebSocketReceiveResult result;
 31             string message;
 32
 33             //1、获取当前的 WebSocket 对象。
 34             WebSocket webSocket = context.WebSocket;
 35             string fileName = null;
 36             byte[] bufferAllSize = new byte[1024 * 256 * 2];//缓存文件总的大小,用于暂时缓存
 37             int loaded = 0; //当前缓存的位置。
 38
 39             //2、监听来至客户端的 WebSocket 请求
 40             while (true)
 41             {
 42                 //此处的值是控制读取客户端数据的长度,如果客户端发送的数据长度超过当前缓存长度,则读取多次。
 43                 everyTimeBufferSize = new ArraySegment<byte>(new byte[1024 * 256]);
 44
 45                 //接受客户端发送来的消息。
 46                 result = await webSocket.ReceiveAsync(everyTimeBufferSize, CancellationToken.None);
 47                 if (webSocket.State == WebSocketState.Open)
 48                 {
 49                     //判断发送的数据是否已经结束。
 50                     int currentLength = Math.Min(everyTimeBufferSize.Array.Length, result.Count);
 51
 52                     try
 53                     {
 54                         //判断客户端发送的消息的类型
 55                         if (result.MessageType == WebSocketMessageType.Text)
 56                         {
 57                             message = Encoding.UTF8.GetString(everyTimeBufferSize.Array, 0, currentLength);
 58                             bool isValid = UploadFileExtensionValidator.ValidateFiles(message);
 59                             if (!isValid && string.Compare(message, "[file:{(:finished:)}200]", true) != 0)
 60                             {
 61                                 continue;
 62                             }
 63                             if (string.Compare(message, "[file:{(:finished:)}200]", true) == 0)
 64                             {
 65                                 SaveFile(fileName, bufferAllSize, loaded);
 66                                 loaded = 0;
 67                             }
 68                             else
 69                             {
 70                                 fileName = message;
 71                             }
 72                         }
 73                         else if (result.MessageType == WebSocketMessageType.Binary)
 74                         {
 75                             var temp = loaded + currentLength;
 76                             if (temp > bufferAllSize.Length)
 77                             {
 78                                 SaveFile(fileName, bufferAllSize, loaded);
 79                                 //添加到缓存区
 80                                 Array.Copy(everyTimeBufferSize.Array, 0, bufferAllSize, 0, currentLength);
 81                                 loaded = currentLength;
 82                             }
 83                             else
 84                             {
 85                                 //添加到缓冲区
 86                                 Array.Copy(everyTimeBufferSize.Array, 0, bufferAllSize, loaded, currentLength);
 87                                 loaded = temp;
 88                             }
 89                         }
 90                     }
 91                     catch (Exception)
 92                     {
 93                         throw;
 94                     }
 95                 }
 96             }
 97         }
 98
 99         /// <summary>
100         /// 将文件以追加的形式保存在物理磁盘上。
101         /// </summary>
102         /// <param name="fileName">要保存的文件的名称。</param>
103         /// <param name="buffer">每次要保存的二进制文件数据。</param>
104         /// <param name="loaded">要追加文件的数据长度。</param>
105         private void SaveFile(string fileName, byte[] buffer, int length)
106         {
107             if (string.IsNullOrEmpty(fileName) || string.IsNullOrWhiteSpace(fileName))
108             {
109                 return;
110             }
111             if (buffer == null || buffer.Length <= 0)
112             {
113                 return;
114             }
115             if (length < 0)
116             {
117                 return;
118             }
119
120             string currentDirectory = HttpContext.Current.Server.MapPath("/UploadFiles/");
121             string filePathFullName = currentDirectory + fileName;
122             try
123             {
124                 if (!Directory.Exists(currentDirectory))
125                 {
126                     Directory.CreateDirectory(currentDirectory);
127                 }
128                 using (FileStream fileStream = new FileStream(filePathFullName, FileMode.Append, FileAccess.Write))
129                 {
130                     fileStream.Write(buffer, 0, length);
131                 }
132             }
133             catch (Exception ex)
134             {
135                 //可以写入日志
136                 throw;
137             }
138         }
139     }
140 }

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

原文地址:https://www.cnblogs.com/PatrickLiu/p/12123882.html

时间: 2024-11-08 19:02:15

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

BootStrap Progressbar 实现大文件上传的进度条

1.首先实现大文件上传,如果是几兆或者几十兆的文件就用基本的上传方式就可以了,但是如果是大文件上传的话最好是用分片上传的方式.我这里主要是使用在客户端进行分片读取到服务器段,然后保存,到了服务器段读取完了之后将分片数据进行组合. 2.前端代码如下: <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="UploadTest2.aspx.cs" Inherits="Htm

php实现大文件上传带进度条

1.使用PHP的创始人 Rasmus Lerdorf 写的APC扩展模块来实现(http://pecl.php.net/package/apc) APC实现方法: 安装APC,参照官方文档安装,可以使用PECL模块安装方法快速简捷,这里不说明 配置php.ini,设置参数 apc.rfc1867=1 ,使APC支持上传进度条功能,在APC源码说明文档里面有说明 代码范例: 大文件(50G)上传的实现细节: 服务端接收文件数据的处理逻辑代码: 2.使用PECL扩展模块uploadprogress实

asp.net web大文件上传带进度条实例代码

using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.Ht

【原创】用JAVA实现大文件上传及显示进度信息

用JAVA实现大文件上传及显示进度信息 ---解析HTTP MultiPart协议 一. 大文件上传基础描述: 各种WEB框架中,对于浏览器上传文件的请求,都有自己的处理对象负责对Http MultiPart协议内容进行解析,并供开发人员调用请求的表单内容. 比如: Spring 框架中使用类似CommonsMultipartFile对象处理表二进制文件信息. 而.NET 中使用HtmlInputFile/ HttpPostedFile对象处理二进制文件信息. 优点:使用框架内置对象可以很方便的

基于Nodejs的大文件上传之断点续传

接着<扒一扒Nodejs formidable的onPart>和<也说文件上传之兼容IE789的进度条---丢掉flash>:前面已完成兼容IE789的大文件上传:无flash的低版本进度条,高版本的分段上传,并已为断点续传做好铺垫: 说什么做好铺垫,原本以为Nodejs端已没问题,只剩前端依靠HTML5接着监听abort事件,保存中断时上传到第几块了(断点续传只支持文件比较大,然后意外上传中断了,暂时定50M开启断点续传吧),通过文件内容hash和该文件唯一上传token来记录断

Nodejs+HTML5兼容IE789的大文件上传完整版

业余将大文件上传重新梳理了一遍,后端基于Nodejs:有几个要点感觉很好玩: 兼容性:IE789为代表: 跨域上传:document.domain||middlePage: 多文件上传:input['type=file'] multiple: 拖拽上传:drag drop: 大文件分段:files.slice(s,e): 断点续传:localStorage: 接收分段的文件:formidable.onPart: 陆续写入分段文件:fs.write(fd,bf,offset,length,posi

封装了okhttp的网络框架,支持大文件上传下载,上传进度...

本帖最后由 anjoy紫外线 于 2016-4-20 16:42 编辑   1.用法 对于Eclipse不能运行项目的,提供了apk供直接运行,位于项目根目录 okhttputils_v1.x.x.apk. 本项目Demo的网络请求是我自己的服务器,有时候可能不稳定,网速比较慢时请耐心等待.. 对于Android Studio的用户,可以选择添加: compile 'com.lzy.net:okhttputils:1.3.0'  //可以单独使用,不需要依赖下方的扩展包    compile'c

spring boot 大文件上传实现方式(一)

控制器 /** * 上传文件 * * @param param * @param request * @return * @throws Exception */@ApiOperation(value = "大文件上传")@PostMapping(value = "/fileUpload")@ResponseBodypublic Result<String> fileUpload(MultipartFileParam param, HttpServlet

Web大文件上传断点续传解决方案

最近遇见一个需要上传百兆大文件的需求,调研了七牛和腾讯云的切片分段上传功能,因此在此整理前端大文件上传相关功能的实现. 在某些业务中,大文件上传是一个比较重要的交互场景,如上传入库比较大的Excel表格数据.上传影音文件等.如果文件体积比较大,或者网络条件不好时,上传的时间会比较长(要传输更多的报文,丢包重传的概率也更大),用户不能刷新页面,只能耐心等待请求完成. 下面从文件上传方式入手,整理大文件上传的思路,并给出了相关实例代码,由于PHP内置了比较方便的文件拆分和拼接方法,因此服务端代码使用