大家好,好久没有写文章了,当然不是不想写,主要是工作太忙,公司有没有网络环境,不让上网,所以写的就少了。今天是2019年的最后一天,明天就要开始新的一年,当然也希望自己有一个新的开始。在2019年的最后一天,写点东西,作为这一年的总结吧!写点啥呢?最近有时间,由于公司的需要,需要实现一个自己的、Web版本的聊天工具,当然也要能传输文件。经过两个星期的无网络、艰苦的学习,终于写出了一个最初的版本。在公司里面里面已经生成正式版本了,很多类型都进行了抽象化,支持注册,头像,私信,群聊,传输大文件,类似 Web 版本的QQ。那是公司的东西,这个版本是我又重新写的,没有做过多的设计,但是功能都实现了,这个版本还比较粗糙,有时间在写第二个版本。
别的先不说,先上一个截图,让大家看一下效果,版本虽然粗糙,但是该有的功能都有了,大家可以根据自己的需要改成自己的东西。效果图如下:
好了,以上就是效果图,挺实用的,大家只要稍加修改就可以使用,所有代码都是可以正常使用的。
代码挺多的,一步一步的来。我先对我的项目做个截图,让大家做到心里有数。项目分为两个部分,一个部分是类库,主要实现代码再次,还有一个就是MVC的前端的项目。
第一步:项目截图:(VS2017)
第二步:前端代码
1 <!DOCTYPE html> 2 <html 3 head 4 Meta name="viewport" content="width=device-width" /> 5 title>Index</ 6 style type="text/css" 7 html,body { 8 font-size: 12px; 9 height 100% 10 width 11 overflow-y auto 12 } 13 14 textarea 15 16 color black 17 18 19 #messageContent 20 background-color cadetblue 21 border 1px solid black 22 500px 23 24 14px 25 26 27 28 .chatLeft 29 display block 30 chocolate 31 32 margin-top 5px 33 margin-left 10px 34 padding 3px 35 float left 36 clear both 37 38 39 .chatRight 40 41 white 42 43 44 margin-right 45 46 text-align right 47 48 49 50 51 .chatTitleSingle 52 white-space Nowrap 53 54 border-radius 55 56 57 #267b8a 58 59 60 61 62 .chatTitleGroup 63 64 65 66 67 #b61111 68 69 70 max-width 250px 71 min-width 200px 72 left 73 74 75 .chatSelfContent 76 77 78 79 80 81 #51d870 82 83 84 85 86 87 .chatContent 88 89 90 91 92 93 94 95 96 97 98 99 .loginContent 100 101 center102 103 gold104 font-weight bold105 106 107 108 .logoutContent 109 110 111 112 darkslateblue113 114 115 116 117 .fileUploadedFinished 118 119 120 121 122 123 124 125 .offlineUser 126 127 128 dimgrey129 130 131 132 133 #chatandFileContainer 134 margin135 136 137 #spnNoticeText 138 red139 140 141 .noticeMessageInContainer 142 143 144 145 146 147 148 a:link 149 #03516f150 text-decoration none151 152 153 a:visited 154 #1ea73c155 wavy156 157 158 a:hover 159 #0597d0160 underline161 162 163 a:active 164 #0bbd33165 166 167 style168 169 body170 div171 ><span ="padding-left:5px;color:red;">提示:spanid="spnNoticeText">暂无连接!></172 div ="margin:5px 4px"173 链接服务:input ="text" name="name" placeholder="请输入登录标识" id="txtUserKey"174 button ="btnConnected" style="margin-right:10px;margin-left:10px;">建立连接button175 ="btnClose">关闭连接176 177 ="chatandFileContainer"="display:none"178 ="margin:5px 0px;"179 消息内容:textarea ="txtContent"="消息内容" cols="35" rows="5"textarea180 181 182 接受人名:="txtPrivateUserKey"="群聊无需填写接收人" 183 184 185 文件上传:="file"="border:1px solid black;margin:0px;padding:0px;width:300px;" multiple 186 187 ="uploadProgress"188 ="margin:10px 60px;"189 ="btnSendGroup"="margin-right:10px">群 聊> ="btnSendPrivate">私 聊190 191 192 ="messageContent"193 194 br 195 script ="text/javascript" src="~/Scripts/jquery-1.10.2.min.js"script196 ="~/Scripts/MyScripts/ChatandUploadFilesProcessHandler.js"197 198 >
第三步:JavaScript 代码,文件名:ChatandUploadFilesProcessHandler.js
//封装文件上传和聊天。 2 (function () { 3 生命全局变量 var webSocketInstance; var chatUrl = "ws://localhost:62073/HttpHandlers/WebChatHandler.ashx"; var isSendFileGroup = false;是否是群发文件,默认状态不是群发。 7 var isOnline = false 8 var mainProcess = { 9 1、初始化基本事件 10 init: 11 this.initClick(); 12 }, 13 2、建立通讯事件。 14 initConnect: 15 if (isOnline == ) { 16 var newUrl = chatUrl + "?userKey=" + $("#txtUserKey").val(); 17 18 webSocketInstance = new WebSocket(newUrl); 19 20 2.1、建立网络连接的时候触发该事件 21 webSocketInstance.onopen = 22 $("#spnNoticeText").html("已经连接!"); 23 $("#chatandFileContainer").attr("style","display:block" 24 } 25 26 2.2、接受服务器发来的消息触发该事件。 27 webSocketInstance.onmessage = (evt) { 28 $("#messageContent").append(evt.data); 29 30 31 2.3、网络错误的时候触发该事件。 32 webSocketInstance.onerror = 33 $("#spnNoticeText").html(JSON.stringify(evt)); 34 35 36 2.4、当连接关闭的时候触发该事件。 37 webSocketInstance.onclose = 38 这里可以根据实际场景编写,比如重连机制。 39 $("#spnNoticeText").html("断开连接!" 40 $("#chatandFileContainer").attr("style","display:none" 41 42 isOnline = true 43 } 44 else 45 $("#spnNoticeText").html($("#txtUserKey").val()+"用户已经在线了!" 46 47 48 3、初始化各种点击事件。 49 initClick: 50 3.1、网络连接事件 51 $("#btnConnected").on("click", 52 if (document.getElementById("txtUserKey") && document.getElementById("txtUserKey").value == "" 53 $("#spnNoticeText").html("请输入登录用户的标识!" 54 return 55 56 mainProcess.initConnect(); 57 }); 58 59 3.2、网络连接事件 60 $("#btnClose").on("click",1)"> 61 if (webSocketInstance && webSocketInstance.readyState == WebSocket.OPEN) { 62 webSocketInstance.close(); 63 isOnline = 64 65 66 67 3.3、群发消息 68 $("#btnSendGroup").on("click",1)"> 69 if (webSocketInstance) { 70 if (webSocketInstance.readyState == 71 clearUploadProgress(); 72 var message = $("#txtContent" 73 74 if (message && message.length > 0 75 webSocketInstance.send(message); 76 } 77 78 if (document.getElementById("file").files.length > 0 79 isSendFileGroup = 80 uploadFiles(); 81 82 clearFilesuploader(); 83 84 } 85 else WebSocket.CLOSED) { 86 $("#spnNoticeText").html("已经与服务器断开连接!" 87 88 WebSocket.CONNECTING) { 89 $("#spnNoticeText").html("正在尝试与服务器建立连接!" 90 91 WebSocket.CLOSING) { 92 $("#spnNoticeText").html("正在关闭与服务器的连接!" 93 94 } 95 96 97 3.4、私聊发消息 98 $("#btnSendPrivate").on("click",1)"> 99 var userKey = $("#txtPrivateUserKey"100 if (userKey == null || userKey == "" || userKey.length <= 0101 $("#spnNoticeText").html("请输入接收用户的标识!"102 103 104 105 106 107 108 109 110 对消息进行拼接 "$--$--**"+ userKey +"$--$--**"+"要发送消息的内容"; 111 112 var finalMessage = "$--$--**" + userKey + "$--$--**" + message; 113 webSocketInstance.send(finalMessage); 114 115 116 117 isSendFileGroup = 118 119 120 121 122 123 124 $("#spnNoticeText").html("已经与服务器断开连接!"125 126 127 $("#spnNoticeText").html("正在尝试与服务器建立连接!"128 129 130 $("#spnNoticeText").html("正在关闭与服务器的连接!"131 132 133 134 } 135 }; 137 开始上传文件部分集成。 138 var filesUrl = "ws://localhost:62073/HttpHandlers/UploadFilesHandler.ashx"139 uploadOperate(file) { 140 (file) { 141 var _this = 142 this.reader = new FileReader();读取文件对象。 143 this.step = 1024 * 256; 每次读取文件的大小 144 this.curLoaded = 0; 当前读取位置 145 this.file = file; 当前文件对象。 146 this.enableRead = true;指示是否可以继续读取。 147 this.total = file.size;文件的总大小。 148 this.startTime = new Date();开始读取时间。 149 .createItem(); 150 this.initWebSocket(151 _this.bindReader(); 152 153 154 155 156 this.step = 1024 * 256157 this.curLoaded = 0158 159 this.total = 0160 161 } 162 uploadOperate.prototype =163 绑定读取事件 164 bindReader: 165 166 var reader = .reader; 167 var webSocketFileInstance = .webSocketFileInstance; 168 reader.onload = (e) { 169 判断是否能再次读取 170 if (_this.enableRead == 171 172 173 根据当前缓冲区控制读取速度 174 if (webSocketFileInstance.bufferedamount >= _this.step * 20175 setTimeout(176 _this.loadSuccess(e.loaded); 177 },5178 } 179 _this.loadSuccess(e.loaded); 180 181 182 开始读取 183 _this.readBlob(); 184 185 成功读取,继续处理 186 loadSuccess: (loaded) { var webSocketFileInstance = _this.webSocketFileInstance; 189 使用 WebSocket 将二进制输出上传到服务器。 var blob = _this.reader.result; 191 if (_this.curLoaded <= 0192 webSocketFileInstance.send(_this.file.name); 193 194 webSocketFileInstance.send(blob); 195 当前发送完成,继续读取。 196 _this.curLoaded += loaded; 197 if (_this.curLoaded < _this.total) { _this.readBlob(); 199 200 201 发送读取完成 202 webSocketFileInstance.send("[file:{(:finished:)}200]"203 this.showInfo('<div class=\"fileUploadedFinished\">文件名:' + fileNameTrim(_this.file.name,6) + ',文件大小:【' + (_this.curLoaded / (1024 * 1024)).toFixed(3) + '】M,上传时间:【' + ((new Date().getTime() - _this.startTime.getTime()) / 1000) + '】秒!</div>'204 205 显示进度 206 _this.showProgress(); 207 208 创建显示项 209 createItem: 210 211 var blockquote = document.createElement("blockquote"212 var abort = document.createElement("input"213 abort.type = 'button'214 abort.value = '暂停'215 abort.onclick = 216 _this.stop(); 217 }; 218 blockquote.appendChild(abort); 219 220 var containue = document.createElement("input"221 containue.type = 'button'222 containue.value = '继续'223 containue.onclick = 224 _this.containue(); 225 226 blockquote.appendChild(containue); 227 228 var progress = document.createElement('progress'229 progress.style.width = '300px'230 progress.max = 100231 progress.value = 0232 blockquote.appendChild(progress); 233 _this.progressBox = progress; 234 235 var status = document.createElement('span'236 status.id = 'Status'237 blockquote.appendChild(status); 238 _this.statusBox = status; 239 240 document.getElementById('uploadProgress').appendChild(blockquote); 241 242 243 showProgress: 244 245 var percent = ((_this.curLoaded / _this.total) * 100).toFixed(); 246 _this.progressBox.value = percent; 247 _this.statusBox.innerHTML =248 249 读取文件 250 readBlob: 251 var blob = this.file.slice(this.curLoaded,1)">this.curLoaded + .step); 252 .reader.readAsArrayBuffer(blob); 253 254 暂停读取 255 stop: 256 257 var percentValue = this.percent(this.curLoaded / .total); 258 if (percentValue != '100%'259 this.showInfo("<div class=\"noticeMessageInContainer\">读取终止,已读取:" + percentValue + "</div>"260 261 .reader.abort(); 262 263 继续读取 264 containue: 265 266 267 268 .readBlob(); 269 this.showInfo("<div class=\"noticeMessageInContainer\">读取继续,已读取:" + percentValue + "</div>"270 271 272 273 274 275 计算百分比 276 percent: (data) { 277 if (data == 0) { return 0; } 278 var valuePercent = Number(data * 100279 valuePercent += "%"280 valuePercent; 281 282 显示日志 283 showInfo: 284 var html = ""285 html += data; 286 document.getElementById("messageContent").innerHTML = document.getElementById("messageContent").innerHTML + html; 287 var divLogContainer = document.getElementById("messageContent"288 divLogContainer.scrollTop = divLogContainer.scrollHeight; 289 290 初始化 WebSocket 291 initWebSocket: (onSuccess) { 292 293 this.webSocketFileInstance = WebSocket(filesUrl); 294 295 webSocketFileInstance.onopen = 296 console.log("connect 链接创建成功"297 298 onSuccess(); 299 300 301 webSocketFileInstance.onmessage = 302 var data = e.data; 303 if (isNaN(data) == 304 showInfo('后台接受成功:' + data); 305 306 307 console.info(data); 308 309 310 webSocketFileInstance.onclose = 311 终止读取 312 313 showInfo("WebSocket 连接已经断开!"314 console.log("WebSocket 连接已断开。"315 316 webSocketFileInstance.onerror = 317 318 showInfo("发生异常:" + e.message); 319 console.log("发生异常:" +320 321 322 323 window.uploadOperate = uploadOperate; 324 window.mainProcess = mainProcess; 325 })(); 326 327 $(328 mainProcess.init(); 329 }); 330 331 上传文件的速度取决于每次 send() 的数据的大小。Google 之所以会慢,是因为他每次 send 的数据很小。 332 uploadFiles() { 333 var fileController = document.getElementById("file"334 checkAndUploadCore(fileController,1)">335 } 336 337 检查文件 338 var fileController2 = document.getElementById("file"339 fileController2.onchange = 340 clearUploadProgress(); 341 document.getElementById("txtContent").value = ""342 checkAndUploadCore(fileController2,1)">343 344 345 如果文件名太长,就会修剪。 346 fileName:文件名 347 length:要截取文件名的长度。 348 fileNameTrim(fileName,length) { 349 if (fileName && fileName.length > 0 && fileName != ""350 if (length > 0 && length >= fileName.length) { 351 fileName; 352 353 354 return fileName.substring(0,length) + "..."355 356 357 358 359 清除文件上传的进度条显示。为下一次做准备。 360 clearUploadProgress() { 361 uploadOperate(); 362 document.getElementById("uploadProgress").innerHTML = ""363 364 365 文件上传后将控件置为初始状态。 366 clearFilesuploader() { 367 document.getElementById("file").value = ""368 369 370 核心的上传文件的方法。 371 uploader:上传文件的控件。 372 isupload:是否开始上传文件。 373 checkAndUploadCore(uploader,isupload) { 374 if (uploader && uploader.files.length > 0375 var maxTotalSize = 5000;单位:M 376 var files = uploader.files; 377 var filetotalSize = 0378 var fileCount = 5379 var fileTypes = [".jpg",".gif",".bmp",".png","jpeg",".rar",".zip",".txt",".doc",".ppt",".xls",".pdf",".csv",".docx",".xlsx"]; 380 381 1、验证上传文件的格式。 382 var isValid = 383 var fileEnd = ''384 if (fileTypes && fileTypes.length > 0385 for (var m = 0; m < files.length; m++386 fileEnd = files[m].name.substring(files[m].name.lastIndexOf(".")); 387 isValid = 388 var i = 0; i < fileTypes.length; i++389 if (fileEnd.toLowerCase() == fileTypes[i].toLowerCase()) { 390 isValid = 391 continue392 393 394 if (!isValid) { 395 break396 397 398 399 alert("不支持此文件类型"400 uploader.value = ''401 return 402 403 404 405 2、检查文件上传的个数。 406 if (files.length > 0 && files.length > fileCount) { 407 alert("最多只能上传【" + fileCount + "】个文件!"408 uploader.value = ''409 410 411 412 3、检查文件的总大小。 413 var i = 0; i < files.length; i++414 filetotalSize += files[i].size; 415 416 filetotalSize = filetotalSize / (1024 * 1024417 filetotalSize = filetotalSize.toFixed(3418 if (filetotalSize > maxTotalSize) { 419 alert("上传文件总自己额大小不能大于【" + (maxTotalSize / 1024).toFixed() + "】G!"420 uploader.value = ''421 422 423 424 4、检查文件名是否有效。 425 var isFileNameValid = 426 var fileName = ''427 var containSpecial = RegExp(/[(\ )(\~)(\!)(\@)(\#)(\$)(\%)(\^)(\&)(\*)(\()(\))(\+)(\=)(\[)(\])(\{)(\})(\|)(\:)(\;)(\')(\")(\,)(\<)(\.)(\>)(\/)(\?)]+/428 429 fileName = files[m].name.substring(0,files[m].name.lastIndexOf("."430 (containSpecial.test(fileName)) { 431 isFileNameValid = 432 433 434 435 isFileNameValid) { 436 alert("文件名包含特殊字符,不可以上传!"437 uploader.value = ''438 439 440 441 442 443 444 445 (isupload) { 446 447 var file = files[i]; 448 var operate = uploadOperate(file); 449 450 451 452 var fileNameList = ""453 454 455 if (i == files.length - 1456 fileNameList += file.name; 457 } 458 fileNameList += file.name + "\n"459 460 461 document.getElementById("txtContent").value = fileNameList; 462 463 }
第四步:前端 文件上传代码,文件名:UploadFilesHandler.ashx
1 using ChatandUploadBaseWebSocket; 2 System.Web; 3 4 namespace WebApplicationForChat.HttpHandlers 5 { 6 /// <summary> 7 /// UploadFilesHandler 的摘要说明 8 </summary> 9 public class UploadFilesHandler : IHttpHandler 10 { 11 private WebSocketUploadFilesHandler uploadFileHandler; 12 13 14 处理来之客户端 WebSocket 请求。 15 16 <param name="context"></param> 17 void ProcessRequest(HttpContext context) 18 { 19 (context.IsWebSocketRequest) 20 { 21 if (uploadFileHandler == null) 22 { 23 uploadFileHandler = WebSocketUploadFilesHandler(); 24 25 context.AcceptWebSocketRequest(uploadFileHandler.ProcessFile); 26 } 27 28 29 30 指示该处理器是否可以重用。默认不重用。 31 32 bool IsReusable 33 34 get 35 36 37 38 39 40 }
第五步:前端聊天处理器代码,文件名:WebChatHandler.ashx
基于 HttpHandler 实现的聊天功能。 WebChatHandler : IHttpHandler WebSocketChatHandler chatHandler; 12 private string userKey = 13 处理来至客户端的 WebSocket请求。 17 <param name="context">WebSocket 请求的上下文。</param> 18 19 20 21 22 userKey = context.Request.QueryString["userKey"23 string.IsNullOrEmpty(userKey) && !string.IsNullOrWhiteSpace(userKey)) 25 if (chatHandler == { 27 chatHandler = WebSocketChatHandler(userKey); 28 29 else 30 31 chatHandler.CurrentUserKey = userKey; 32 context.AcceptWebSocketRequest(chatHandler.ProcessChat); 34 36 37 38 39 指示该处理器是否可以重用,默认不可以重用。 40 41 42 43 44 45 46 47 48 49 }
第六步:后端类库代码,文件名:IOnlineUserManager.cs
System; System.Collections.Generic; 3 System.Linq; System.Net.WebSockets; System.Text; 6 System.Threading; 7 System.Threading.Tasks; 8 9 ChatandUploadBaseWebSocket 11 12 该类型定义在线用户的管理器的抽象接口。 13 14 interface IOnlineUserManager 15 增加新用户。 18 19 <param name="userKey">增加用户的标识符名称。20 <param name="webSocket">增加的用户标识符对应的 WebSocket 对象实例。21 <returns>返回布尔类型的值,true 表示增加用户成功,false 表示增加用户失败。</returns> 22 void Add( userKey,WebSocket webSocket); 23 24 25 移除指定用户标识符名称的用户实例。 26 27 要移除用户的标识符。28 返回布尔类型的值,true 表示移除用户成功,false 表示移除用户失败。29 Task Remove( userKey); 30 32 要获取指定名称名称的用户实例。 33 34 要获取用户实例的标识符名称。35 如果获取到就返回其实,没有就返回 Null 值。36 WebSocket Get( 根据指定用户标识符名称判断相应用户实例是否存在。 41 要判断用户实例是否存在的标识符名称。42 返回布尔类型的值,true 表示指定标识符名称的用户实例存在,false 表示不存在指定标识符名称的用户实例。43 bool IsExists(44 45 46 清空所有在线的用户实例。 47 Task Clear(); 49 50 51 获取所有在线用户的人数。 52 53 int Count { get54 55 56 向所有在线用户发送消息,当然也包括自己在内。 57 58 <param name="content">具体要发送消息的内容。59 <param name="cancellationToken">取消发送的标识对象。</param> 60 该操作是异步完成的。61 Task Send(string content,CancellationToken? cancellationToken = 62 63 64 65 如果没有指定接受消息人员的列表,默认就是向所有人发送消息。如果指定了接收消息的人员列表,只有指定的人才会接受到消息。 66 67 具体要发送消息的内容68 69 <param name="includedUsers">具体接收消息的用户列表。70 71 Task Send(params [] includedUsers); 72 73 74 如果没有指定哪些在线人员不需要接受信息,就向所用的在线用户发送消息,如果指定了不接受消息人员的列表,就去掉这些在线用户,向其他向所有在线用户发送消息。 75 76 77 78 <param name="excludedUsers">具体不需要接收消息的用户列表。79 80 Task SendUn(null,1)">[] excludedUsers); 81 82 }
第七步:后端类库代码,文件名:OnlineUsersManager.cs
System.Collections.Concurrent; 4 5 6 7 8 9 10 11 该类型定义在线用户的管理器,该类型不可以被继承。 12 13 sealed OnlineUsersManager:IOnlineUserManager 14 15 private ConcurrentDictionary<string,WebSocket> _userContainer; 16 17 #region 获取单件对象 19 static readonly IOnlineUserManager Current = OnlineUsersManager(); 20 21 #endregion 22 23 24 初始化 OnlineUsersManager 类型的新实例。 25 26 OnlineUsersManager() 27 28 _userContainer = new ConcurrentDictionary<(); } 31 32 33 34 35 36 37 38 39 if (string.IsNullOrEmpty(userKey) || string.IsNullOrWhiteSpace(userKey) || webSocket == 40 41 42 43 _userContainer.ContainsKey(userKey)) 44 45 _userContainer.TryAdd(userKey,webSocket); 48 49 50 51 52 53 54 async Task Remove( userKey) 56 58 (_userContainer.ContainsKey(userKey)) 59 60 WebSocket temp; 61 if (_userContainer.TryRemove(userKey,1)">out temp)) 63 await temp.CloseAsync(WebSocketCloseStatus.normalClosure,Close,CancellationToken.None); 66 67 68 69 70 71 72 73 74 public WebSocket Get( 76 77 78 79 80 _userContainer[userKey]; 81 83 85 86 87 88 89 90 91 92 93 bool result = 94 string.IsNullOrWhiteSpace(userKey) && !.IsNullOrEmpty(userKey)) 96 _userContainer.ContainsKey(userKey); 97 98 result; 99 100 101 102 103 104 async Task Clear() 105 106 foreach (var item in _userContainer.Keys) 108 WebSocket socket; 109 if (_userContainer.TryRemove(item,1)"> socket)) 110 111 await socket.CloseAsync(WebSocketCloseStatus.normalClosure,1)">112 116 117 118 119 int Count 121 get { _userContainer.Count; } 123 124 125 126 127 128 129 130 async Task Send(132 string.IsNullOrEmpty(content) && !.IsNullOrWhiteSpace(content)) 134 if (cancellationToken == 136 cancellationToken = CancellationToken.None; 137 138 ArraySegment<byte> buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(content)); 139 _userContainer.Values) 140 141 await item.SendAsync(buffer,WebSocketMessageType.Text,cancellationToken.Value); 142 143 144 145 146 147 148 149 150 151 152 153 [] includedUsers) 154 156 157 if (includedUsers == null || includedUsers.Length <= 0158 159 await Send(content,cancellationToken); 161 162 163 164 165 cancellationToken =166 167 ArraySegment<168 _userContainer) 170 var name includedUsers) 171 { 172 string.Compare(name,item.Key,1)">true) == 173 { 174 await item.Value.SendAsync(buffer,1)">175 } 177 178 181 182 183 184 185 186 187 188 189 async Task SendUn([] excludedUsers) 190 193 if (excludedUsers == null || excludedUsers.Length <= 195 await Send(content,1)">196 197 199 cancellationToken =200 201 ArraySegment<202 203 204 var userName excludedUsers) 205 206 string.Compare(item.Key,userName,1)">true) != 208 209 210 } 211 212 213 214 215 }
第九步:后端类库代码,文件名:UploadFileExtensionValidator.cs
System.Text.RegularExpressions; 2 5 该类型定义上传文件扩展名是否有效的验证器。 8 UploadFileExtensionValidator 10 11 验证上传的文件的格式是否是有效的。true 表示是有效的文件格式,false 表示不是有效的文件格式。 12 <param name="value">要验证的文件名。返回布尔类型的值,true 表示是有效的文件格式,false 表示不是有效的文件格式。15 bool ValidateFiles( value) 16 17 string.IsNullOrEmpty(value) && !string.IsNullOrWhiteSpace(value) && value.IndexOf(.") != -119 bool result = Regex.IsMatch(value,1)">@"^.+\.(jpg|png|gif|bmp|rar|txt|zip|doc|ppt|xls|pdf|docx|xlsx|jpeg|xml|csv)(\?.+)?$noreCase); 20 22 23 24 验证图片的格式是否正确,true 表示是有效的图品格式,false 表示不是有效的图片格式。 要验证的图片名称。返回布尔类型的值,true 表示是有效的图品格式,false 表示不是有效的图片格式。30 bool ValidateImages(31 32 34 ^.+\.(jpg|png|gif|bmp|jpeg)(\?.+)?$35 37 40 }
第十步:后端类库代码,文件名:WebSocketChatHandler.cs
System.IO; 7 System.Web.WebSockets; 9 10 11 13 14 15 WebSocketChatHandler 16 #region 私有字段 _userKey; 23 #region 构造函数 24 26 以指定的默认值初始化该类型的新实例。默认值:无界 27 28 public WebSocketChatHandler():this(无界){} 29 30 以指定的用户标识符初始化该类型的新实例。 用户的标识符。<exception cref="ArgumentNullException">userKey is null.</exception> 35 public WebSocketChatHandler( 36 37 39 _userKey = 41 43 throw new ArgumentNullException( 46 47 49 #region 实例属性 51 CurrentUserKey 52 53 _userKey; } 54 set 56 .IsNullOrWhiteSpace(value)) 58 _userKey = value; 61 62 63 64 65 #region 核心方法 67 68 处理客户端发送过来的文本信息。 返回异步操作的实例对象 Task 。 72 Task ProcessChat(AspNetWebSocketContext context) 73 74 #region 局部变量 75 string messageNotice = 77 string messageBody = 78 string content = 79 string selfContent = 80 string receiveUser = 81 string[] arrays = 82 string messageMain = string offlineContent = 84 ArraySegment< echor; 85 ArraySegment< buffer; 86 WebSocketReceiveResult result; 87 88 89 90 1、获取 WebSocket 实例对象。 91 WebSocket webSocket = context.WebSocket; 92 bool isExists = OnlineUsersManager.Current.IsExists(CurrentUserKey); (isExists) 95 表示该用户在线。 await OnlineUsersManager.Current.Send($<div class=\"onlineUser\">用户【{CurrentUserKey}】已经在线!</div>100 OnlineUsersManager.Current.Add(CurrentUserKey,1)">101 表示登陆成功 102 某人成功登陆后,可以给群里其他人发送登陆成功的提示消息(本人除外) 103 messageNotice = $<div class=\"loginContent\">用户【{CurrentUserKey}】进入聊天室,登录时间:{DateTime.Now.ToString("yyyy-M-dd HH:mm)}</div> OnlineUsersManager.Current.Send(messageNotice); 106 107 2、开始监听来至客户端的 WebSocket 请求。 108 while (webSocket.State == WebSocketState.Open) 109 110 每次读取客户端发送来的消息的大小。 111 buffer = byte>(new byte[1024*256]); 112 113 result = webSocket.ReceiveAsync(buffer,1)">114 关闭 WebSocket 请求 115 if (result.MessageType == WebSocketMessageType.Close) 116 117 OnlineUsersManager.Current.Remove(CurrentUserKey); 118 119 发送离开提醒 120 messageNotice = $<div class=\"logoutContent\">用户【{CurrentUserKey}】离开聊天室,退出时间:{DateTime.Now.ToString(121 122 await webSocket.CloseAsync(WebSocketCloseStatus.normalClosure,1)">.Empty,1)">123 124 126 用于发送聊天内容 127 WebSocketMessageType.Text) 129 messageBody = Encoding.UTF8.GetString(buffer.Array,result.Count); 130 判断是群聊还是私聊 131 if (messageBody.Length > 8 && messageBody.Substring(0,1)">8) == $--$--**133 此处表示私聊 134 arrays = messageBody.Split(string[] { },StringSplitOptions.RemoveEmptyEntries); 135 receiveUser = arrays[137 messageMain = UbbAndHtmlConverter.UBBToHTML(arrays[138 messageMain = TextToAnchor(messageMain); 139 140 var isExistsUser = OnlineUsersManager.Current.IsExists(receiveUser); 141 (isExistsUser) { 143 string.IsNullOrEmpty(messageMain) && !.IsNullOrWhiteSpace(messageMain)) { 145 私聊给对方 146 content = $<div class=\"chatLeft\"><span class=\"chatTitleSingle\">{CurrentUserKey} {DateTime.Now.ToString("yyyy-MM-dd HH:mm)}</span><span class=\"chatContent\">{messageMain}</span></div>147 OnlineUsersManager.Current.Send(content,receiveUser); 148 149 私聊给自己 150 selfContent = $<div class=\"chatRight\"><span class=\"chatTitleSingle\">{CurrentUserKey} {DateTime.Now.ToString()}</span><span class=\"chatSelfContent\">{messageMain}</span></div>151 echor = (Encoding.UTF8.GetBytes(selfContent)); 152 await webSocket.SendAsync(echor,1)"> } } 155 157 offlineContent = $<div class=\"offlineUser\">用户【{receiveUser}】不在线!</div>158 echor = (Encoding.UTF8.GetBytes(offlineContent)); 159 162 163 164 messageBody = UbbAndHtmlConverter.UBBToHTML(messageBody); 165 messageBody = TextToAnchor(messageBody); 166 167 这里表示群聊 168 if (OnlineUsersManager.Current.Count > 170 群发给他人,不包含自己 171 content = $<div class=\"chatLeft\"><span class=\"chatTitleGroup\">{CurrentUserKey} {DateTime.Now.ToString()}</span><span class=\"chatContent\">{messageBody}</span></div>172 OnlineUsersManager.Current.SendUn(content,1)">173 174 单独在给自己发送一份 175 selfContent = $<div class=\"chatRight\"><span class=\"chatTitleGroup\">{CurrentUserKey} {DateTime.Now.ToString()}</span><span class=\"chatSelfContent\">{messageBody}</span></div>176 echor = 177 182 185 如果文本中包含文件名,就将文件名转换带链接的文件名,便于下载。 189 要转换的内容。190 返回成功转换的值。string TextToAnchor(193 string.IsNullOrWhiteSpace(value) && UploadFileExtensionValidator.ValidateFiles(value)) 195 string[] values = value.Split(<br/>},1)">196 StringBuilder fileLinkBuilder = new StringBuilder(1024197 198 int i = 0; i < values.Length; i++200 (UploadFileExtensionValidator.ValidateFiles(values[i])) 201 202 (IsExists(values[i])) 204 (UploadFileExtensionValidator.ValidateImages(values[i])) 206 if (i == values.Length - 208 fileLinkBuilder.AppendFormat(<img src=\"{0}\" title=\"上传时间:{1},右键点击保存\" alt=\"{2}\" width=\"200px\">",1)">/UploadFiles/"+values[i],DateTime.Now.ToString(),values[i]); 210 212 fileLinkBuilder.AppendFormat(<img src=\"{0}\" title=\"上传时间:{1},右键点击保存\" alt=\"{2}\" width=\"200px\"><br/>" + values[i],1)">215 217 219 fileLinkBuilder.AppendFormat(<a href=\"{0}\" target=\"_blank\" title=\"右键单击保存\">{1}</a>220 221 222 223 fileLinkBuilder.AppendFormat(<a href=\"{0}\" target=\"_blank\" title=\"右键单击保存\">{1}</a><br/>227 228 229 230 fileLinkBuilder.Append(values[i]+231 233 fileLinkBuilder.ToString(); 234 236 237 238 239 判断指定文件名的文件是否存在,true 表示存在,false 表示不存在。 240 241 <param name="fileName">要判断是否存在的文件名。242 返回布尔类型的值,true 表示文件存在,false 表示文件不存在。243 fileName) 244 245 Thread.Sleep(300246 247 string.IsNullOrEmpty(fileName) && !.IsNullOrWhiteSpace(fileName)) 249 if (File.Exists(HttpContext.Current.Server.MapPath(") + fileName)) 250 251 result = 252 254 255 256 257 258 259 }
第十一步:后端类库代码,文件名:WebSocketUploadFilesHandler.cs
基于 HttpHandler 实现的文件上传的功能。 WebSocketUploadFilesHandler 17 18 初始化类型的新实例。 19 20 public WebSocketUploadFilesHandler() { } 21 22 处理从客户端上传的文件。 返回异步操作的实例对象 Task。 27 Task ProcessFile(AspNetWebSocketContext context) 28 29 ArraySegment< everyTimeBufferSize; 30 31 32 33 1、获取当前的 WebSocket 对象。 34 WebSocket webSocket = 35 string fileName = 36 byte[] bufferAllSize = 1024 * 256 * 2];缓存文件总的大小,用于暂时缓存 int loaded = 0; 当前缓存的位置。 2、监听来至客户端的 WebSocket 请求 40 while ( 42 此处的值是控制读取客户端数据的长度,如果客户端发送的数据长度超过当前缓存长度,则读取多次。 43 everyTimeBufferSize = 44 45 接受客户端发送来的消息。 46 result = webSocket.ReceiveAsync(everyTimeBufferSize,1)"> 47 if (webSocket.State == 48 49 判断发送的数据是否已经结束。 50 int currentLength = Math.Min(everyTimeBufferSize.Array.Length,1)"> 51 52 try 53 54 判断客户端发送的消息的类型 55 57 message = Encoding.UTF8.GetString(everyTimeBufferSize.Array,currentLength); 58 bool isValid = UploadFileExtensionValidator.ValidateFiles(message); 59 if (!isValid && string.Compare(message,1)">[file:{(:finished:)}200] 61 63 SaveFile(fileName,bufferAllSize,loaded); 66 loaded = 68 69 70 fileName = 72 73 WebSocketMessageType.Binary) 74 75 var temp = loaded + currentLength; 76 if (temp > bufferAllSize.Length) 78 79 添加到缓存区 80 Array.copy(everyTimeBufferSize.Array,1)"> 81 loaded = 83 85 添加到缓冲区 86 Array.copy(everyTimeBufferSize.Array,loaded,1)"> 87 loaded = temp; 88 89 catch (Exception) 93 throw 96 99 100 将文件以追加的形式保存在物理磁盘上。 要保存的文件的名称。<param name="buffer">每次要保存的二进制文件数据。104 <param name="loaded">要追加文件的数据长度。105 void SaveFile(string fileName,1)">byte[] buffer,1)"> length) 106 107 string.IsNullOrEmpty(fileName) || 111 if (buffer == null || buffer.Length <= 113 115 if (length < 117 120 string currentDirectory = HttpContext.Current.Server.MapPath(string filePathFullName = currentDirectory +122 124 Directory.Exists(currentDirectory)) 126 Directory.CreateDirectory(currentDirectory); 127 128 using (FileStream fileStream = FileStream(filePathFullName,FileMode.Append,FileAccess.Write)) 129 130 fileStream.Write(buffer,length); 133 (Exception ex) 135 可以写入日志 136 138 139 140 }
好了,全部代码都贴出去了。希望对大家有帮助。类库里面的类型还可以继续升级和优化,有时间了我写第二个版本,今天就到这里了,祝福大家元旦快乐,也祝自己和家人元旦快乐。
新年新气象,也希望自己的2020年有一个优秀的成绩。