突破短板,传统桌面程序 使用webapi 扩展迎合web和移动端融合的需求

传统桌面程序不能完全被web和移动端替代,但是需要改造。这里要说的是巧用webapi把以前用dll和com组件,ocx等方式做接口,做分布式开发的方式,改成restful 风格api的方式实现跨平台,多客户端(类型).并分享几则案例.

1、智能储物柜

项目背景:某智慧城市项目需要用到有智能锁的储物柜,用app扫码控制存取,并和智慧城市后台交互。智能锁系统是工业的塔式控制器,使用modbus ascii协议控制,端口使用串口。储物柜配备了工控电脑32寸竖屏,工控电脑控制塔式控制器(单片机),工控机上需要开发一套桌面程序,对外暴露储物柜的功能性(存取物品),对用户来说作为人机交互界面。话写的有点难懂还是上图吧:

规格有几种,这是不是实物忘记了。总之也没去过现场。

柜机人机界面

说明:

工作区是底部的1024*1080像素的区域,关键设计是把二维码的内容设计成了JSON,app扫描后获取到设备和意图,智慧城市后台对云主机上的中间件发起控制请求,中间件转发给柜机程序,柜机程序和塔式控制器通信,塔式控制器控制锁动作。

中间件程序界面

说明:中间使用winform+owin宿主webapi,对外暴露api,对柜机程序提供套接字连接。中间件是socket server端,柜机程序作为client。

还是晕了吧,没看懂么。简单来说柜机程序是个上位机程序,设备需要把控制锁的需求封装成api给外部调用。这里的解决方案是使用中间件,中间件对外暴露api外部发起控制请求,中间件对内(设备端程序)执行控制指令。

为了实现"网页和移动客户端控制工控设备"这个核心需求,这也是挤破了脑袋吧.呵呵呵,总算不枉费你进来围观了一回...

不留下点代码,算什么分享呢!哼!

好的,上代码:

这就是传说中的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启动成功,监听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 = "停止监听";
                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 = "设备已经受理请求。";
                    result.Result = requestEntity.RequestID.ToString();
                }
            }
            catch (Exception ex)
            {
                result.Message = "中间件发生异常:" + ex.Message;
            }
            return result;
        }

  这可是项目分析的关键之处啊。中间件是如何转发api请求并通知柜机客户端执行指令的呢。就是webapi里使用socket作为client去连接中间件的socket server的。

问题就是出在这里!webapi不能阻塞socket 直到柜机客户端响应之后回复了再返回给外部。

2、php页面js开POS触摸屏电脑外接的钱箱

  这是昨天晚上接的一个小活。新年第一单,正是有了前面项目的经验,给提供了这个解决方案。

项目背景: php做的bs项目打包成桌面项目用内嵌浏览器访问php页面来代替POS触摸屏桌面程序。打印使用插件听说解决了,但是打开钱箱遇到麻烦了。由于发包方不知道网页如何控制本地设备,也不想用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取发起请求的客户端的IP,中间件以此IP为依据通知该POS机执行开钱箱动作
        /// 局域网环境IP最好是静态IP,不要使用DHIP,动态获取
        /// </summary>
        /// <returns>{Success,Result=请求发起机器的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 = "中间件发生异常:" + 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>
        /// 中间件监听的端口
        /// </summary>
        public int MiddlewarePort
        {
            get
            {
                return Convert.ToInt32(config.AppSettings.Settings["MiddlewarePort"].Value);
            }
        }

        protected Socket clientSocket = null;
        /// <summary>
        /// 缓冲区
        /// </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 发0字节的包探测连接是否可用
                    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();
            }
        }
    }
}

  好久没写博客了。就这样吧,目的就是分享和总结。还有不说你也知道的,这文章怎么看怎么“软”。希望大家体谅一下,技术把代码变成钱本身就是困难的事情。适度广告一下吧,项目和私活就是这样找上门的。

时间: 2024-10-25 17:06:32

突破短板,传统桌面程序 使用webapi 扩展迎合web和移动端融合的需求的相关文章

迁移桌面程序到MS Store(5)——.NET Standard

接下来的几篇,我想讨论下迁移桌面程序到MS Store,可以采用的比较常见.通用性比较强的实施步骤和分层架构. 通常商业项目一般都是不断的迭代,不太可能突然停止更新现有的桌面版本,然后花很长时间从头来写UWP版本,所以我建议将迁移的过程分阶段,通过几个版本的迭代,慢慢地从传统桌面程序,转变成一个可以通过MS Store审核的APPX程序.至于之后要不要变成真·UWP可以视市场反馈决定. 假设现有的桌面程序包含一个MainUI的WPF工程,多个.NET Framework 4.5的类库,以及一个C

小米新旗舰“翻车” 冲击中高端凸显品控短板(小米的缺点还真不少:电商、性价比、爆款、粉丝经济,说到底也都只是商业上的创新)

