在网页上通过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 }

webscoket调用封装

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 }

http调用封装

 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 }

Ajax调用

 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 }

其他相关js方法

客户端程序

客户端采用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 }

WebSocket服务主类

  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 }

WebSocket消息处理类

在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

js调用本地程序资源-兼容所有浏览器的更多相关文章

  1. js调用本地程序

    前几天,做项目时候用到js调用本地的程序,找了好多资料,一种是写入注册表,一种是写一个浏览器插件,相对来说,写一个注册表更简单一点,因为需求很紧.下面就是我的总结,希望可以对你们有所帮助,具体从哪里找 ...

  2. 网页调用本地程序(Windows下浏览器全兼容)

    用网页调用本地应用程序的思路是,先进行注册表注册自定义一个URL Protocol协议,再利用URL Protocol实现网页调用本地应用程序. 1.先写一个注册表文件,将其保存为.reg后缀的注册表 ...

  3. 使用JS读取本地文本文件(兼容各种浏览器)

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. 【Web前端】---js调用本地应用程序

    最近进入了一个项目组,向大牛们一起学习如何搞开发,可谓是边开发边学习.就在前两天,我们的项目被领导们验收了一次,顺便给我们提了点新的需求,要求我们能够使用外在设备拍照上传.君要臣死,臣不能不死.更何况 ...

  5. JS获取回车事件(兼容各浏览器)

    一.用到onkeydown获取事件动作, 二.用到键盘对应代码keyCode, 三. var event=arguments.callee.caller.arguments[0]||window.ev ...

  6. PC网页js调用本地应用程序

    最近要现实一个在PC网页中实现点击按钮调用本地应用程序的功能 其实实现原理也非常简单, 首先注册一个本地注册表文件,指向本地应用程序路径 其次在网页中用js指向这个注册表文件,就可以实现网页调用本地应 ...

  7. javascript创建css、js,onload触发callback兼容主流浏览器的实现

    http://www.fantxi.com/blog/archives/load-css-js-callback/ 由于需要写个函数,既可以加载css,又可以加载js,所以对各主流浏览器对加载js.c ...

  8. 【问题记录】— web页面调用本地程序

    起因: 最近由于项目需要在web页面中调用本地部署的exe程序:进而对该功能实现做了对应了解:以及存在的问题进行记录. 要实现该功能就不得不说浏览器自定义协议:解决办法:那么它是什么呢? 浏览器自定义 ...

  9. JS调用本地设备

    JS 允许通过 navigator.mediaDevices.getUserMedia(options) 直接调用本地的设备,比如麦克风,摄像头等.因为该操作涉及到用户隐私,所以调用的时候会弹框请求权 ...

  10. js调用winform程序(带参数)

    我们会发现,我们点击迅雷下载的时候  网页可以调用应用程序,而且连接会传入迅雷,这个是怎么做到的呢? 原理: 先注册表中添加软件的具体信息,然后通过 href 可以直接调用 1.写入注册表信息,注册, ...

随机推荐

  1. ant利用ivy从maven仓库下载项目所依赖的jar包默认的存储位置

    ant利用ivy从maven仓库下载项目所依赖的jar包默认的存储位置为: 当前登录系统的用户目录下(如"C:\Users\Administrator\.ivy2\cache"), ...

  2. WorldWind源码剖析系列:WorldWind瓦片调度策略说明

    1 基于源码的分析 首先我们来看WorldWind中摄像头变化相关的几个方法的内部逻辑. 1.1 NltTerrainAccessor. GetElevationAt 方法声明:public over ...

  3. 【Java 温故而知新系列】基础知识-01 概述

    1.什么是Java Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了 C++里难以理解的多继承. 指针等概念,因此Java语言具有功能强大和简单易 用两个特征.Java语言作为 ...

  4. OpenMMLab AI实战营 第四课笔记

    OpenMMLab AI实战营 第四课笔记 目录 OpenMMLab AI实战营 第四课笔记 目标检测与MMDetection 1.什么是目标检测 1.1 目标检测的应用 1.1.1 目标检测 in ...

  5. react之Lazy和Suspense(懒加载)

    React.lazy React.lazy 函数能让你像渲染常规组件一样处理动态引入(的组件). 什么意思呢? 其实就是懒加载. 其原理就是利用es6 import()函数.这个import不是imp ...

  6. Solution Set -「LOCAL」冲刺省选 Round XXVIII

    \(\mathscr{Summary}\)   A 题显然是图论模型嘛--但是卡得太久了,B 题 C 题都不好骗,裂开 qwq.   感觉时间安排上不尽合理,如果 B C 简单一点我这个就要挂打分了. ...

  7. 教你实现GPUImage【OpenGL渲染原理】

    一.前言 本篇主要讲解GPUImage底层是如何渲染的,GPUImage底层使用的是OPENGL,操控GPU来实现屏幕展示 由于网上OpenGL实战资料特别少,官方文档对一些方法也是解释不清楚,避免广 ...

  8. AGC008

    AGC008 B 题目大意 给出一个序列,一开始全是白色,一次操作可以染黑或染白一段长度为 \(K\) 的区间,要让最后序列中黑色格子上数的和最大,求这个最大值. 解题思路 考虑找结论. 发现我们一定 ...

  9. Dibble pg walkthrough Intermediate

    nmap 21/tcp open ftp vsftpd 3.0.3 | ftp-anon: Anonymous FTP login allowed (FTP code 230) |_Can't get ...

  10. linux命令操作android手机

    目的 通过一台linux机器操作android手机做一些常用的操作 复杂的操作都是由简单操作开始的, 可以自行发掘 环境 笔记本: thinkpad t480 操作系统: archlinux adb版 ...