在網頁上通過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