• 突破短板,传统桌面程序 使用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();
                }
            }        
        }
    }
    

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

  • 相关阅读:
    关于$.ajax同步和异步的问题和提交后台的一些问题。
    maven的web项目手工发布
    eclipse中的maven配置
    springmvc转页面
    javax.validation.ValidationException: Unable to create a Configuration, because no Bean Validation provider could be found. Add a provider like Hibernate Validator (RI) to your classpath.
    Maven项目导入后打红色X
    springmvc 拦截器,不拦截jsp文件
    RAID 磁盘阵列
    消息队列
    查看占用内存最高的进程
  • 原文地址:https://www.cnblogs.com/datacool/p/2016datacool_project.html
Copyright © 2020-2023  润新知