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

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

          别的先不说,先上一个截图,让大家看一下效果,版本虽然粗糙,但是该有的功能都有了,大家可以根据自己的需要改成自己的东西。效果图如下:
基于 WebSocket 的聊天和大文件上传(有进度提示)完美实现_第1张图片

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

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

        第一步:项目截图:(VS2017)
           基于 WebSocket 的聊天和大文件上传(有进度提示)完美实现_第2张图片
          第二步:前端代码

  1 DOCTYPE html>
  2 <html>
  3 <head>
  4     <meta name="viewport" content="width=device-width" />
  5     <title>Indextitle>
  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('
文件名:' + fileNameTrim(_this.file.name, 6) + ',文件大小:【' + (_this.curLoaded / (1024 * 1024)).toFixed(3) + '】M,上传时间:【' + ((new Date().getTime() - _this.startTime.getTime()) / 1000) + '】秒!
'); 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("
读取终止,已读取:" + percentValue + "
"); 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("
读取继续,已读取:" + percentValue + "
"); 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     /// 
 7     /// UploadFilesHandler 的摘要说明
 8     /// 
 9     public class UploadFilesHandler : IHttpHandler
10     {
11         private WebSocketUploadFilesHandler uploadFileHandler;
12 
13         /// 
14         /// 处理来之客户端 WebSocket 请求。
15         /// 
16         /// 
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         /// 
30         /// 指示该处理器是否可以重用。默认不重用。
31         /// 
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     /// 
 7     /// 基于 HttpHandler 实现的聊天功能。
 8     /// 
 9     public class WebChatHandler : IHttpHandler
10     {
11         private WebSocketChatHandler chatHandler;
12         private string userKey = null;
13 
14         /// 
15         /// 处理来至客户端的 WebSocket请求。
16         /// 
17         /// WebSocket 请求的上下文。
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         /// 
39         /// 指示该处理器是否可以重用,默认不可以重用。
40         /// 
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     /// 
12     /// 该类型定义在线用户的管理器的抽象接口。
13     /// 
14     public interface IOnlineUserManager
15     {
16         /// 
17         /// 增加新用户。
18         /// 
19         /// 增加用户的标识符名称。
20         /// 增加的用户标识符对应的 WebSocket 对象实例。
21         /// 返回布尔类型的值,true 表示增加用户成功,false 表示增加用户失败。
22         void Add(string userKey, WebSocket webSocket);
23 
24         /// 
25         /// 移除指定用户标识符名称的用户实例。
26         /// 
27         /// 要移除用户的标识符。
28         /// 返回布尔类型的值,true 表示移除用户成功,false 表示移除用户失败。
29         Task Remove(string userKey);
30 
31         /// 
32         /// 要获取指定名称名称的用户实例。
33         /// 
34         /// 要获取用户实例的标识符名称。
35         /// 如果获取到就返回其实,没有就返回 Null 值。
36         WebSocket Get(string userKey);
37 
38         /// 
39         /// 根据指定用户标识符名称判断相应用户实例是否存在。
40         /// 
41         /// 要判断用户实例是否存在的标识符名称。
42         /// 返回布尔类型的值,true 表示指定标识符名称的用户实例存在,false 表示不存在指定标识符名称的用户实例。
43         bool IsExists(string userKey);
44 
45         /// 
46         /// 清空所有在线的用户实例。
47         /// 
48         Task Clear();
49 
50         /// 
51         /// 获取所有在线用户的人数。
52         /// 
53         int Count { get; }
54 
55         /// 
56         /// 向所有在线用户发送消息,当然也包括自己在内。
57         /// 
58         /// 具体要发送消息的内容。
59         /// 取消发送的标识对象。        
60         /// 该操作是异步完成的。
61         Task Send(string content, CancellationToken? cancellationToken = null);
62 
63 
64         /// 
65         /// 如果没有指定接受消息人员的列表,默认就是向所有人发送消息。如果指定了接收消息的人员列表,只有指定的人才会接受到消息。
66         /// 
67         /// 具体要发送消息的内容
68         /// 取消发送的标识对象。
69         /// 具体接收消息的用户列表。
70         /// 该操作是异步完成的。
71         Task Send(string content, CancellationToken? cancellationToken, params string[] includedUsers);
72 
73         /// 
74         /// 如果没有指定哪些在线人员不需要接受信息,就向所用的在线用户发送消息,如果指定了不接受消息人员的列表,就去掉这些在线用户,向其他向所有在线用户发送消息。
75         /// 
76         /// 具体要发送消息的内容。
77         /// 取消发送的标识对象。
78         /// 具体不需要接收消息的用户列表。
79         /// 该操作是异步完成的。
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     /// 
 11     /// 该类型定义在线用户的管理器,该类型不可以被继承。
 12     /// 
 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         /// 
 24         /// 初始化 OnlineUsersManager 类型的新实例。
 25         /// 
 26         private OnlineUsersManager()
 27         {
 28             _userContainer = new ConcurrentDictionary<string, WebSocket>();
 29         }        
 30 
 31         /// 
 32         /// 增加新用户。
 33         /// 
 34         /// 增加用户的标识符名称。
 35         /// 增加的用户标识符对应的 WebSocket 对象实例。
 36         /// 返回布尔类型的值,true 表示增加用户成功,false 表示增加用户失败。
 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         /// 
 50         /// 移除指定用户标识符名称的用户实例。
 51         /// 
 52         /// 要移除用户的标识符。
 53         /// 返回布尔类型的值,true 表示移除用户成功,false 表示移除用户失败。
 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         /// 
 70         /// 要获取指定名称名称的用户实例。
 71         /// 
 72         /// 要获取用户实例的标识符名称。
 73         /// 如果获取到就返回其实,没有就返回 Null 值。
 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         /// 
 87         /// 根据指定用户标识符名称判断相应用户实例是否存在。
 88         /// 
 89         /// 要判断用户实例是否存在的标识符名称。
 90         /// 返回布尔类型的值,true 表示指定标识符名称的用户实例存在,false 表示不存在指定标识符名称的用户实例。
 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         /// 
102         /// 清空所有在线的用户实例。
103         /// 
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         /// 
117         /// 获取所有在线用户的人数。
118         /// 
119         public int Count
120         {
121             get { return _userContainer.Count; }
122         }
123 
124         /// 
125         /// 向所有在线用户发送消息,当然也包括自己在内。
126         /// 
127         /// 具体要发送消息的内容。
128         /// 取消发送的标识对象。        
129         /// 该操作是异步完成的。
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         /// 
147         /// 如果没有指定接受消息人员的列表,默认就是向所有人发送消息。如果指定了接收消息的人员列表,只有指定的人才会接受到消息。
148         /// 
149         /// 具体要发送消息的内容
150         /// 取消发送的标识对象。
151         /// 具体接收消息的用户列表。
152         /// 该操作是异步完成的。
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         /// 
183         /// 如果没有指定哪些在线人员不需要接受信息,就向所用的在线用户发送消息,如果指定了不接受消息人员的列表,就去掉这些在线用户,向其他向所有在线用户发送消息。
184         /// 
185         /// 具体要发送消息的内容。
186         /// 取消发送的标识对象。
187         /// 具体不需要接收消息的用户列表。
188         /// 该操作是异步完成的。
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     /// 
 6     /// 该类型定义上传文件扩展名是否有效的验证器。
 7     /// 
 8     public sealed class UploadFileExtensionValidator
 9     {
10         /// 
11         /// 验证上传的文件的格式是否是有效的。true 表示是有效的文件格式,false 表示不是有效的文件格式。
12         /// 
13         /// 要验证的文件名。
14         /// 返回布尔类型的值,true 表示是有效的文件格式,false 表示不是有效的文件格式。
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         /// 
26         /// 验证图片的格式是否正确,true 表示是有效的图品格式,false 表示不是有效的图片格式。
27         /// 
28         /// 要验证的图片名称。
29         /// 返回布尔类型的值,true 表示是有效的图品格式,false 表示不是有效的图片格式。
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     /// 
 13     /// 基于 HttpHandler 实现的聊天功能。
 14     /// 
 15     public sealed class WebSocketChatHandler
 16     {
 17         #region 私有字段
 18 
 19         private string _userKey;
 20 
 21         #endregion
 22 
 23         #region 构造函数
 24 
 25         /// 
 26         /// 以指定的默认值初始化该类型的新实例。默认值:无界
 27         /// 
 28         public WebSocketChatHandler():this("无界"){}
 29 
 30         /// 
 31         /// 以指定的用户标识符初始化该类型的新实例。
 32         /// 
 33         /// 用户的标识符。
 34         /// userKey is null.
 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         /// 
 68         /// 处理客户端发送过来的文本信息。
 69         /// 
 70         /// WebSocket 请求的上下文。
 71         /// 返回异步操作的实例对象 Task 。
 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($"
用户【{CurrentUserKey}】已经在线!
", CancellationToken.None, CurrentUserKey); 97 } 98 else 99 { 100 OnlineUsersManager.Current.Add(CurrentUserKey,webSocket); 101 //表示登陆成功 102 //某人成功登陆后,可以给群里其他人发送登陆成功的提示消息(本人除外) 103 messageNotice = $"
用户【{CurrentUserKey}】进入聊天室,登录时间:{DateTime.Now.ToString("yyyy-M-dd HH:mm")}
"; 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 = $"
用户【{CurrentUserKey}】离开聊天室,退出时间:{DateTime.Now.ToString("yyyy-M-dd HH:mm")}
"; 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 = $"
{CurrentUserKey}     {DateTime.Now.ToString("yyyy-MM-dd HH:mm")}{messageMain}
"; 147 await OnlineUsersManager.Current.Send(content, CancellationToken.None, receiveUser); 148 149 //私聊给自己 150 selfContent = $"
{CurrentUserKey}    {DateTime.Now.ToString("yyyy-MM-dd HH:mm")}{messageMain}
"; 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 = $"
用户【{receiveUser}】不在线!
"; 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 = $"
{CurrentUserKey}     {DateTime.Now.ToString("yyyy-MM-dd HH:mm")}{messageBody}
"; 172 await OnlineUsersManager.Current.SendUn(content, CancellationToken.None, CurrentUserKey); 173 174 //单独在给自己发送一份 175 selfContent = $"
{CurrentUserKey}     {DateTime.Now.ToString("yyyy-MM-dd HH:mm")}{messageBody}
"; 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 /// 187 /// 如果文本中包含文件名,就将文件名转换带链接的文件名,便于下载。 188 /// 189 /// 要转换的内容。 190 /// 返回成功转换的值。 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[] { "
"},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("\"{2}\"","/UploadFiles/"+values[i],DateTime.Now.ToString(),values[i]); 209 } 210 else 211 { 212 fileLinkBuilder.AppendFormat("\"{2}\"
", "/UploadFiles/" + values[i], DateTime.Now.ToString(), values[i]); 213 } 214 } 215 else 216 { 217 if (i == values.Length - 1) 218 { 219 fileLinkBuilder.AppendFormat("{1}","/UploadFiles/"+values[i],values[i]); 220 } 221 else 222 { 223 fileLinkBuilder.AppendFormat("{1}
", "/UploadFiles/" + values[i], values[i]); 224 } 225 } 226 } 227 } 228 else 229 { 230 fileLinkBuilder.Append(values[i]+"
"); 231 } 232 } 233 return fileLinkBuilder.ToString(); 234 } 235 return value; 236 } 237 238 /// 239 /// 判断指定文件名的文件是否存在,true 表示存在,false 表示不存在。 240 /// 241 /// 要判断是否存在的文件名。 242 /// 返回布尔类型的值,true 表示文件存在,false 表示文件不存在。 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     /// 
 13     /// 基于 HttpHandler 实现的文件上传的功能。
 14     /// 
 15     public sealed class WebSocketUploadFilesHandler
 16     {
 17         /// 
 18         /// 初始化类型的新实例。
 19         /// 
 20         public WebSocketUploadFilesHandler() { }
 21 
 22         /// 
 23         /// 处理从客户端上传的文件。
 24         /// 
 25         /// WebSocket 请求的上下文。
 26         /// 返回异步操作的实例对象 Task。
 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         /// 
100         /// 将文件以追加的形式保存在物理磁盘上。
101         /// 
102         /// 要保存的文件的名称。
103         /// 每次要保存的二进制文件数据。
104         /// 要追加文件的数据长度。
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年有一个优秀的成绩。

            

              

              

你可能感兴趣的:(基于 WebSocket 的聊天和大文件上传(有进度提示)完美实现)