jbase的计划有借助虚拟M来实现连仪器,之前陆续写了些TCP逻辑,今天终于整理完成了仪器设计。首先用java的cs程序测试TCP的服务和客户端。
javafx的示例加强
package sample;import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.ScrollPane;import java.awt.*;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;public class Main extends Application {//数字static int num = 0;//场景Scene scene;//场景1Scene scene1;//定时发送Socket sendSocket = null;//自动发送次数int sendNum = 0;//通讯日志StringBuilder sbLog=new StringBuilder();//换行符String lineStr=System.getProperty("line.separator");@Overridepublic void start(Stage stage) throws Exception {//舞台标题stage.setTitle("第一个java程序");// 流式布局:按照控件的添加次序按个摆放,按照从上到下、从左到右的次序摆放。FlowPane pane = new FlowPane(5, 5);// 居中显示pane.setAlignment(Pos.CENTER);// 场景scene = new Scene(pane, 800, 600);// 标签Label label = new Label("初始值:" + num);// 加1按钮Button addButton = new Button("加1");addButton.setOnMouseClicked(e -> {num++;label.setText("当前值:" + num);});//减1按钮Button subButton = new Button("减1");subButton.setOnMouseClicked(e -> {num--;label.setText("当前值:" + num);});//切换到场景1Button btnScene1 = new Button("场景1");btnScene1.setOnMouseClicked(e -> {stage.setScene(scene1);});//弹出一个子窗口Button btnShowChildForm = new Button("子窗口");btnShowChildForm.setOnMouseClicked(e -> {Child.ShowChild("子窗口", "传给子窗口的参数");});// 创建一个TextArea控件TextArea textArea = new TextArea();textArea.setWrapText(true); // 设置文本自动换行//定时器,主动上传时候用Timer timer = new Timer();TimerTask task = new TimerTask() {@Overridepublic void run() {if (sendSocket != null) {try {sendNum++;// 发送请求消息到服务端OutputStream outputStream = sendSocket.getOutputStream();PrintWriter out = new PrintWriter(new OutputStreamWriter(outputStream, "GBK"), true);out.println("这是定时发送的第" + sendNum + "次数据");out.flush();} catch (Exception ex) {}}}};// 设置定时器的执行策略,延迟0秒后开始执行,每隔1秒执行一次timer.schedule(task, 0, 5000);//启动tcp服务Button btnTcpServer = new Button("TCP服务");btnTcpServer.setOnMouseClicked(e -> {Thread clientThread = new Thread(new Runnable() {@Overridepublic void run() {try {ServerSocket serverSocket = new ServerSocket(8888);//增加一个无限循环while (true) {sbLog.append("服务启动"+lineStr);System.out.println("服务启动");//通知界面javafx.application.Platform.runLater(()->{textArea.setText(sbLog.toString());});//等待客户端连接,阻塞Socket clientSocket = serverSocket.accept();sendSocket = clientSocket;//得到输出流PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);System.out.println("客户端连接");sbLog.append("客户端连接"+lineStr);//通知界面javafx.application.Platform.runLater(()->{textArea.setText(sbLog.toString());});//得到输入流InputStream inputStream = clientSocket.getInputStream();//IO读取byte[] buf = new byte[10240];int readlen = 0;//阻塞读取数据while ((readlen = inputStream.read(buf)) != -1) {System.out.println(new String(buf, 0, readlen, "GBK"));sbLog.append(new String(buf, 0, readlen, "GBK")+lineStr);//通知界面javafx.application.Platform.runLater(()->{textArea.setText(sbLog.toString());});}//关闭输入inputStream.close();//关闭输出out.close();clientSocket.close();}} catch (Exception e) {}}});clientThread.start();});//启动tcp客户端Button btnTcpClient = new Button("TCP客户端");btnTcpClient.setOnMouseClicked(e -> {Thread clientThread = new Thread(new Runnable() {@Overridepublic void run() {try {try {// 创建 Socket 对象并连接到服务端Socket socket = new Socket("172.16.1.232", 1991);sendSocket = socket;// 发送请求消息到服务端OutputStream outputStream = socket.getOutputStream();PrintWriter out = new PrintWriter(new OutputStreamWriter(outputStream, "GBK"), true);out.println("你好,我是客户端");out.flush();//得到输入流InputStream inputStream = socket.getInputStream();//IO读取byte[] buf = new byte[10240];int readlen = 0;//阻塞读取数据while ((readlen = inputStream.read(buf)) != -1) {System.out.println(new String(buf, 0, readlen, "GBK"));sbLog.append(new String(buf, 0, readlen, "GBK")+lineStr);out.println("我已经收到数据");//通知界面javafx.application.Platform.runLater(()->{textArea.setText(sbLog.toString());});}//关闭输入inputStream.close();//关闭输出out.close();// 关闭连接socket.close();} catch (IOException ex) {ex.printStackTrace();}} catch (Exception e) {}}});clientThread.start();});pane.getChildren().addAll(addButton, subButton, btnScene1, btnShowChildForm, label, textArea, btnTcpServer, btnTcpClient);// 场景1// 流式布局:按照控件的添加次序按个摆放,按照从上到下、从左到右的次序摆放。FlowPane pane1 = new FlowPane(10, 10);// 居中显示pane1.setAlignment(Pos.CENTER);scene1 = new Scene(pane1, 200, 150);//返回开始的场景Button btnReturn = new Button("返回");btnReturn.setOnMouseClicked(e -> {stage.setScene(scene);});pane1.getChildren().addAll(btnReturn);//默认场景和显示stage.setScene(scene);stage.show();}public static void main(String[] args) {launch(args);}
}
客户端测试成功后设计jbase仪器基础,这里首先要理解连设备的本质,连TCP设备的话,其实真正的业务只关心我当做客户端还是服务端、以什么编码、然后解析和发送数据,我并不想关系TCP的实现细节。所以可以把初始化TCP和处理数据及定时上传以接口形式抽取出来。
先抽取处理仪器数据接口IMachDealData,具体仪器只需要实现该接口即可,抽取变性很重要
package LIS.Core.Socket;import java.io.PrintWriter;
import java.net.Socket;/*** 仪器处理数据接口,具体的仪器接口实现此接口后初始化MachSocketBase对象*/
public interface IMachDealData {/*** 处理上传定时器接口* @param sender Socket对象,用来发送比特用* @param writer 用来发布初始化指定的字符用*/public void DealUpTimer(Socket sender, PrintWriter writer);/*** 处理数据接收* @param data 公共层处理成字符串的数据* @param buf 没处理的比特数组* @param sender Socket对象,用来发送比特用* @param writer 用来发布初始化指定的字符用*/public void DealReceive(String data, byte[] buf, Socket sender, PrintWriter writer);}
然后实现MachSocketBase类作为连接TCP仪器的基础类,里面实现了tcp的客户端和服务端和定时器,里面对数据处理都是调用接口操作
package LIS.Core.Socket;import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;import LIS.Core.Socket.IMachDealData;
import LIS.Core.Util.LogUtils;/*** 通过Socket连接的仪器操作基类,由该类提供TCP客户端和服务端的公共逻辑,并且调用处理数据接口处理数据,从而分离TCP主体和业务处理逻辑,达到简化连设备目的*/
public class MachSocketBase {/*** 发送数据的操作对象*/public Socket Sender = null;/*** 写操作对象*/public PrintWriter Writer = null;/*** 主动上传定时器*/public Timer UpTimer = null;/*** 处理接口对象*/public IMachDealData DealObj;/*** 编码*/public String Encode;/*** 要关闭的客户端端口*/private Socket closeSocket = null;/*** 要关闭的服务端口*/private ServerSocket closeServerSocket = null;/*** 存IP和端口的唯一标识串*/public String ID;/*** 处理类的全面*/public String DealClassName;/*** 主处理进程*/Thread MainThread = null;/*** 定时器任务*/TimerTask timerTask = null;/*** 上传间隔*/Long UpPeriod = null;/*** 存启动和停止的错误串*/public String Err="";/*** 停止负载** @return 有错误就返回错误*/public String Stop() {try {if (MainThread != null) {MainThread.interrupt();}if (UpTimer != null) {UpTimer.cancel();}if (closeSocket != null) {closeSocket.close();;}if (closeServerSocket != null) {closeServerSocket.close();;}} catch (Exception ex) {Err=ex.getCause().getMessage();LogUtils.WriteExceptionLog("停止仪器TCP异常", ex);return ex.getCause().getMessage();}return "";}/*** 启动连接和定时器* @return 有错误就返回错误*/public String Start() {try {if (UpTimer != null) {// 设置定时器的执行策略,延迟0秒后开始执行,每隔1秒执行一次UpTimer.schedule(timerTask, 0, UpPeriod);}if (MainThread != null) {MainThread.start();}} catch (Exception ex) {Err=ex.getCause().getMessage();LogUtils.WriteExceptionLog("启动仪器TCP异常", ex);return ex.getCause().getMessage();}return "";}/*** 构造一个Socket基础并且启动Socket** @param ip IP地址,当服务端传空串* @param port 端口* @param upPeriod 上传毫秒间隔,不是主动上传的传null* @param dealObj 处理接口实现类* @param encode 编码格式,传null或空串默认为GBK*/public MachSocketBase(String ip, int port, Long upPeriod, IMachDealData dealObj, String encode) {//处理对象DealObj = dealObj;//编码if (encode == null || encode.isEmpty()) {encode = "GBK";}Encode = encode;UpPeriod = upPeriod;ID = ip + ":" + port;if(dealObj!=null){DealClassName=dealObj.getClass().getName();}//上传定时器if (upPeriod != null) {UpTimer = new Timer();timerTask = new TimerTask() {@Overridepublic void run() {try {//初始化写对象if (Sender != null && Writer == null) {Writer = new PrintWriter(new OutputStreamWriter(Sender.getOutputStream(), Encode), false);}DealObj.DealUpTimer(Sender, Writer);} catch (Exception ex) {LogUtils.WriteExceptionLog("仪器上传定时器异常", ex);}}};}//当客户端if (!ip.isEmpty()) {MainThread = new Thread(new Runnable() {@Overridepublic void run() {//得到输入流InputStream inputStream = null;//创建Socket对象并连接到服务端Socket socket = null;try {//创建Socket对象并连接到服务端socket = new Socket(ip, port);Sender = socket;closeSocket = socket;Writer = new PrintWriter(new OutputStreamWriter(Sender.getOutputStream(), Encode), false);//得到输入流inputStream = socket.getInputStream();//IO读取byte[] buf = new byte[102400];int readlen = 0;//阻塞读取数据while ((readlen = inputStream.read(buf)) != -1) {String res = new String(buf, 0, readlen, Encode);byte[] targetArray = new byte[readlen];System.arraycopy(buf, 0, targetArray, 0, readlen);//处理接收数据DealObj.DealReceive(res, targetArray, Sender, Writer);}} catch (IOException ex) {LogUtils.WriteExceptionLog("侦听仪器TCP异常", ex);} finally {try {if (inputStream != null) {//关闭输入inputStream.close();}if (Writer != null) {Writer.flush();//关闭输出Writer.close();}if (socket != null) {// 关闭连接socket.close();}} catch (Exception ex) {LogUtils.WriteExceptionLog("释放TCP资源异常", ex);}}}});}//当服务端else {MainThread = new Thread(new Runnable() {@Overridepublic void run() {//得到输入流InputStream inputStream = null;//创建Socket对象并连接到服务端Socket socket = null;try {ServerSocket serverSocket = new ServerSocket(port);closeServerSocket = serverSocket;//增加一个无限循环while (true) {//等待客户端连接,阻塞socket = serverSocket.accept();Sender = socket;//得到输出流Writer = new PrintWriter(new OutputStreamWriter(Sender.getOutputStream(), Encode), false);//得到输入流inputStream = socket.getInputStream();//IO读取byte[] buf = new byte[102400];int readlen = 0;//阻塞读取数据while ((readlen = inputStream.read(buf)) != -1) {String res = new String(buf, 0, readlen, Encode);byte[] targetArray = new byte[readlen];System.arraycopy(buf, 0, targetArray, 0, readlen);//处理接收数据DealObj.DealReceive(res, targetArray, Sender, Writer);}}} catch (IOException ex) {LogUtils.WriteExceptionLog("侦听仪器TCP异常", ex);} finally {try {if (inputStream != null) {//关闭输入inputStream.close();}if (Writer != null) {Writer.flush();//关闭输出Writer.close();}if (socket != null) {// 关闭连接socket.close();}} catch (Exception ex) {LogUtils.WriteExceptionLog("释放TCP资源异常", ex);}}}});}}
}
然后为了统一管理所有TCP仪器提供MachManager,接口开发者之需要关心GetMachSocketBase方法
package LIS.Core.Socket;import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import LIS.Core.Socket.MachSocketBase;
import LIS.Core.Socket.IMachDealData;/*** 统一管理所有仪器的MachSocketBase对象*/
public class MachManager {/*** 存所有仪器连接*/public static ConcurrentHashMap<String, MachSocketBase> AllMach = new ConcurrentHashMap<>();/*** 更新处理接口对象* @param dealClass* @throws Exception*/public static void UpdateDeal(Class dealClass) throws Exception{String dealClsName=dealClass.getName();for (Map.Entry<String, MachSocketBase> entry : AllMach.entrySet()) {String key = entry.getKey();MachSocketBase value = entry.getValue();if(value.DealClassName.equals(dealClsName)){//创建对象Object o = dealClass.getConstructor().newInstance();value.DealObj=(IMachDealData)o;System.out.println("更新:"+value.ID+"的数据处理类");}}}/*** 得到仪器连接的Socket管理类,直接就启动了控制了* @param ip IP地址,当服务端传空串* @param port 端口* @param upPeriod 上传毫秒间隔,不是主动上传的传null* @param dealObj 处理接口实现类* @param encode 编码格式,传null或空串默认为GBK* @return*/public static MachSocketBase GetMachSocketBase(String ip, int port, Long upPeriod, IMachDealData dealObj, String encode){MachSocketBase base=new MachSocketBase(ip,port,upPeriod,dealObj,encode);//注册到管理RegisterMachSocketBase(base);return base;}/*** 注册到管理* @param base*/public static void RegisterMachSocketBase(MachSocketBase base){//先停止老的接口if(AllMach.containsKey(base.ID)){AllMach.get(base.ID).Stop();AllMach.remove(base.ID);}//加入管理AllMach.put(base.ID,base);base.Start();}
}
然后为了解决修改仪器接口脚本后实时生效实现仪器注解,接口修改后会自动调用MachManager.UpdateDeal更新处理类,这样不用频繁重启仪器控制
//特性,申明此特性的仪器接口在修改后自动调用启动
package LIS.Core.CustomAttributes;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 申明此特性的仪器接口在修改后自动调用启动*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Mach {}
然后实现一个实例仪器接口
import LIS.Core.CustomAttributes.Mach;
import LIS.Core.Socket.IMachDealData;
import LIS.Core.Socket.MachSocketBase;
import appcode.BaseHttpHandlerNoSession;
import appcode.Helper;import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;/***连仪器示例代码*/
@Mach
public class MachDemo extends BaseHttpHandlerNoSession implements IMachDealData {/*** 缓存数据*/private static StringBuilder dataCache=new StringBuilder();//换行符private static String lineStr=System.getProperty("line.separator");/*** 显示日志* http://localhost:8080/ankilis/mi/MachDemo.ashx?Method=ShowLog* @return*/public String ShowLog(){return dataCache.toString();}/*** 启动控制* http://localhost:8080/ankilis/mi/MachDemo.ashx?Method=Start* @return* @throws Exception*/public String Start() throws Exception{//ip地址String ip=Helper.ValidParam(LIS.Core.MultiPlatform.LISContext.GetRequest(Request, "ip"), "");//端口int port=Helper.ValidParam(LIS.Core.MultiPlatform.LISContext.GetRequest(Request, "port"), 8888);MachSocketBase base=LIS.Core.Socket.MachManager.GetMachSocketBase(ip,port,Long.valueOf(5000),this,"GBK");if(base.Err.isEmpty()){return Helper.Success();}else{return Helper.Error(base.Err);}}/*** 处理上传定时器接口* @param sender Socket对象,用来发送比特用* @param writer 用来发布初始化指定的字符用*/public void DealUpTimer(Socket sender, PrintWriter writer){String sendStr="H->M:我主动定时给你推送的数据</br>";//返回数据writer.print(sendStr);writer.flush();dataCache.append(sendStr+lineStr);}/*** 处理数据接收* @param data 公共层处理成字符串的数据* @param buf 没处理的比特数组* @param sender Socket对象,用来发送比特用* @param writer 用来发布初始化指定的字符用*/public void DealReceive(String data, byte[] buf, Socket sender, PrintWriter writer){//缓存数据dataCache.append("M->H:"+data+lineStr);String sendStr="H->M:我收到你发给我的:"+data+"</br>";//返回数据writer.print(sendStr);writer.flush();dataCache.append(sendStr+lineStr);}
}
启动控制
这样就可以在这个基础上轻松连接TCP的仪器了,还是脚本化、还不用来回重启控制