在网页上通过JavaScript调用本地程序,兼容IE8/9/10/11、Opera、Chrome、Safari、Firefox等所有浏览器,在做Web开发时经常会遇到需要调用本地的一些exe或者dll程序,比如身份证读卡器,打印机等设备。
传统方式通过Activex控件,但这种方式只针对IE浏览器,并且在IE11之后Microsoft Edge浏览器中,微软也不在支持Activex控件了。因此想到了利用Websocket以及Http长链接方式来实现网页对本地资源程序的调用。
通过c#创建一个winform的客户端程序开启Websocket以及Http请求监听程序,在js中判断浏览器支持Websocket,则通过Websocket与本地程序通信,如果浏览器不支持Websocket(比如IE8/9)则自动采用Http长链接实现通信。
支持功能:心跳检测,断开重连,客户端版本检测,在一个页面可同时给客户端发送多个请求任务。每一次SocketCall调用都会生成一个唯一的任务Id,在断开重连等情况下能保证任务不丢失。
js调用示例
SocketCall(path, sendMsg, function (data) { //客户端返回回调方法 console.log("返回结果:" + JSON.stringify(data)); }, function (msg, isHeart) { //调用消息的输出,比如心跳检测,错误消息 console.log(msg); });
js代码-自动识别通信方式
/* * 本地资源调用封装 * url: 调用地址 不需要主机和端口,例如:/yb * data: 输入参数 * callback: 成功回调函数,函数签名为 function(data), data参数为服务端返回对象{"Code":1,"Msg":"","Data":null} * output: 日志输出函数,函数签名为 function(msg,isHeart), msg输出字符串 isHeart是否心跳,重试消息 */ function SocketCall(url, data, callback, output) { //输入参数json序列号 if (typeof data == "object" && typeof (data) != null) { data = JSON.stringify(data); } //发送请求 if ('WebSocket' in window) { return new WSocket(url, data, callback, output); } else { return new HttpSocket(url, data, callback, output); } }
js代码-websocket调用封装

1 /* 2 * webscoket调用封装 3 * url: 调用地址 4 * data: 输入参数 5 * callback: 成功回调函数,函数签名为 function(data), data参数为服务端返回对象{"Code":1,"Msg":"","Data":null} 6 * output: 日志输出函数,函数签名为 function(msg,isHeart), msg输出字符串 isHeart是否心跳,重试消息 7 */ 8 var WSocket = function (url, data, callback, output) { 9 this.url = url; 10 this.data = data; 11 this.callback = callback; 12 this.output = output; 13 this.outMsg = function (msg, isHeart) { 14 if (!isHeart) isHeart = false; 15 if (this.output) { 16 this.output(msg, isHeart); 17 } 18 if (!isHeart) { 19 console.log(msg); 20 } 21 }; 22 if (this.data == "HeartBeat" || this.data == "TaskIsExist") { 23 this.outMsg("发送内容不能是关键字:HeartBeat或TaskIsExist"); 24 return; 25 } 26 //初始化 27 this.taskId = GetTaskId();//任务Id 28 this.url = "ws://" + SocketHost + GetUrlQueryString(this.url, this.taskId);//连接地址 29 this.IsComplate = false;//任务是否已完成 30 this.lockReconnect = false;//是否锁定重连 31 this.rcTimeoutObj = null;//重连定时器 32 this.heartCheck = new WSHeartCheck(this);//心跳检测对象 33 this.webSocket = null;//webSocket链接对象 34 this.taskIsSend = false;//任务请求是否已发送 35 this.IsUpgrade = false;//当前是否版本升级 36 37 this.Open = function () {//连接 38 this.webSocket = new WebSocket(this.url); 39 this.webSocket.SrcWSocketSelf = this; 40 if (!window.WSocketObjs) { 41 window.WSocketObjs = new Array(); 42 } 43 window.WSocketObjs.push(this); 44 //连接成功建立的回调方法 45 this.webSocket.onopen = function (event) { 46 this.SrcWSocketSelf.IsUpgrade = false; 47 RemoveAlertClientRunMsg(); 48 //心跳检测 49 this.SrcWSocketSelf.heartCheck.reset().start(); 50 //发送任务数据 51 if (!this.SrcWSocketSelf.taskIsSend) { 52 this.SrcWSocketSelf.webSocket.send(this.SrcWSocketSelf.data); 53 this.SrcWSocketSelf.taskIsSend = true; 54 this.SrcWSocketSelf.outMsg("ws发送任务成功,等待返回..."); 55 } else { 56 //判断任务是否存在 57 this.SrcWSocketSelf.webSocket.send("TaskIsExist"); 58 } 59 }; 60 //接收到消息的回调方法 61 this.webSocket.onmessage = function (event) { 62 var resModel = JSON.parse(event.data); 63 //处理消息 64 if (resModel.Code == 8) { 65 this.SrcWSocketSelf.outMsg("ws接收心跳响应:" + event.data, true); 66 this.SrcWSocketSelf.heartCheck.reset().start();//重置并开始下一个心跳检测 67 } else if (resModel.Code == 2) {//继续等待 68 this.SrcWSocketSelf.outMsg("重连成功,继续等待返回..."); 69 } else if (resModel.Code == 3) {//版本升级等待 70 this.SrcWSocketSelf.outMsg("客户端版本升级中,请等待..."); 71 this.SrcWSocketSelf.taskIsSend = false; 72 this.SrcWSocketSelf.IsUpgrade = true; 73 this.SrcWSocketSelf.Reconnect();//启动重连 74 } else if (this.SrcWSocketSelf.callback) { 75 this.SrcWSocketSelf.IsComplate = true; 76 this.SrcWSocketSelf.Close(); 77 this.SrcWSocketSelf.callback(resModel); 78 } else { 79 this.SrcWSocketSelf.IsComplate = true; 80 this.SrcWSocketSelf.Close(); 81 this.SrcWSocketSelf.outMsg("ws接收数据:" + event.data); 82 } 83 }; 84 //连接发生错误的回调方法 85 this.webSocket.onerror = function (event) { 86 //启动应用 87 if (this.SrcWSocketSelf.IsUpgrade != true) { 88 StartLocalExe(); 89 } 90 //重新链接 91 this.SrcWSocketSelf.Reconnect(); 92 }; 93 //连接关闭的回调方法 94 this.webSocket.onclose = function (event) { 95 this.SrcWSocketSelf.outMsg("ws连接已关闭", true); 96 this.SrcWSocketSelf.heartCheck.reset();//心跳检测 97 this.SrcWSocketSelf.Reconnect(); 98 }; 99 }; 100 this.Open(); 101 102 //以下定义公共方法 103 this.Reconnect = function () {//重连 104 if (this.lockReconnect) { 105 return; 106 }; 107 if (this.IsComplate) { 108 return; 109 } 110 var self = this; 111 this.lockReconnect = true; 112 this.rcTimeoutObj && clearTimeout(this.rcTimeoutObj); 113 this.rcTimeoutObj = setTimeout(function () { 114 self.lockReconnect = false; 115 self.outMsg('ws开始重试...', true); 116 self.Open(); 117 }, 1000); 118 }; 119 this.SendData = function (data) {//发送数据 120 if (this.webSocket == null) { 121 return "链接未初始化!"; 122 } 123 if (this.webSocket.readyState != 1) { 124 return "链接不是正常状态"; 125 } 126 this.webSocket.send(data); 127 return ""; 128 }; 129 this.Close = function (data) {//关闭连接 130 if (this.webSocket != null) { 131 this.heartCheck.reset(); 132 this.webSocket.close(); 133 } 134 }; 135 }; 136 137 //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接或者HttpSocket。 138 window.onbeforeunload = function () { 139 if (window.WSocketObjs) { 140 for (var i in window.WSocketObjs) { 141 window.WSocketObjs[i].Close(); 142 } 143 } 144 }; 145 146 //websocket心跳检测 147 var WSHeartCheck = function (wSocket) { 148 this.timeout = 8000;//心跳间隔 8秒 149 this.timeoutObj = null; 150 this.serverTimeoutObj = null; 151 this.wSocket = wSocket; 152 153 this.reset = function () {//重置 154 clearTimeout(this.timeoutObj); 155 clearTimeout(this.serverTimeoutObj); 156 return this; 157 }; 158 159 this.start = function () {//开始心跳 160 var self = this; 161 this.timeoutObj && clearTimeout(this.timeoutObj); 162 this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj); 163 this.timeoutObj = setTimeout(function () { 164 //这里发送一个心跳,后端收到后,返回一个心跳消息,onmessage拿到返回的心跳就说明连接正常 165 self.wSocket.SendData("HeartBeat"); 166 self.serverTimeoutObj = setTimeout(function () { // 如果超过5秒还没重置,说明链接断开了 167 self.wSocket.Close();//onclose会执行reconnect 168 }, 5000); 169 }, this.timeout); 170 }; 171 }
js代码-http长链接调用封装

1 /* 2 * http调用封装 3 * url: 调用地址 4 * data: 输入参数 5 * callback: 成功回调函数,函数签名为 function(data), data参数为服务端返回对象{"Code":1,"Msg":"","Data":null} 6 * output: 日志输出函数,函数签名为 function(msg,isHeart), msg输出字符串 isHeart是否心跳,重试消息 7 */ 8 var HttpSocket = function (url, data, callback, output) { 9 this.url = url; 10 this.data = data; 11 this.callback = callback; 12 this.output = output; 13 this.taskId = GetTaskId();//任务Id 14 this.url = "http://" + SocketHost + GetUrlQueryString(this.url, this.taskId); 15 this.taskIsSend = false;//任务请求是否已发送 16 this.IsComplate = false;//任务是否已完成 17 this.lockReconnect = false;//是否锁定重连 18 this.rcTimeoutObj = null;//重连定时器 19 this.IsUpgrade = false;//当前是否版本升级 20 this.outMsg = function (msg, isHeart) { 21 if (!isHeart) isHeart = false; 22 if (this.output) { 23 this.output(msg, isHeart); 24 } 25 if (!isHeart) { 26 console.log(msg); 27 } 28 }; 29 //发送任务请求 30 this.Open = function () { 31 if (this.taskIsSend) { 32 return; 33 } 34 WAjax.post(this.url, this.data, function (resData, state) { 35 //请求成功 36 RemoveAlertClientRunMsg(); 37 state.outMsg("htp发送任务成功,等待返回..."); 38 state.taskIsSend = true; 39 state.IsUpgrade = false; 40 if (resData.Code == 2) {//开始获取任务结果 41 state.IsComplate = false; 42 state.GetTaskReslut(); 43 } else if (resData.Code == 3) {//版本升级等待 44 state.outMsg("客户端版本升级中,请等待..."); 45 state.taskIsSend = false; 46 state.IsUpgrade = true; 47 //重新发送 48 state.Reconnect(); 49 } else { 50 //返回结果 51 state.IsComplate = true; 52 if (state.callback) { 53 state.callback(resData); 54 } 55 } 56 }, function (err, state) {//ajax调用失败 57 state.outMsg("htp发送任务失败:" + err, true); 58 //重新发送 59 state.Reconnect(); 60 }, this); 61 }; 62 this.Open(); 63 64 //重新发送 65 this.Reconnect = function () { 66 if (this.lockReconnect) { 67 return; 68 }; 69 if (this.IsComplate) { 70 return; 71 } 72 var self = this; 73 this.lockReconnect = true; 74 this.rcTimeoutObj && clearTimeout(this.rcTimeoutObj); 75 this.rcTimeoutObj = setTimeout(function () { 76 self.lockReconnect = false; 77 self.outMsg('htp开始重试...', true); 78 self.Open(); 79 }, 1000); 80 }; 81 82 //获取任务结果 83 this.GetTaskReslut = function () { 84 //地址 85 var gUrl = "http://" + SocketHost + "/_getTaskReslut" + GetUrlQueryString("", this.taskId); 86 //get请求 87 WAjax.get(gUrl, function (resData, state) { 88 state.outMsg("htp获取任务状态:" + JSON.stringify(resData), true); 89 if (resData.Code == 2) {//继续获取 90 state.IsComplate = false; 91 state.GetTaskReslut(); 92 } else {//返回结果 93 state.IsComplate = true; 94 if (state.callback) { 95 state.callback(resData); 96 } 97 } 98 }, function (err, state) { 99 state.outMsg("htp获取任务状态失败:" + err, true);//输出错误 100 state.GetTaskReslut();//继续获取 101 }, this); 102 } 103 }

1 //Ajax调用 2 var WAjax = { 3 get: function (url, onSuccess, onError, state) { 4 var xhr = new XMLHttpRequest(); 5 if (window.XMLHttpRequest) { 6 xhr = new XMLHttpRequest(); 7 } else { 8 xhr = new ActiveXObject("Microsoft.XMLHTTP"); 9 } 10 xhr.open('GET', url, true); 11 xhr.onreadystatechange = function () { 12 if (xhr.readyState == 4) { 13 if (xhr.status == 200) { 14 //请求成功 15 var model = JSON.parse(xhr.responseText); 16 if (onSuccess) { 17 onSuccess(model, state); 18 } 19 } else { 20 if (onError) { 21 onError("http状态码" + xhr.status, state); 22 } 23 } 24 } 25 } 26 xhr.onerror = function (evt) { 27 //启动应用 28 if (state.IsUpgrade != true) { 29 StartLocalExe(); 30 } 31 //此处不执行错误回调,因为在onreadystatechange里面也会执行 32 } 33 xhr.send(); 34 }, 35 36 post: function (url, data, onSuccess, onError, state) { 37 var xhr = new XMLHttpRequest(); 38 if (window.XMLHttpRequest) { 39 xhr = new XMLHttpRequest(); 40 } else { 41 xhr = new ActiveXObject("Microsoft.XMLHTTP"); 42 } 43 xhr.open('POST', url, true); 44 xhr.onreadystatechange = function () { 45 if (xhr.readyState == 4) { 46 if (xhr.status == 200) { 47 //请求成功 48 var model = JSON.parse(xhr.responseText); 49 if (onSuccess) { 50 onSuccess(model, state); 51 } 52 } else { 53 if (onError) { 54 onError("http状态码" + xhr.status, state); 55 } 56 } 57 } 58 } 59 xhr.onerror = function (evt) { 60 //启动应用 61 if (state.IsUpgrade != true) { 62 StartLocalExe(); 63 } 64 //此处不执行错误回调,因为在onreadystatechange里面也会执行 65 } 66 xhr.send(data); 67 } 68 }

1 //获取任务Id 2 function GetTaskId() { 3 var now = new Date(); 4 var taskId = (now.getMonth() + 1).toString(); 5 taskId += now.getDate(); 6 taskId += now.getHours(); 7 taskId += now.getMinutes(); 8 taskId += now.getSeconds(); 9 taskId += now.getMilliseconds(); 10 taskId += Math.random().toString().substr(3, 5);//3位随机数 11 return taskId; 12 } 13 14 //启动本地exe 15 function StartLocalExe() { 16 var ifrm = document.getElementById("ifrm_StartLocalExe"); 17 if (!ifrm) { 18 ifrm = document.createElement('iframe'); 19 ifrm.id = "ifrm_StartLocalExe"; 20 ifrm.src = "XhyClinicClient://"; 21 ifrm.width = 0; 22 ifrm.height = 0; 23 document.body.appendChild(ifrm); 24 } 25 document.getElementById('ifrm_StartLocalExe').contentWindow.location.reload(); 26 27 //var linkTmp = document.createElement('a'); 28 //linkTmp.href = 'XhyClinicClient://'; 29 //linkTmp.click(); 30 }; 31 32 //获取地址请求参数 33 function GetUrlQueryString(url, taskId) { 34 //参数中添加time 以兼容在Ie9以下浏览器中 相同http地址,浏览器的缓存不处理问题 35 if (url.indexOf("?") > 0) { 36 return url + "&taskId=" + taskId + "&clientVer=" + ClientVer + "&time" + new Date().getTime(); 37 } else { 38 return url + "?taskId=" + taskId + "&clientVer=" + ClientVer + "&time" + new Date().getTime(); 39 } 40 }
客户端程序
客户端采用c#,winform实现,采用.netframework4.0版本,支持xp,win7,win10等操作系统。websocket实现采用的是开源项目:websocket-sharp.dll

1 /// <summary> 2 /// WebSocket主类 3 /// </summary> 4 public class WSMain 5 { 6 #region 字段 7 8 /// <summary> 9 /// 检测客户端连接 处理线程 10 /// </summary> 11 private BackgroundWorker _doWork_checkClient; 12 /// <summary> 13 /// 检测线程 信号灯 14 /// </summary> 15 private AutoResetEvent _doWorkARE = new AutoResetEvent(true); 16 17 private int _heartBeatSecond = 8000; 18 /// <summary> 19 /// 获取或设置 心跳检测时间间隔(单位毫秒) 默认值(8000毫秒) 20 /// 此值必须与客户端设置相同值 最小值1000毫秒 21 /// </summary> 22 public int HeartBeatSecond 23 { 24 get { return _heartBeatSecond; } 25 set 26 { 27 if (value < 1000) 28 { 29 throw new Exception("心跳检测时间间隔必须大于等于1秒"); 30 } 31 _heartBeatSecond = value; 32 } 33 } 34 /// <summary> 35 /// 服务器是否正在运行 36 /// </summary> 37 public bool IsRunning { get; private set; } 38 39 /// <summary> 40 /// WebSocketServer 41 /// </summary> 42 private HttpServer _httpServer; 43 /// <summary> 44 /// WebSocketServer对象 45 /// </summary> 46 public HttpServer HttpServer 47 { 48 get 49 { 50 return _httpServer; 51 } 52 } 53 54 #endregion 55 56 /// <summary> 57 /// 启动WebSocket 58 /// </summary> 59 public void Start() 60 { 61 _httpServer = new HttpServer(CommonInfo.WsPort); 62 _httpServer.OnGet += _httpServer_Handler; 63 _httpServer.OnPost += _httpServer_Handler; 64 foreach (KeyValuePair<string, HandlerModel> item in HandlerManager.Handlers) 65 { 66 _httpServer.AddWebSocketService<MyWebSocketBehavior>(string.Format("/{0}", item.Key)); 67 } 68 _httpServer.Start(); 69 70 //启动检测 71 IsRunning = true; 72 _doWork_checkClient = new BackgroundWorker(); 73 _doWork_checkClient.DoWork += DoWorkCheckMethod; 74 _doWork_checkClient.RunWorkerAsync(); 75 } 76 77 #region Http请求处理 78 79 /// <summary> 80 /// Http请求处理 81 /// </summary> 82 /// <param name="sender"></param> 83 /// <param name="e"></param> 84 private void _httpServer_Handler(object sender, HttpRequestEventArgs e) 85 { 86 try 87 { 88 if (string.IsNullOrWhiteSpace(e.Request.Url.AbsolutePath)) 89 { 90 HttpResponse(e, new WSResModel(ResCode.Err, "请求路径不存在!")); 91 return; 92 } 93 string path = e.Request.Url.AbsolutePath.TrimStart('/'); 94 string taskId = e.Request.QueryString["taskId"]; 95 string clientVer = e.Request.QueryString["clientVer"];//客户端版本号 96 if (path == "TestConnect") 97 { 98 HttpResponse(e, new WSResModel(ResCode.OK)); 99 return; 100 } 101 if (!string.IsNullOrWhiteSpace(clientVer) && CommonInfo.Version != clientVer) 102 { 103 HttpResponse(e, new WSResModel(ResCode.ClientAutoUpgrade, string.Format("请将客户端版本升级至{0}", clientVer))); 104 Program.ExitAndStartAutoUpgrade(); 105 return; 106 } 107 if (string.IsNullOrWhiteSpace(taskId)) 108 { 109 HttpResponse(e, new WSResModel(ResCode.Err, "请求参数TaskId必须!")); 110 return; 111 } 112 if (path == "_getTaskReslut") 113 { 114 Stopwatch sw = new Stopwatch(); 115 sw.Start(); 116 GetTaskWaitReslut(taskId, e, sw); 117 } 118 else 119 { 120 BaseHandler handler = HandlerManager.CreateHandler(path); 121 if (handler == null) 122 { 123 HttpResponse(e, new WSResModel(ResCode.Err, string.Format("请求路径{0}未找到对应Handler", path))); 124 return; 125 } 126 //参数解析 127 string msg = string.Empty; 128 if (e.Request.ContentLength64 > 0) 129 { 130 using (System.IO.StreamReader stream = new System.IO.StreamReader(e.Request.InputStream, Encoding.UTF8)) 131 { 132 msg = stream.ReadToEnd(); 133 } 134 } 135 //断开并返回客户端 136 HttpResponse(e, new WSResModel(ResCode.Wait)); 137 //线程继续处理消息 138 handler.Path = path; 139 handler.TaskId = taskId; 140 handler.ReqIsWebSocket = false; 141 handler.InPara = msg; 142 if (HandlerTaskManager.AddTask(handler)) 143 { 144 handler.HandlerTask(msg); 145 } 146 } 147 } 148 catch (Exception ex) 149 { 150 HttpResponse(e, new WSResModel(ResCode.Err, string.Format("服务器异常:{0}", ex.Message))); 151 } 152 } 153 154 /// <summary> 155 /// 等待并获取任务结果 156 /// </summary> 157 /// <param name="taskId"></param> 158 /// <param name="e"></param> 159 private void GetTaskWaitReslut(string taskId, HttpRequestEventArgs e, Stopwatch sw) 160 { 161 try 162 { 163 if (sw.ElapsedMilliseconds > HeartBeatSecond) 164 { 165 //超过心跳 则返回给客户端,以便下次继续请求 166 HttpResponse(e, new WSResModel(ResCode.Wait)); 167 return; 168 } 169 //获取任务状态 170 WSResModel resModel = HandlerTaskManager.GetTaskReslut(taskId); 171 if (resModel != null) 172 { 173 HttpResponse(e, resModel); 174 } 175 else 176 { 177 Thread.Sleep(50); 178 GetTaskWaitReslut(taskId, e, sw); 179 } 180 } 181 catch (Exception ex) 182 { 183 HttpResponse(e, new WSResModel(ResCode.Err, string.Format("服务器内部异常:{0}", ex.Message))); 184 } 185 } 186 187 /// <summary> 188 /// Http响应客户端 189 /// </summary> 190 /// <param name="e"></param> 191 /// <param name="resMsg"></param> 192 private void HttpResponse(HttpRequestEventArgs e, WSResModel resModel) 193 { 194 try 195 { 196 string json = CommonLibrary.MyJson.JsonSerializer(resModel); 197 byte[] content = System.Text.Encoding.UTF8.GetBytes(json); 198 199 e.Response.StatusCode = (int)HttpStatusCode.OK; 200 e.Response.AddHeader("Access-Control-Allow-Origin", "*");//允许跨域访问 201 e.Response.ContentEncoding = Encoding.UTF8; 202 e.Response.ContentLength64 = content.Length; 203 e.Response.OutputStream.Write(content, 0, content.Length); 204 e.Response.OutputStream.Close(); 205 e.Response.Close(); 206 } 207 catch (Exception ex) 208 { 209 CommonInfo.Output("Http响应客户端异常:{0}", ex.Message); 210 } 211 } 212 213 #endregion 214 215 /// <summary> 216 /// 后台线程方法 217 /// 主要处理客户端连接 218 /// </summary> 219 /// <param name="sender"></param> 220 /// <param name="e"></param> 221 private void DoWorkCheckMethod(object sender, DoWorkEventArgs e) 222 { 223 int timeout = HeartBeatSecond * 3;//连续3次检测无心跳 则表示客户端已死掉了 224 while (IsRunning) 225 { 226 try 227 { 228 _doWorkARE.Reset(); 229 _doWorkARE.WaitOne(HeartBeatSecond); 230 231 //定时清理任务 232 HandlerTaskManager.ClearTask(); 233 234 foreach (WebSocketServiceHost host in _httpServer.WebSocketServices.Hosts) 235 { 236 List<IWebSocketSession> tempList = host.Sessions.Sessions.ToList(); 237 foreach (var item in tempList) 238 { 239 MyWebSocketBehavior handler = item as MyWebSocketBehavior; 240 if (handler != null) 241 { 242 //检测连接 243 if ((DateTime.Now - handler.LastRecvTime).TotalSeconds > timeout) 244 { 245 //断开客户端连接 246 CloseClientSocket(handler); 247 } 248 } 249 } 250 } 251 } 252 catch (Exception ex) 253 { 254 CommonInfo.Output("检测连接异常:{0}", ex.Message); 255 } 256 } 257 } 258 259 /// <summary> 260 /// 关闭客户端连接 261 /// </summary> 262 /// <param name="session"></param> 263 public void CloseClientSocket(MyWebSocketBehavior handler) 264 { 265 try 266 { 267 handler.Context.WebSocket.Close(); 268 } 269 catch (Exception ex) 270 { 271 CommonInfo.Output("断开客户端异常:{0}", ex.Message); 272 } 273 } 274 275 /// <summary> 276 /// 退出WebSocket 277 /// </summary> 278 public void Stop() 279 { 280 //关闭WebScoket 281 IsRunning = false; 282 _doWorkARE.Set(); 283 HandlerTaskManager.TaskQueue.Clear(); 284 _httpServer.Stop(WebSocketSharp.CloseStatusCode.Abnormal, "服务退出"); 285 } 286 287 #region 单例对象 288 289 private static WSMain _instance; 290 /// <summary> 291 /// 单例对象 292 /// </summary> 293 public static WSMain Instance 294 { 295 get 296 { 297 if (_instance == null) 298 { 299 _instance = new WSMain(); 300 } 301 return _instance; 302 } 303 } 304 #endregion 305 }

1 /// <summary> 2 /// WebSocket消息处理类 3 /// </summary> 4 public class MyWebSocketBehavior : WebSocketBehavior 5 { 6 #region 字段 7 8 /// <summary> 9 /// 最后接受数据时间 10 /// </summary> 11 public DateTime LastRecvTime = DateTime.Now; 12 13 /// <summary> 14 /// 任务Id 15 /// </summary> 16 public string TaskId 17 { 18 get 19 { 20 return this.Context.QueryString["taskId"]; 21 } 22 } 23 24 /// <summary> 25 /// 客户端版本号 26 /// </summary> 27 public string ClientVer 28 { 29 get 30 { 31 return this.Context.QueryString["clientVer"]; 32 } 33 } 34 35 /// <summary> 36 /// 请求路径 37 /// </summary> 38 public string PathName 39 { 40 get 41 { 42 return this.Context.RequestUri.AbsolutePath.TrimStart('/'); 43 } 44 } 45 46 /// <summary> 47 /// 连接数发生变化事件 48 /// </summary> 49 public static event Action<MyWebSocketBehavior> ConnectCountChange; 50 51 #endregion 52 53 /// <summary> 54 /// 新连接 55 /// </summary> 56 protected override void OnOpen() 57 { 58 if (ConnectCountChange != null) 59 { 60 ConnectCountChange(this); 61 } 62 if (!string.IsNullOrWhiteSpace(this.ClientVer) && CommonInfo.Version != this.ClientVer) 63 { 64 SendAndClose(new WSResModel(ResCode.ClientAutoUpgrade, string.Format("请将客户端版本升级至{0}", this.ClientVer))); 65 Program.ExitAndStartAutoUpgrade(); 66 } 67 //CommonInfo.Output("新连接{0}", this.TaskId); 68 } 69 70 /// <summary> 71 /// 新消息 72 /// </summary> 73 /// <param name="e"></param> 74 protected override void OnMessage(WebSocketSharp.MessageEventArgs e) 75 { 76 try 77 { 78 LastRecvTime = DateTime.Now; 79 if (e.IsText) 80 { 81 switch (e.Data) 82 { 83 case "HeartBeat": 84 this.Send(CommonLibrary.MyJson.JsonSerializer(new WSResModel(ResCode.HeartBeatRes))); 85 return; 86 case "TaskIsExist": 87 if (HandlerTaskManager.ContainsTask(this.TaskId)) 88 { 89 this.Send(CommonLibrary.MyJson.JsonSerializer(new WSResModel(ResCode.Wait))); 90 } 91 else 92 { 93 SendAndClose(new WSResModel(ResCode.Err, "任务不存在!")); 94 } 95 return; 96 } 97 } 98 BaseHandler handler = HandlerManager.CreateHandler(PathName); 99 if (handler == null) 100 { 101 SendAndClose(new WSResModel(ResCode.Err, string.Format("请求路径{0}未找到对应Handler", PathName))); 102 return; 103 } 104 handler.Path = this.PathName; 105 handler.TaskId = this.TaskId; 106 handler.ReqIsWebSocket = true; 107 handler.InPara = e.Data; 108 //添加任务并执行 109 if (HandlerTaskManager.AddTask(handler)) 110 { 111 ThreadPool.QueueUserWorkItem((state) => 112 { 113 BaseHandler bh = state as BaseHandler; 114 try 115 { 116 bh.HandlerTask(bh.InPara); 117 } 118 catch (Exception ex) 119 { 120 bh.ReturnToClient(new WSResModel(ResCode.Err, ex.Message)); 121 } 122 }, handler); 123 } 124 } 125 catch (Exception ex) 126 { 127 CommonInfo.Output("WS处理消息异常:{0}", ex.Message); 128 SendAndClose(new WSResModel(ResCode.Err, string.Format("服务器WS处理消息异常", ex.Message))); 129 } 130 } 131 132 /// <summary> 133 /// 错误 134 /// </summary> 135 /// <param name="e"></param> 136 protected override void OnError(WebSocketSharp.ErrorEventArgs e) 137 { 138 if (e.Exception != null) 139 { 140 CommonInfo.Output("连接{0}错误:{1}", this.TaskId, e.Exception.GetBaseException().Message); 141 } 142 else 143 { 144 CommonInfo.Output("连接{0}错误:{1}", this.TaskId, e.Message); 145 } 146 } 147 148 /// <summary> 149 /// 关闭 150 /// </summary> 151 /// <param name="e"></param> 152 protected override void OnClose(WebSocketSharp.CloseEventArgs e) 153 { 154 if (ConnectCountChange != null) 155 { 156 ConnectCountChange(this); 157 } 158 //CommonInfo.Output("断开连接{0}", this.TaskId); 159 } 160 161 /// <summary> 162 /// 结果返回给客户端并断开链接 163 /// </summary> 164 /// <param name="msg"></param> 165 public void SendAndClose(WSResModel resModel) 166 { 167 try 168 { 169 string json = CommonLibrary.MyJson.JsonSerializer(resModel); 170 this.Send(json); 171 try 172 { 173 this.Context.WebSocket.Close(); 174 } 175 catch (Exception ex) { } 176 } 177 catch (Exception ex) 178 { 179 CommonInfo.Output("发送消息异常:{0}", ex.Message); 180 } 181 } 182 }
在c#中,自定义消息处理类,只需自己新建一个类,然后继承至BaseHandler,并实现HandlerTask方法,比如:
/// <summary> /// 消息处理器 /// </summary> [HandlerAttribute("yb")]//对应websocket或者http请求路径,比如:ws:127.0.0.1:3964/yb 或者: http://127.0.0.1:3964/yb public class YBHandler : BaseHandler { /// <summary> /// 处理消息 /// </summary> /// <param name="msg"></param> /// <returns></returns> public override void HandlerTask(string msg) { //Thread.Sleep(20000); FrmTest frm = new FrmTest(); frm.TopMost = true; frm.ShowDialog(); //在子类中通过调用父类的ReturnToClient方法将消息json对象返回给网页 this.ReturnToClient(new WSResModel(ResCode.OK, DateTime.Now.ToString("HH:mm:ss") + "OK:" + msg)); } }
结语
源代码托管于GitHub,供大伙学习参考,项目地址:https://github.com/keguoquan/JsCallExe。感兴趣或觉得不错的望赏个star,不胜感激!
若能顺手点个赞,更加感谢!
如有疑问可以QQ咨询:343798739