传统桌面程序不能完全被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();
}
}
}
}

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

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

  1. 行动起来:转换传统桌面应用程序到UWP 并发布到Windows 应用商店!

    一个月前微软发布了桌面应用程序转换器(Desktop Application Converter),让我们可以把现有的桌面应用程序(.NET 4.6.1 或 Win32)轻松转换成 通用 Window ...

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

    接下来的几篇,我想讨论下迁移桌面程序到MS Store,可以采用的比较常见.通用性比较强的实施步骤和分层架构. 通常商业项目一般都是不断的迭代,不太可能突然停止更新现有的桌面版本,然后花很长时间从头来 ...

  3. 迁移桌面程序到MS Store(1)——通过Visual Studio创建Packaging工程

    之前跑去做了一年多的iOS开发,被XCode恶心得不行.做人呢,最重要的是开心.所以我就炒了公司鱿鱼,挪了个窝回头去做Windows开发了.        UWP什么的很久没有正儿八经写了,国内的需求 ...

  4. 迁移桌面程序到MS Store(3)——开机自启动

    迁移桌面程序的时候,有可能你会遇到这么个需求——开机自启动.Windows传统桌面程序的传统陋习.不论什么奇葩软件都想要开机自启动,默认就给你打开,一开机哐哐哐什么雷,什么企鹅都蹦出来,也不管你用不用 ...

  5. 迁移桌面程序到MS Store(2)——Desktop App Converter

    迁移传统桌面程序到MS Store的另一种方式是使用Desktop App Converter工具.虽然本篇标题包含了Desktop App Converter(以下简称DAC),实际上我是来劝你别用 ...

  6. 迁移桌面程序到MS Store(11)——应用SVG图标

    在传统桌面程序中,对图标的使用大多是直接嵌入JPG或者PNG的图片.在祖传的1366x768分辨率下,并没有什么问题.相对于手机硬件的突飞猛进,也侧面反映了PC行业的落寞和桌面程序开发的不思进取.用3 ...

  7. 迁移桌面程序到MS Store(14)——APPX嵌入WCF Service以Admin权限运行

    Windows10 1809版本开始,微软又对UWP开放了新的Capability:AllowElevation. 通过这个新的Capability,UWP APP能够在运行时向用户请求Admin权限 ...

  8. 桌面程序的其他实现方式----使用WPF窗体展现网页

    需求 在WPF应用程序中,需要使用到WEB项目的资源,第一时间想到的就是在WPF窗口中,展现WEB项目中的页面,这样子有两点好处:一是,实现简单,不需要在WPF应用程序中实现UI布局和数据绑定:二是, ...

  9. 个人永久性免费-Excel催化剂功能第22波-Excel文件类型、密码批量修改,补齐PowerQuery短板

    Excel的多工作薄.多工作表批量合并功能,Excel用户很多这方面的使用场景,也促使了各大Excel各大插件们都在此功能上有所开发,体验程度不一,但总体能够满足大多数的应用场景,本人之前也开发个单独 ...

随机推荐

  1. django orm总结

    目录1.1.1 生成查询1.1.2 创建对象1.1.3 保存修改的对象1.1.4 保存 ForeignKey 和 ManyToManyField 字段1.1.5 检索对象1.1.6 检索所有的对象1. ...

  2. paip.windows io监控总结

    paip.windows io监控总结 io的主要参数是个.disk queue length 作者Attilax  艾龙,  EMAIL:1466519819@qq.com 来源:attilax的专 ...

  3. atitit.dw不能显示正确的百分比高度in dw的解决

    atitit.dw不能显示正确的百分比高度in dw的解决 div 设置35%的高度,三,不能正确的显示高度...环境dw cs6 但是设置161px奏能ok了...表明这个是dw的一个bug... ...

  4. paip.jdbc 连接自动释放的测试

    paip.jdbc 连接自动释放的测试 使用的mysql jdbc3.1.6  以及5.1.7 测试结果,在没有conn.close()的情况哈.. 作者Attilax  艾龙,  EMAIL:146 ...

  5. Windows Error Code(windows错误代码详解)

    0 操作成功完成. 1 功能错误. 2 系统找不到指定的文件. 3 系统找不到指定的路径. 4 系统无法打开文件. 5 拒绝访问. 6 句柄无效. 7 存储控制块被损坏. 8 存储空间不足,无法处理此 ...

  6. CreateJSのeasel.js(一)

    CreateJS是基于HTML5开发的一套模块化的库和工具. 基于这些库,可以非常快捷地开发出基于HTML5的游戏.动画和交互应用. CreateJS为CreateJS库,可以说是一款为HTML5游戏 ...

  7. NGUI ScrollView 循环 Item 实现性能优化

    今天来说说一直都让我在项目中头疼的其中一个问题,NGUI 的scrollView 列表性能问题,实现循环使用item减少性能上的开销. 希望能够给其他同学们使用和提供一个我个人的思路,这个写的不是太完 ...

  8. delegate 集成在类中,还是单独写在.h文件中?

    转:http://stackoverflow.com/questions/11382057/declaring-a-delegate-protocol There definitely are sub ...

  9. TexturePacker压缩png的命令

    压缩png效果最好的当然是TinyPNG这种神器了,不过一般情况下TexturePacker压缩出来的也基本上能达到效果. 你需要先安装TP(TexturePacker的简称,以下TP无特殊说明均指T ...

  10. NXP LPC 状态可配置的定时器(SCT)

    状态可配置的定时器(SCT) 前言正在申请专利的状态可配置的定时器(SCT),是一个复杂的,但易于配置的定时器,它提供前所未有的灵活性,使工程师们在未来证明他们的设计,并减少进入市场的时间.在其最简单 ...