需求说明:

下位机是plc,西门子1200

下位机只能做服务器端,监听一个端口,不能主动给客户端发送消息(原计划是上位机也是监听一个端口,供下位机来访问,上传数据,结果现实很骨感)

上位机(pc)充当客户端,可以主动连接下位机交换信息

具体需求:

1,上位机给下位机下达工作数据

(比如下位机是生产纸张,上位机需要发给下位机纸张的尺寸,数量等数据)

2,下位机会在某个特定的时间“发送”数据,供上位机“接收”

(注意  “发送” 是加上引号的,因为服务器(下位机)是不能主动给客户端(上位机,pc)发送数据的,这是此文需要解决的问题)

需求1毫无难度,直接在用户在上位机点击下单,上位机即可创建一个socket将相关数据下传即可

重点来看看需求2;

可以想象为服务器与客户端(浏览器)

不妨以秒杀为例,

客户端(浏览器)提交秒杀请求(相当于这里的上位机给下位机下单),由于秒杀并发很大,不能实时返回数据(一般是异步下单,这里不深究)

客户端如果需要知道秒杀的结果,会在浏览器(客户端)通过轮询查询订单状态(如果有,则表示下单成功)

在需求2中,“客户端” 就指的是上位机创建的“socket”,socket是唯一和下位机沟通的途径,而且下位机不能创建socket,主动发数据给上位机,那么上位机创建得到socket,能否让他成为一个永久的客户端,上位机定时的取这个socket里的数据呢??

答案是肯定的:

思路:

将socket封装成一个单例,需求1和需求2中的客户端(上位机的socket) 使用同一个socket

问题又来了:

问题1:

如果上位机正在给下位机下传工作信息,下位机恰好也在此时完成了一些工作,将工作状态通过socket(由上位机创建的)发送数据,那么会有可能出现冲突,有点乱套

解决:

尝试加锁,让这个socket同一时间只能用在  过程1 ( 上位机给下位机下传工作数据)

                                                                           或者 过程2(上位机周期读取socket中的数据(也就是下位机返回的工作完成情况这些数据)),

通过加锁:让这一个socket在某一个时刻,只能用于过程1或者过程2

问题2:

如果socket挂掉了?该如何获取与下位机的交互数据的“要道” 呢??

解决:

封装后的socket单例类,在使用socket读、写时,加上try catch 捕捉  SocketException,出现此异常,socket连接就是断开了,重新是用封装好的单例类的getInstance方法获取 socket对象,重建连接

此单例实现代码如下:

其中需要注意的是:

1,Semphore这一锁(信号量),参数指定此锁允许最多同时被多少个线程使用某一个资源(或者是资源的个数)

2,isDead 标记,用来标记当前单例对象是否断开(socket),如果是断开的,在getInstance是对其做了判断

如果没有单例对象,或者单例对象标记为“死亡”,那么就创建新的单例对象

第2点保证了,同一时刻,只能有一个线程使用socket

第2点保证了,socket的持久通信,及时下位机重启或者其他原因导致socket连接中断,也能重新创建连接


/*** socket单例  用于维系上位机与下位机的通信* 需要保证是同一个socket,并且需要注意锁的问题*/
public class SocketSingleton extends Socket{//只允同时一个线程访问此单例public static Semaphore singletonLock=new Semaphore(1);private boolean isLocked;//标记单例是否被锁住,有Semaphore,这里就可以省略了private volatile  boolean isDead;//标记单例是否死亡private static SocketSingleton ourInstance=null;private SocketSingleton() throws IOException {super(WINDER_IP,WINDER_PORT);isDead=false;ourInstance=this;isLocked=false;}/*** 获取锁*/public synchronized  void  getLock(){this.isLocked=true;}/*** 释放锁*/public synchronized  void  releaseLock(){this.isLocked=false;}/**** @return 检测锁*/public synchronized  boolean checkIfLock(){return this.isLocked;}/***   kill 连接异常的socket*/public static void killSingleton(){ourInstance.isDead=true;}/**** @return* @throws IOException* 如果当前单例的socket是dead,就创建新的socket*/public synchronized static SocketSingleton getInstance() throws IOException {return (Objects.isNull(ourInstance)||ourInstance.isDead) ? new SocketSingleton():ourInstance;}
}

