应用系统端口的统一设计(一)
本设计不通用,只针对特定的一类问题。
如果系统经常和多种端口打交道,例如:COM,TCP,UDP,短信,打印机等等外接端口通讯,那么可以这样设计。
每种通讯方式都是类似,连接,打开端口,接收和发送数据,解析数据给系统。
所以可以统一起来进行设计,这样扩充端口更加容易。
使用C#语言描述
public delegate void GameEventDelegate(object sender, Action action);// public class Action : EventArgs { public PlayPort Receiver = PlayPort.Logic; public Action(PlayPort sender, ICommand command) { Sender = sender; Receiver = PlayPort.Logic; Command = command; } public Action(PlayPort sender, PlayPort receiver, ICommand command) { Sender = sender; Receiver = receiver; Command = command; } public PlayPort Sender { get; set; } public ICommand Command { get; set; } }//端口服务接口 public interface IPortService { event GameEventDelegate OnPort; void Write(Action action); }Write函数:向设备写入数据
OnPort事件接收数据,并通知系统接收数据
Java代码和上面类似!
扩展接口可以这样
public class ControlPanelPort : IPortService { private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private ConfigManager config; private ControlPanelExecutor executor = new ControlPanelExecutor(); ControlPanelPortQueueManager controlPanelPortQueueManager = new ControlPanelPortQueueManager(); private IPersistService persistService; public event EventHandler DataArrived; public IDataParser Parser { set { controlPanelPortQueueManager.Parser = value; controlPanelPortQueueManager.Start(); } } public Queue<byte[]> DataQueue = new Queue<byte[]>(); public ControlPanelPort(IPersistService persistService) { this.persistService = persistService; config = ConfigManager.Instance(persistService.LoadConfig()); Init(); } public SerialPort ControlPanelSerialPort { get; set; } #region IPortService Members public event GameEventDelegate OnPort; /// <summary> /// action中的data格式必须为int[] /// </summary> /// <param name="action"></param> public void Write(Action action) { try { if (action.Receiver != PlayPort.ControlPanel) return; if (ControlPanelSerialPort != null && ControlPanelSerialPort.IsOpen) executor.Execute(this, action); } catch (IOException ex) { log.Error(ex.Message,ex); if(ControlPanelSerialPort!=null) ControlPanelSerialPort.Close(); } } #endregion // private void Init() { try { ControlPanelSerialPort = new SerialPort("control panel"); ControlPanelSerialPort.PortName = config[ConfigKey.控制面板串口]; ControlPanelSerialPort.BaudRate = 57600; ControlPanelSerialPort.Handshake = Handshake.None; ControlPanelSerialPort.DataBits = 8; ControlPanelSerialPort.StopBits = StopBits.One; ControlPanelSerialPort.Parity = Parity.None; ControlPanelSerialPort.ReceivedBytesThreshold = 2; ControlPanelSerialPort.WriteTimeout = SerialPort.InfiniteTimeout; ControlPanelSerialPort.ReadTimeout = SerialPort.InfiniteTimeout; ControlPanelSerialPort.DataReceived += controlPanelPort_DataReceived; ControlPanelSerialPort.Open(); ControlPanelSerialPort.DiscardInBuffer(); ControlPanelSerialPort.DiscardOutBuffer(); // controlPanelPortQueueManager.ControlPort = this; } catch (Exception ex) { log.Error("控制面板端口初始化失败!" + ex.Message); } } private void controlPanelPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { try { if (OnPort != null) { if (ControlPanelSerialPort == null) return; Application.DoEvents(); Thread.Sleep(50); byte[] data = new byte[ControlPanelSerialPort.BytesToRead]; ControlPanelSerialPort.Read(data, 0, data.Length); log.Warn("<<<<接收控制面板信息,length:" + data.Length); DataQueue.Enqueue(data); if (DataArrived != null) { DataArrived(this, null); } } } catch (Exception ex) { log.Error(ex.Message, ex); } } public void FireOnPort(Action action) { if(OnPort!=null) { OnPort(this, action); } } }其他所有的端口扩展都是类似的
在接受端口数据后,将数据直接放到处理队列里面,不要直接将处理代码放到这里,否则影响端口的继续工作,接收数据。不可以认为数据一次性都直接收完毕,数据可能是多次才能接受完成,所以处理的时候都要将数据放到队列里面,然后通过另一个线程处理。
数据接收函数
controlPanelPort_DataReceived
将数据放到队列中
DataQueue.Enqueue(data);
处理完毕后,触发事件,通知订阅者
OnPort(this, action);
Write数据到端口的执行者:ControlPanelExecutor
处理队列数据的管理者:ControlPanelPortQueueManager
持久化服务:IPersistService
其他端口都是类似处理!!!
下面将所有端口统一起来
public class PortProxy : IPortService
PortProxy类型是向前端表露的统一端口代理
他统一了所有端口的访问方式,这里使用了Proxy模式
public class PortProxy : IPortService { private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private IPortService communicationPortService1; private IPortService monitorPortService; private IPortService controlPanelPortService1; private IPortService printPortService1; private IPortService tcpPortService1; private IPortService sMSPortService; public IPortService CommunicationPortService { set { communicationPortService1 = value; communicationPortService1.OnPort += Event_OnPort; } } public IPortService MonitorPortService { set { monitorPortService = value; monitorPortService.OnPort += Event_OnPort; } } public IPortService PrintPortService { set { printPortService1 = value; printPortService1.OnPort += Event_OnPort; } } public IPortService ControlPanelPortService { set { controlPanelPortService1 = value; controlPanelPortService1.OnPort += Event_OnPort; } } public IPortService TcpPortService { set { tcpPortService1 = value; tcpPortService1.OnPort += Event_OnPort; } } public IPortService SMSPortService { set { sMSPortService = value; sMSPortService.OnPort += Event_OnPort; } } #region IPortService Members public event GameEventDelegate OnPort; public void Write(Action action) { switch (action.Receiver) { case PlayPort.ControlPanel: { controlPanelPortService1.Write(action); } break; case PlayPort.Printer: { printPortService1.Write(action); } break; case PlayPort.Udp: { communicationPortService1.Write(action); } break; case PlayPort.Tcp: { tcpPortService1.Write(action); } break; case PlayPort.SMS: { sMSPortService.Write(action); } break; case PlayPort.Monitor: { monitorPortService.Write(action); } break; default: Event_OnPort(this, action); break; } } #endregion private void Event_OnPort(object sender, Action action) { if (OnPort != null) OnPort(sender, action); } }他代理了所有的端口,另外值得说明的是他充当了一个逻辑接口,该接口是一个虚拟的系统之间通信的接口,并不代表一个实际存在的接口,该接口的存在大大减少了系统模块之间的耦合性
应用的时候通过注入将可以将Port实例注入了
public abstract class AbstractGameState : IGameState { protected IPortService logic; // protected const PlayPort LOGIC_PORT = PlayPort.Logic; protected const PlayPort CONTROL_PANEL_PORT = PlayPort.ControlPanel; protected const PlayPort PRINTER_PORT = PlayPort.Printer; protected const PlayPort SMS_PORT = PlayPort.SMS; protected const PlayPort UDP_PORT = PlayPort.Udp; #region IGameState Members public IPortService PortService { set { logic = value; logic.OnPort += Logic_OnPortEvent; } get { return logic; } } protected abstract void Logic_OnPortEvent(object sender, Action action); }这里面的logic即使端口,他代表了所有的端口服务,通过它可以访问所有的端口,端口服务的客户访问大大简化,也与所有的具体端口松耦合,维护上大为简化
访问方法:
SingleCommand command = SingleCommand.Instance[CommandCode.指令]; command.Data = printParams; logic.Write(new Action(PlayPort.Logic, PlayPort.Printer, command));
表示逻辑端口想打印机发送指令,进行打印
数据接收会触发OnPort事件
客户可以通过该事件进行程序处理
public abstract class AbstractSingleGameState:AbstractGameState { protected override void Logic_OnPortEvent(object sender, Action action) { // if(GameContext.GameState==null) return; if (GameContext.GameState.CurrentState == CurrentState) { try { ActionChainNode stateListener = ActionLockStateManager.Instance; if (stateListener.Process(this, action)) return; ThreadPool.QueueUserWorkItem(new WaitCallback(Doo),action); OnPortEvent(sender, action); } catch (Exception ex) { log.Error(ex.Message,ex); } } } private static ActionChainNode globalChain = GlobalActionChainFactory.GetChain(); protected void Doo(object state) { //将不是立即执行的命令填出gamestate栈中,等到actionFilterChain的调用 ActionChainNode stackChain = GlobalActionStackFactory.GetChain(); stackChain.Process(this, (Action) state); // globalChain.Process(this, (Action)state); } protected virtual void OnPortEvent(object sender, Action action) { } }程序通过Logic_OnPortEvent处理程序进行接受指令的处理
这里使用了职责链模式进行所有的指令处理,下一节将写出该模式的设计
该处理函数既可以处理全局指令,也可以处理当前状态指令,并将事件进行传递。
传递到具体的处理类
public class StepWaitingState : AbstractSingleGameState { public override GameState CurrentState { get { return GameState.StepWaiting; } } public StepWaitingState() { } public override object Enter(Action action) { } protected override void OnPortEvent(object sender, Action action) { Doo(action); } private void Doo(object state) { Action action = (Action) state; ActionChainNode controlPanelActionChain = WatingActionChainFactory.GetChain(); controlPanelActionChain.Process(this, action); } public override IGameState Transit(Action action) { if (GameContext.GameState.CurrentState == CurrentState) GameContext.SetState(StateFactory.StepPost, action); return GameContext.GameState; } }如果接受的指令数据传递到当前状态后,通过该状态的职责链进行处理
ActionChainNode controlPanelActionChain = WatingActionChainFactory.GetChain();
整体就是这样了!
端口代理的实例化
<object id="PortProxy" type="Single.Core.PortProxy,Single.Core"><property name="CommunicationPortService" ref="UdpPort"/><property name="PrintPortService" ref="PrintPort"/><property name="ControlPanelPortService" ref="ControlPanelPort"/><property name="TcpPortService" ref="TcpPort"/><property name="SMSPortService" ref="SMSPort"/><property name="MonitorPortService" ref="UdpMonitorPort"/></object>
PortProxy的注入
<object id="AbstractStateBase" abstract="true"><property name="PortService" ref="PortProxy"/><property name="PersistService" ref="SinglePersistService"/></object>
综上,这样设计之后,客户在使用的过程中所有的细节都没有接触,只是统一通过PortProxy进行通信,所有的端口扩展也都通过ProtProxy进行扩充,不影响客户使用,扩展也更容易,端口的扩展继承统一的PortService,扩展方式统一!
原始链接
http://qixin000.iteye.com/blog/1425135