热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

上位机与下位机交互让socket不死

需求说明:下位机是plc,西门子1200下位机只能做服务器端,监听一个端口,不能主动给客户端发送消息(原计

需求说明:

下位机是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的创建,完成任务就好

 


推荐阅读
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • Netty源代码分析服务器端启动ServerBootstrap初始化
    本文主要分析了Netty源代码中服务器端启动的过程,包括ServerBootstrap的初始化和相关参数的设置。通过分析NioEventLoopGroup、NioServerSocketChannel、ChannelOption.SO_BACKLOG等关键组件和选项的作用,深入理解Netty服务器端的启动过程。同时,还介绍了LoggingHandler的作用和使用方法,帮助读者更好地理解Netty源代码。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • Oracle seg,V$TEMPSEG_USAGE与Oracle排序的关系及使用方法
    本文介绍了Oracle seg,V$TEMPSEG_USAGE与Oracle排序之间的关系,V$TEMPSEG_USAGE是V_$SORT_USAGE的同义词,通过查询dba_objects和dba_synonyms视图可以了解到它们的详细信息。同时,还探讨了V$TEMPSEG_USAGE的使用方法。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 本文介绍了在处理不规则数据时如何使用Python自动提取文本中的时间日期,包括使用dateutil.parser模块统一日期字符串格式和使用datefinder模块提取日期。同时,还介绍了一段使用正则表达式的代码,可以支持中文日期和一些特殊的时间识别,例如'2012年12月12日'、'3小时前'、'在2012/12/13哈哈'等。 ... [详细]
  • 本文介绍了在iOS开发中使用UITextField实现字符限制的方法,包括利用代理方法和使用BNTextField-Limit库的实现策略。通过这些方法,开发者可以方便地限制UITextField的字符个数和输入规则。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • 本文讨论了微软的STL容器类是否线程安全。根据MSDN的回答,STL容器类包括vector、deque、list、queue、stack、priority_queue、valarray、map、hash_map、multimap、hash_multimap、set、hash_set、multiset、hash_multiset、basic_string和bitset。对于单个对象来说,多个线程同时读取是安全的。但如果一个线程正在写入一个对象,那么所有的读写操作都需要进行同步。 ... [详细]
  • 本文介绍了在使用Laravel和sqlsrv连接到SQL Server 2016时,如何在插入查询中使用输出子句,并返回所需的值。同时讨论了使用CreatedOn字段返回最近创建的行的解决方法以及使用Eloquent模型创建后,值正确插入数据库但没有返回uniqueidentifier字段的问题。最后给出了一个示例代码。 ... [详细]
  • 本文分析了Wince程序内存和存储内存的分布及作用。Wince内存包括系统内存、对象存储和程序内存,其中系统内存占用了一部分SDRAM,而剩下的30M为程序内存和存储内存。对象存储是嵌入式wince操作系统中的一个新概念,常用于消费电子设备中。此外,文章还介绍了主电源和后备电池在操作系统中的作用。 ... [详细]
  • Iamtryingtocreateanarrayofstructinstanceslikethis:我试图创建一个这样的struct实例数组:letinstallers: ... [详细]
  • 本文提供了关于数据库设计的建议和注意事项,包括字段类型选择、命名规则、日期的加入、索引的使用、主键的选择、NULL处理、网络带宽消耗的减少、事务粒度的控制等方面的建议。同时还介绍了使用Window Functions进行数据处理的方法。通过遵循这些建议,可以提高数据库的性能和可维护性。 ... [详细]
author-avatar
run032_736
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有