使用:

以下位机状态“返回“”为例”

1 先获取锁(获取资源)

2  业务操作

3 再释放锁(释放资源)

 @Scheduled(cron = "0/10 * * * * ?")@Transactional(rollbackFor = Exception.class)public void myTestWork() throws IOException, InterruptedException {TubeServiceWithPC2Winder tubeServiceWithPC2Winder = new TubeServiceWithPC2Winder();log.info("定时器启动!{}", new Timestamp(System.currentTimeMillis()));SocketSingleton socket=SocketSingleton.getInstance();try{
//理解为获取锁,或者是获取资源,如果没有,这里会阻塞吗,直到其他线程将资源释放,这里才会往下进行SocketSingleton.singletonLock.acquire();log.info("状态交互获取到锁  singletonLock:{}",new Timestamp(System.currentTimeMillis()));socket.setSoTimeout( SOCKET_TIME_READ_timeout);log.info("状态交互 hashcode:{}",socket.hashCode());InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream();byte[] dataFromServer = new byte[WORK_FINISHED_BYTES_LENGTH];inputStream.read(dataFromServer);ByteQueue byteQueue = new ByteQueue(dataFromServer);log.info("来自服务端的数据:{}", byteQueue.toString());//数据处理的业务逻辑,此处忽略Tube tubeInfo = resolveData(dataFromServer);}catch(SocketTimeoutException e){log.info("状态交互 read 超时");}catch (SocketException e){//处理连接中断,重新创建单例socketSocketSingleton.killSingleton();SocketSingleton.getInstance();}  finally{
// 这里理解为释放锁,或者是释放资源(一共只有1个资源),如果不释放,其他线程将不能获得此资源,比如下一个周期到了,定时器将停在     SocketSingleton.singletonLock.acquire() 无法向下进行SocketSingleton.singletonLock.release();log.info("状态交互 释放成功  singletonLock:{}",new   Timestamp(System.currentTimeMillis()));}}

定时任务改进说明:

最初是在定时器里面没有使用单例,每个周期到了,将new Socket(),发现随着执行的任务次数增加,会出现问题

内存资源的问题:

比如我创建2000个socket:

 @Testpublic  void  testCreateMoreSocket() throws IOException, InterruptedException {for(int i=0;i<2000;i++){Socket socket=new Socket(WINDER_IP,WINDER_PORT);log.info("socket:{},hashCode:{}",new Object[]{i,socket.hashCode()});
//           Thread.sleep(1*1000);}}

创建之前:

创建之后:变化感觉不大,但是idea电脑卡死将近5秒

定时任务是会一直执行的,如果是4000次又会是怎样的呢?

当创建完1563个的时候已经抛异常了,有一瞬间cpu使用率100%

改下代码,创建socket使用完就将其close,测试结果竟然让调试助手还是无响应了,

  @Testpublic  void  testCreateMoreSocket() throws IOException, InterruptedException {for(int i=0;i<4000;i++){Socket socket=new Socket(WINDER_IP,WINDER_PORT);log.info("socket:{},hashCode:{}",new Object[]{i,socket.hashCode()});
//           Thread.sleep(1*1000);socket.close();}}

可能是创建台频繁,那就延时1秒试试,然后喝 1*n 杯咖啡(4000个创建完需要一个小时,暂时就不测试了,毕竟咖啡不够了)

上面的测试过程是为了说明创建socket是需要耗费资源,那么就得减少socket的创建,完成任务就好

上位机与下位机交互--让socket不死相关推荐

  1. 基于STM32C8T6、ESP8266-01S、JavaWeb、JSP、Html、JavaScript、Android、服务器和客户端设计、上位机和下位机设计等技术融合的物联网智能监控系统设计与实现

    系列文章目录 第一章ESP8266的java软件仿真测试 第二章ESP8266硬件与软件测试 第三章ESP8266客户端与Java后台服务器联调 第四章ESP8266客户端与JavaWeb服务器联调 ...

  2. 上下位机通讯协议_上位机与下位机的区别通讯

    上位机是指可以直接发出操控命令的计算机,一般是PC/host computer/master computer/upper computer,屏幕上显示各种信号变化(液压,水位,温度等).下位机是直接 ...

  3. 上位机和下位机的概念,理解如何实现PC从PLC中读取数据?

    市面上的PLC有上百种, 西门子的, 三菱的, 欧姆龙的等等. 上位机和下位机的理解: 上位机是指可以直接发出操控命令的计算机,一般是PC/host computer/master computer/ ...

  4. 打开单片机世界的大门——上位机控制下位机实例详解

    上位机控制下位机实例详解 一.基本概念 上位机与下位机 串口 数据表达 二.下位机程序 三.上位机程序 四.总结 一.基本概念 在开始讲解前,先来看几个基本概念,如果是有基础的大佬,请直接跳到下一节. ...

  5. 超详细Klipper 上位机与下位机配置

    (适用多数Mega2560芯片打印机主板,本文使用香橙派ZERO2作为上位机) 上位机:ZERO2 下位机:打印机主板 下载镜像系统 首先,去Armbian官网下载Buster系统镜像:Armbian ...

  6. 计算机基础-工控机、上位机、下位机、stm32、单片机

    工控机 定义:(Industrial Personal Computer,IPC)即工业控制计算机,主要用于工业生产上. 性能:采用全钢机箱,抗震性能好,抗电磁干扰,抗冲击. 结构:包括CPU.io外 ...

  7. 什么是上位机、下位机

    上位机 上位机是指可以直接发出操控命令的计算机, 一般是PC/host computer/master computer/upper computer, 屏幕上显示各种信号变化(液压,水位,温度等). ...

  8. java实现上位机与下位机串口通信

    串口通信是在工程应用中很常见.在上位机与下位机通讯过程中常通过有线的串口进行通信,在低速传输模式下串口通信得到广泛使用.在说个之前先来简单解释一下上位机与下位机的概念. 上位机与下位机 通常上位机指的 ...

  9. QT5实现串口收发数据(上位机与下位机通信)

    最近帮老师做一个应用程序,通过上位机与下位机进行串口通信,最后实现实时绘图,通过几天努力,成功实现蓝牙串口通信. 参考博客1 注意:代码中一些与串口无关代码,可以忽略掉 一.QT5串口基础知识 1. ...

最新文章

  1. 这就是我向您推荐使用Thunderbird邮件客户端的理由
  2. docker安装mysql及相关配置、运行细节和常见报错解决方案
  3. Angular Component template函数执行上下文的对象
  4. C语言与Java的深情对话:儿子,还得多练几年啊!老子还是老子
  5. oracle中的序列 cache,oracle row cache lock 之sequence
  6. 2012、12、17
  7. TSC工业型条码打印机的价格的影响因素有哪些呢?
  8. 为Node.js编写组件的几种方式
  9. php系列框架的加速器Opcache
  10. Object.defineProperty()属性设置介绍
  11. DevExpress chartControl 数据绑定
  12. 机器视觉可以应用到哪些场景中?
  13. jquery.blockui示例
  14. alpha测试和betal测试
  15. 学习Linux你必须知道的那些事儿
  16. Pytorch——pytorch的基本数据类型
  17. 跨时钟域问题(二)(单bit信号跨时钟域 1. 电平同步器 2. 边沿同步器 3. 脉冲检测器)
  18. 电脑端手机模拟器软件
  19. python信息检索系统_GitHub - Uyouii/SearchingSystem: python实现的基于倒排索引和向量空间模型实现的信息检索系统...
  20. 海外邮件发送指南(一)

热门文章

  1. mysql中查找出生日期_如何在MySQL中根据出生日期记录显示日期名称?
  2. 大智慧专业财务数据服务器文件,大智慧专业财务数据及代码内容对照表-2
  3. 初识CMake,如何编写一个CMake工程(上)
  4. sgu482 Impudent Thief (动态规划)
  5. 数据研究:淘宝流量与转化率隐藏的那些秘密
  6. 居于mtk芯片安卓车机系统具体流程
  7. cad调了比例因子没反应_天正CAD标注比例大小调整方法
  8. cad注释比例和打印比例不一样_CAD注释性比例该如何设置?看完你就懂了
  9. python中取对数怎么表示_python中取对数
  10. 公告丨Dex.top(大力士)上线Opengram (GRAM)