之前一直在研究浏览器调用本地程序,但发现似乎只有IE的OCX才能做到,谷歌浏览器虽然说有插件,但实现IE下OCX的功能却不太可能,忽然有一天,有个同事让我看一下菜鸟的打印组件,豁然开朗。
菜鸟的打印组件竟然是在谷歌浏览器下实现了类似IE下的OCX技术,直接可以调用打印机,但分析发现,浏览器扩展根本没有安装新东西,说明菜鸟的打印组件根本不是通过谷歌浏览器的插件机制实现的。
无奈只能分析打印按钮的HMTL代码
没有行内脚本,猜测应该是根据类选择器printBtn J_printBtn 处理的点击事件,于是保存了一份网页,在下载中的中脚本中查看可能的点击事件,竟然在newWaybillPrint.js文件中找到了可能的代码
$("body").undelegate(".J_printBtn", "click").delegate(".J_printBtn", "click", function (e) {
if (!J.printBtnFlag)
return !1;
var t = $(e.currentTarget),
a = t.attr("data-id");
"undefined" != typeof socket ? J.socketReadyState = socket.readyState : x._doConnect(),
0 == J.socketReadyState ? (J.printBtnFlag = !0, m.Alert("webSocket\u8fde\u63a5\u6b63\u5728\u5efa\u7acb", "info")) : 2 == J.socketReadyState ? (J.printBtnFlag = !0, m.Alert("webSocket\u8fde\u63a5\u6b63\u5728\u8fdb\u884c\u5173\u95ed", "info")) : 3 == J.socketReadyState ? (J.printBtnFlag = !0, null == J.notInitOrOpenDialog && x._renderNotInitPrintDialog(), J.notInitOrOpenDialog.show()) : (J.printBtnFlag = !1, x._printViewDialog("single"), J.previewDialog.root.attr("data-curId", a), J.previewDialog.show())
}),
在浏览器中调试,明显是压缩过的代码,但点击红色部分,可以格式化,并添加断点,调试,果然是。
发现有一个 "undefined" != typeof socket ? J.socketReadyState = socket.readyState : x._doConnect(),
然后在分析socket,找到了
_doConnect: function () {
var e = window.location,
t = "",
a = "";
if ("https:" === e.protocol ? (t = "wss:", a = "13529") : (t = "ws:", a = "13528"), window.WebSocket)
window.socket = new WebSocket(t + "//localhost:" + a), socket.onopen = function (e) {
J.socketReadyState = e.target.readyState,
x._doGetPrinters("123")
},
竟然是通过WebSocket和本地的打印程序通讯,然后就可以调用本地资源了。感觉真的是太巧妙了。把打印程序作为WebSocket的服务端,这样就实现了通过浏览器调用本地资源的功能,实现了B/S,C/S巧妙结合的使用,充分合理利用了B/S,C/S的好处。
客户端浏览器和打印程序之间通过WebSocket进行交互
浏览器和菜鸟服务器是正常的BS程序
打印程序和和菜鸟服务器是正常的CS程序
简单的网页通讯代码
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<style type="text/css">body *
{
font-family:微软雅黑;font-size:14px
}
</style>
<script type="text/javascript">
var websocket = null;
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8181");
}
else {
alert('当前浏览器 Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function () {
setMessageInnerHTML("WebSocket连接发生错误");
};
//连接成功建立的回调方法
websocket.onopen = function () {
// setMessageInnerHTML("WebSocket连接成功");
websocket.send("WebSocket连接成功");
}
//接收到消息的回调方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function () {
setMessageInnerHTML("WebSocket连接关闭");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
closeWebSocket();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML +="收到消息:"+ innerHTML + '<br/>';
}
function closeWebSocket() {
websocket.close();
}
function clear(){
document.getElementById('message').innerHTML="";
}
//发送消息
function send() {
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</head>
<body style="padding:0px;width:1015px">
<button onclick="closeWebSocket()">关闭WebSocket连接</button>
<button onclick="clear()">清空</button>
<hr />
<input type="text" id="text" />
<button onclick="send()">发送消息</button>
<div id="message"></div>
</body>
</html>
使用Csharp插件Fleck编写的简单的模拟打印程序
using Fleck;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsWebSocket
{
public partial class Form1 : Form
{
WebSocketServer server = null;
// IWebSocketConnection client = null;
Dictionary<Guid, IWebSocketConnection> dic = new Dictionary<Guid, IWebSocketConnection>();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
server = new WebSocketServer("ws://127.0.0.1:8181");
server.RestartAfterListenError = true;
server.Start(socket =>
{
socket.OnOpen = () => OnOpen(socket);
socket.OnClose = () => OnClose(socket);
socket.OnMessage = m => OnMessage(socket, m);
});
}
private void OnMessage(IWebSocketConnection socket, string m)
{
MessageBox.Show("收到客户端消息:"+m);
// throw new NotImplementedException();
}
private void OnClose(IWebSocketConnection socket)
{
Guid id = socket.ConnectionInfo.Id;
if(dic.ContainsKey(id))
{
IWebSocketConnection sond= dic[id];
sond.Close();
dic.Remove(id);
}
MessageBox.Show("OnClose");
}
private void OnOpen(IWebSocketConnection socket)
{
Guid id = socket.ConnectionInfo.Id;
if (dic.ContainsKey(id))
{
IWebSocketConnection sond = dic[id];
sond.Close();
dic.Remove(id);
}
else
{
dic[id] = socket;
}
}
private void button1_Click(object sender, EventArgs e)
{
string text= textBox1.Text;
foreach(var i in dic)
{
IWebSocketConnection sond = i.Value;
sond.Send(text);
}
}
}
}
Writing a WebSocket server in C#