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;
//场景1
Scene scene1;
//定时发送
Socket sendSocket = null;
//自动发送次数
int sendNum = 0;
//通讯日志
StringBuilder sbLog=new StringBuilder();
//换行符
String lineStr=System.getProperty("line.separator");
@Override
public 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);
});
//切换到场景1
Button 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() {
@Override
public 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() {
@Override
public 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() {
@Override
public 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() {
@Override
public 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() {
@Override
public 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() {
@Override
public 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的仪器了,还是脚本化、还不用来回重启控制