突破短板,传统桌面程序 使用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宿主:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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去看看张善友等的文章吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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。

这个项目图片不高大上,所以只有代码了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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;
        }      
    }
}

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
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();
            }
        }       
    }
}

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

 

posted @   数据酷软件  阅读(2990)  评论(12编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示