基於 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年有一個優秀的成績。

            

              

              


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM