標簽:
傳統(tǒng)桌面程序不能完全被web和移動端替代,但是需要改造。這里要說的是巧用webapi把以前用dll和com組件,ocx等方式做接口,做分布式開發(fā)的方式,改成restful 風格api的方式實現(xiàn)跨平臺,多客戶端(類型).并分享幾則案例. 1、智能儲物柜 項目背景:某智慧城市項目需要用到有智能鎖的儲物柜,用app掃碼控制存取,并和智慧城市后臺交互。智能鎖系統(tǒng)是工業(yè)的塔式控制器,使用modbus ascii協(xié)議控制,端口使用串口。儲物柜配備了工控電腦32寸豎屏,工控電腦控制塔式控制器(單片機),工控機上需要開發(fā)一套桌面程序,對外暴露儲物柜的功能性(存取物品),對用戶來說作為人機交互界面。話寫的有點難懂還是上圖吧: 規(guī)格有幾種,這是不是實物忘記了。總之也沒去過現(xiàn)場。 柜機人機界面 說明: 工作區(qū)是底部的1024*1080像素的區(qū)域,關鍵設計是把二維碼的內容設計成了JSON,app掃描后獲取到設備和意圖,智慧城市后臺對云主機上的中間件發(fā)起控制請求,中間件轉發(fā)給柜機程序,柜機程序和塔式控制器通信,塔式控制器控制鎖動作。 中間件程序界面 說明:中間使用winform+owin宿主webapi,對外暴露api,對柜機程序提供套接字連接。中間件是socket server端,柜機程序作為client。 還是暈了吧,沒看懂么。簡單來說柜機程序是個上位機程序,設備需要把控制鎖的需求封裝成api給外部調用。這里的解決方案是使用中間件,中間件對外暴露api外部發(fā)起控制請求,中間件對內(設備端程序)執(zhí)行控制指令。 為了實現(xiàn)"網(wǎng)頁和移動客戶端控制工控設備"這個核心需求,這也是擠破了腦袋吧.呵呵呵,總算不枉費你進來圍觀了一回...
不留下點代碼,算什么分享呢!哼!
好的,上代碼: 這就是傳說中的asp.net mvc webapi啊 winform宿主: IPEndPoint ipeSender = new IPEndPoint(IPAddress.Any, 0); EndPoint epSender = (EndPoint)ipeSender; serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 首次探測時間5 秒, 間隔偵測時間2 秒 byte[] inValue = new byte[] { 1, 0, 0, 0, 0x88, 0x13, 0, 0, 0xd0, 0x07, 0, 0 }; serverSocket.IOControl(IOControlCode.KeepAliveValues, inValue, null); IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse(SocketBindingIP), int.Parse(config.AppSettings.Settings["Middleware_PORT"].Value)); try { serverSocket.Bind(ipEndPoint); serverSocket.Listen(1024); backgroundWorker.WorkerSupportsCancellation = true; backgroundWorker.RunWorkerAsync(); LogMessage(DateTime.Now + "->Socket啟動成功,監(jiān)聽IP:" + ipEndPoint.Address.ToString() + ":" + config.AppSettings.Settings["Middleware_PORT"].Value); } catch (Exception ex) { Com.DataCool.DotNetExpand.LogHelper.Error("服務啟動失敗,原因:" + ex.Message); } btnServiceControl.Tag = 1; btnServiceControl.Text = "停止監(jiān)聽"; btnServiceControl.BackColor = Color.Green; pbxServiceStatus.BackgroundImage = Properties.Resources.online_status; lbWebApiBaseAddress.Text = SocketBindingIP; hostObject = WebApp.Start<RegisterRoutesStartup>("http://" + SocketBindingIP + ":5990"); public class RegisterRoutesStartup { public void Configuration(IAppBuilder appBuilder) { HttpConfiguration config = new HttpConfiguration(); //自定義路由 config.Routes.MapHttpRoute( name: "CustomApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } ); //只響應Json請求 var jsonFormatter = new JsonMediaTypeFormatter(); config.Services.Replace(typeof(IContentNegotiator), new JsonContentNegotiator(jsonFormatter)); appBuilder.UseWebApi(config); } } 就是最后一句了。owin怎么宿主webapi去看看張善友等的文章吧。 public ApiActionResult BufferBox_API_Request(string StationNo, string CellNo, string Action) { var result = new ApiActionResult() { Success = false, Result = null, Message = "操作失敗。" }; byte[] results = new byte[1024]; Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { clientSocket.Connect(new IPEndPoint(IPAddress.Parse(conf.AppSettings.Settings["Middleware_IP"].Value), Convert.ToInt32(conf.AppSettings.Settings["Middleware_PORT"].Value))); using (var db = new BufferBoxDBEntities()) { var stationEntity = db.station_signin_session.Where(st => st.SessionDict == StationNo).FirstOrDefault(); if (stationEntity == null) { result.Message = "設備不存在或者設備編號有誤!"; result.Result = ""; return result; } var requestEntity = new API_Request_session { API_Request_IP = Request.GetClientIpAddress(), RequestID = Guid.NewGuid(), RequestData = CellNo + "|" + Action, RequestDataTime = DateTime.Now, ResultData = "", ExecuteFlag = false, StationNo = StationNo }; db.API_Request_session.AddObject(requestEntity); db.SaveChanges(); clientSocket.Send(Encoding.UTF8.GetBytes("api_request:" + JsonConvert.SerializeObject(requestEntity))); result.Success = true; result.Message = "設備已經(jīng)受理請求。"; result.Result = requestEntity.RequestID.ToString(); } } catch (Exception ex) { result.Message = "中間件發(fā)生異常:" + ex.Message; } return result; } 這可是項目分析的關鍵之處啊。中間件是如何轉發(fā)api請求并通知柜機客戶端執(zhí)行指令的呢。就是webapi里使用socket作為client去連接中間件的socket server的。 問題就是出在這里!webapi不能阻塞socket 直到柜機客戶端響應之后回復了再返回給外部。
2、php頁面js開POS觸摸屏電腦外接的錢箱 這是昨天晚上接的一個小活。新年第一單,正是有了前面項目的經(jīng)驗,給提供了這個解決方案。 項目背景: php做的bs項目打包成桌面項目用內嵌瀏覽器訪問php頁面來代替POS觸摸屏桌面程序。打印使用插件聽說解決了,但是打開錢箱遇到麻煩了。由于發(fā)包方不知道網(wǎng)頁如何控制本地設備,也不想用activex方式,所以提供了這個解決方案: POS觸摸屏上運行一windows服務程序對外提供api(控制錢箱)和php服務器端的中間件通信,中間件對外部暴露api。 這個項目圖片不高大上,所以只有代碼了: using System; using System.Net; using System.Web.Http; using System.Net.Sockets; using System.Configuration; using System.Text; namespace MiddlewareServer { /// <summary> /// POS觸摸屏收銀機錢箱控制API控制器 /// </summary> public class MoneyBoxApiController : ApiController { public static readonly Configuration conf = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); [HttpGet] /// <summary> /// 打開POS錢箱,IP取發(fā)起請求的客戶端的IP,中間件以此IP為依據(jù)通知該POS機執(zhí)行開錢箱動作 /// 局域網(wǎng)環(huán)境IP最好是靜態(tài)IP,不要使用DHIP,動態(tài)獲取 /// </summary> /// <returns>{Success,Result=請求發(fā)起機器的IP地址,Message}</returns> public ApiActionResult OpenMoneyBox() { var result = new ApiActionResult() { Success = false, Result = null, Message = "操作失敗。" }; byte[] results = new byte[1024]; Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { clientSocket.Connect(new IPEndPoint(IPAddress.Parse(conf.AppSettings.Settings["Middleware_IP"].Value), Convert.ToInt32(conf.AppSettings.Settings["Middleware_PORT"].Value))); string ip = Request.GetClientIpAddress(); clientSocket.Send(Encoding.UTF8.GetBytes("api_request:" + ip)); result.Result = ip; result.Success = true; result.Message = "請求成功。"; } catch (Exception ex) { result.Message = "中間件發(fā)生異常:" + ex.Message; } return result; } } }
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.ServiceProcess; using System.Text; using System.Runtime.InteropServices; using System.Configuration; using Microsoft.Win32.SafeHandles; using System.IO; using System.Net.Sockets; using System.Net; namespace MoneyBoxSvr { public partial class MoneyBoxService : ServiceBase { [DllImport("kernel32.dll", CharSet = CharSet.Auto)] private static extern IntPtr CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, int lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, int hTemplateFile); private Configuration config; /// <summary> /// 打印機端口名稱 /// </summary> public string PrintPortName { get { return config.AppSettings.Settings["PortName"].Value; } } /// <summary> /// 中間件的IP地址 /// </summary> public string RemoteServerIP { get { return config.AppSettings.Settings["MiddlewareIP"].Value; } } /// <summary> /// 中間件監(jiān)聽的端口 /// </summary> public int MiddlewarePort { get { return Convert.ToInt32(config.AppSettings.Settings["MiddlewarePort"].Value); } } protected Socket clientSocket = null; /// <summary> /// 緩沖區(qū) /// </summary> protected byte[] buffers = new byte[1024]; protected System.Threading.Thread socketThread; public MoneyBoxService() { InitializeComponent(); config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); } protected override void OnStart(string[] args) { StartSocketThread(); } protected override void OnStop() { base.OnStop(); } protected override void OnShutdown() { base.OnShutdown(); socketThread.Abort(); socketThread = null; } private void StartSocketThread() { socketThread = new System.Threading.Thread(ThreadWork); socketThread.Start(); } /// <summary> /// 異步接收到遠程請求 /// </summary> /// <param name="ar"></param> private void OnReceive(IAsyncResult ar) { try { IPEndPoint ipeSender = new IPEndPoint(IPAddress.Any, 0); EndPoint epSender = (EndPoint)ipeSender; //結束掛起的,從特定終結點進行異步讀取 if (clientSocket != null) { int len = clientSocket.EndReceiveFrom(ar, ref epSender); string requestCommand = System.Text.Encoding.UTF8.GetString(buffers); if (requestCommand.StartsWith("api_request")) { OpenMoneyBox(); } } } catch { } finally { try { IPEndPoint ipeSender = new IPEndPoint(IPAddress.Any, 0); EndPoint epSender = (EndPoint)ipeSender; buffers = new byte[1024]; clientSocket.BeginReceiveFrom(buffers, 0, buffers.Length, SocketFlags.None, ref epSender, new AsyncCallback(OnReceive), epSender); } catch { } } } private void ThreadWork() { while (true) { if (clientSocket == null) { #region 建立socket連接 IPAddress ip = IPAddress.Parse(RemoteServerIP); clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { // 首次探測時間5 秒, 間隔偵測時間2 秒 byte[] inValue = new byte[] { 1, 0, 0, 0, 0x88, 0x13, 0, 0, 0xd0, 0x07, 0, 0 }; clientSocket.IOControl(IOControlCode.KeepAliveValues, inValue, null); clientSocket.Connect(new IPEndPoint(IPAddress.Parse(RemoteServerIP), MiddlewarePort)); //配置服務器IP與端口 #region 簽到 string request = "pos_sign_in:"; clientSocket.Send(Encoding.UTF8.GetBytes(request)); IPEndPoint ipeSender = new IPEndPoint(IPAddress.Any, 0); EndPoint epSender = (EndPoint)ipeSender; buffers = new byte[1024]; clientSocket.BeginReceiveFrom(buffers, 0, buffers.Length, SocketFlags.None, ref epSender, new AsyncCallback(OnReceive), epSender); #endregion } catch { if (clientSocket != null) { clientSocket.Close(); clientSocket = null; } } #endregion } if (clientSocket != null ) { #region 發(fā)0字節(jié)的包探測連接是否可用 bool blockingState = clientSocket.Blocking; try { byte[] tmp = new byte[1]; clientSocket.Blocking = false; clientSocket.Send(tmp, 0, 0); } catch { if (clientSocket != null) { clientSocket.Close(); clientSocket = null; } } finally { if (clientSocket != null) { clientSocket.Blocking = blockingState; } } #endregion } System.Threading.Thread.Sleep(5000); } } /// <summary> /// 開錢箱 /// </summary> public void OpenMoneyBox() { IntPtr iHandle = CreateFile(PrintPortName, 0x40000000, 0, 0, 3, 0, 0); if (iHandle.ToInt32() != -1) { SafeFileHandle handle = new SafeFileHandle(iHandle, true); FileStream fs = new FileStream(handle, FileAccess.ReadWrite); StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.Default); sw.Write(((char)27).ToString() + "p" + ((char)0).ToString() + ((char)60).ToString() + ((char)255).ToString()); sw.Close(); fs.Close(); } } } } 好久沒寫博客了。就這樣吧,目的就是分享和總結。還有不說你也知道的,這文章怎么看怎么“軟”。希望大家體諒一下,技術把代碼變成錢本身就是困難的事情。適度廣告一下吧,項目和私活就是這樣找上門的。
突破短板,傳統(tǒng)桌面程序 使用webapi 擴展迎合web和移動端融合的需求 標簽: |
|