大家好,好久没有写文章了,当然不是不想写,主要是工作太忙,公司有没有网络环境,不让上网,所以写的就少了。今天是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} {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} {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} {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} {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年有一个优秀的成绩。