小米新旗舰“翻车” 冲击中高端凸显品控短板 按照消费者的理解,旗舰手机应该是绝大部分用户在手机选购上的终极选择,任何产品一旦定位旗舰市场,必定有顶级的价格和顶级的使用体验,功能上无所不能,质量上更是坚若磐石.然而,2017年小米手机有点烦,其刚刚发布的小米手机6是小米今年的重磅中高端新品,也是小米重塑品牌形象再次冲击中高端市场的关键,不过上市以来却出现了充电重启.WiFi断流等问题. 实际上,小米从2015年推出小米Note系列起就开始向中高端进军,只是效果一直不理想.既要有销量又要有口碑,从这

《zw版&#183;ddelphi与halcon系列原创教程》Halcon的短板与delphi

[<zw版·delphi与Halcon系列原创教程>Halcon的短板与delphi 看过<delphi与Halcon系列>blog的网友都知道,笔者对Halcon一直是非常推崇的,简直是天下第一,无所不能.     的确,Halcon是目前蓝星上最强的图像库,也许某些国家军用卫星的图像库,精度会高一点,这个只能猜测.     Halcon毕竟只是图像库而已,作为开发平台,与专业的delphi.vs相比,还是有很大差距的.      有经验的用户,特别是从事二次开发的外包团队,采用

如何使用程序调用webApi接口

如何使用程序调用webApi接口 在C#中,传统调用HTTP接口一般有两种办法: WebRequest/WebResponse组合的方法调用 WebClient类进行调用. 第一种方法抽象程度较低,使用较为繁琐:而WebClient主要面向了WEB网页场景,在模拟Web操作时使用较为方便,但用在RestFul场景下却比较麻烦,在Web API发布的同时,.NET提供了两个程序集:System.Net.Http和System.Net.Http.Formatting.这两个程序集中最核心的类是Htt

金牌董秘肖莉去职 万科合伙人制短板显现

金牌董秘肖莉去职 万科合伙人制短板显现 公司动向中国经营报[微博]2014-11-15 00:03 我要分享 3 11月11日,万科成立30年来唯一的女董事.高级副总裁肖莉跟万科“散伙”了.当天中午,肖莉通过朋友圈发出辞职信称,为了追求新的职业梦想,其将告别20年的万科黄金岁月,加盟深圳一家年轻的创业公司“房多多”,成为其合伙人.为完成工作交接,今年内将继续在万科履职一段时间,出席既定的境内外投资者会议. 这是近年来继徐洪舸.肖楠.刘爱明.袁伯银.杜晶之后,从万科出走的第6位副总裁级别高管. 对

Heron(一)—-storm的一些短板

twitter在使用storm过程中发现了一些storm的弊端,开发Herons相对storm需要提升的几个特性:1)更好的扩展性, 2)更容易调试 3)更高的性能  4)更好管理–可以和其他系统复用资源 storm的短板: 1)storm的worker调度策略非常复杂,一台机器上的多个worker由操作系统调度,worker中的每个executor会映射两个线程,这些线程的调度就使用jvm预先设定的基于优先级的调度策略,而每个线程需要运行多个task,excutor有需要根据输入数据运行合适的

IT建设如何面对“短板”和“孤峰”?

在与很多企业探讨信息化建设时,很多IT负责人都提出过一个问题,企业信息化建设的资源是有限的,信息化的资源是先强化企业的“孤峰”还是先支持“短板”? 企 业虽说是一个整体,但不同的领域如研发.生产.市场.销售.质量.管理等等发展是不均衡的,其中有些领域是企业的相对优势,甚至成为核心竞争力:还有些领 域相对比较弱,被称为短板.在企业规划IT建设的时候,往往会存在争论,优势部门会强调竞争对手正在追赶,企业要投入资源强化发展,IT资源也必须支持: 薄弱部门也会强调企业资源投入不足,与标杆企业对比存在很大

impinj R2000开发板维修记录——程序下载

1.impinj R2000开发板的组成部分,图1是开发板的照片 图1 电路板主要由R2000芯片和ARM芯片AT91SAM7S256组成,如图2所示,左边芯片是ARM芯片,右边芯片是R2000芯片.ARM芯片部分是一个相对独立的部分,只要ARM系统能够正常工作,就可以像其他的ARM系统(如Jlink)一样下载程序,程序可以任意下载,甚至可以下载一下Jlink的固件,这件电脑会识别成一个Jlink设备,下面讨论如何下载开发板的固件程序. 2.固定程序的下载 R2000开发板的固件可以在官网(ww

找出你的短板

<软技能:代码之外的生存指南>第35章发现自己的知识短板,本章的内容都是关于发现妨碍你发挥自身全部潜能的知识短板的.我们将研究这些短板为何会存在,如何找到它们,以及最终如何填补它们,从而让你不受自己所不知的限制.本节为大家介绍找出你的短板. 找出你的短板 你的知识短板并不全都显而易见.事实上,大多数的知识短板,你只能隐约觉察到.对于自己不知道的,你很难清楚地意识到,也很容易忽略. 知识短板会阻碍你进步.准确识别它们的最佳方式之一就是看看自己在哪些工作上花费了大量的时间,或者一直进行重复性劳动.