微信公众号:bugstack虫洞栈 | 关注获得源码
沉淀、分享、成长,专注于原创专题案例,以最易学习编程的方式分享知识,让自己和他人都能有所收获。目前已完成的专题有;Netty4.x实战专题案例、用Java实现JVM、基于JavaAgent的全链路监控、手写RPC框架、架构设计专题案例[Ing]等。

前言介绍

在物联网开发中,常常需要通过网页端来控制设备,包括;获取信息、执行操作、启动停止等,就像我们在手机上会控制家里的小米盒子、路由器、电饭煲或者在线养狗等一些设备一样。在这里所有的下层设备都可以通过socket通信链接到服务端,而用户一端在通过http链接或者websocket链接到服务端,通过发送和接收数据来做出相应的行为操作。如下图;

案例目标

  1. 本章节整合Springboot+Netty,通过部署nettySocket与webSocket两套服务端,来接收转发行为消息。
  2. 客户端采用js链接websocket,用于接收服务端反馈与发送指令,用于获取下位机信息。
  3. 在test中启动一个模拟下位机,用于反馈信息数据。在真实开发中下位机与服务端通信通常是定义好的字节码,需要自己编写解码器。

环境准备

  1. jdk 1.8.0
  2. IntelliJ IDEA Community Edition 2018.3.1 x64
  3. Netty 4.1.36.Final

代码示例

itstack-demo-netty-3-01
└── src├── main│   ├── java│   │   └── org.itstack.demo.ark│   │       ├── domain│   │       │  ├── msgobj│   │       │ │   ├── Feedback.java│   │       │  │   ├── QueryInfoReq.java│   │       │  │   └── Text.java│   │       │  ├── Device.java│   │       │    ├── EasyResult.java │   │       │   ├── InfoProtocol.java│   │       │  └── ServerInfo.java │   │       ├── server│   │       │ ├── socket│   │       │ │   ├── MyChannelInitializer.java│   │       │  │   ├── MyServerHandler.java│   │       │   │   └── NettyServer.java    │   │       │   └── websocket│   │       │      ├── MyWsChannelInitializer.java│   │       │        ├── MyWsServerHandler.java│   │       │     └── WsNettyServer.java│   │       ├── util│   │       │ ├── CacheUtil.java│   │       │ ├── MsgUtil.java│   │       │   └── MsgUtil.java│   │       ├── web│   │       │    └── NettyController.java    │   │       └── Application.java│   └── resources   │   │   └── application.yml│   └── webapp│       ├── arkWs│       │ ├── js│       │ │   └── ws.js   │       │   └── arkWsControlCenter.html │       ├── res     │       └── WEB-INF│            └── index.jsp   │└── test└── java└── org.itstack.demo.test└── ApiTest.java

演示部分重点代码块,完整代码下载关注公众号;bugstack虫洞栈,回复Netty案例

server/socket/MyServerHandler.java & socket数据处理

  • 当有下位机链接服务端时,构建下位机信息,实际使用可以通过注册方式进行链接验证。
  • 当收到下位机信息后转发到websocket端,使网页端收到下位机信息反馈
public class MyServerHandler extends ChannelInboundHandlerAdapter {private Logger logger = LoggerFactory.getLogger(MyServerHandler.class);/*** 当客户端主动链接服务端的链接后,这个通道就是活跃的了。也就是客户端与服务端建立了通信通道并且可以传输数据*/@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {SocketChannel channel = (SocketChannel) ctx.channel();String channelId = channel.id().toString();System.out.println("链接报告开始");System.out.println("链接报告信息:有一客户端链接到本服务端。channelId:" + channelId);System.out.println("链接报告IP:" + channel.localAddress().getHostString());System.out.println("链接报告Port:" + channel.localAddress().getPort());System.out.println("链接报告完毕");//构建设备信息{下位机、中继器、IO板卡}Device device = new Device();device.setChannelId(channelId);device.setNumber(UUID.randomUUID().toString());device.setIp(channel.remoteAddress().getHostString());device.setPort(channel.remoteAddress().getPort());device.setConnectTime(new Date());//添加设备信息deviceGroup.put(channelId, device);CacheUtil.cacheClientChannel.put(channelId, channel);}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object objMsgJsonStr) throws Exception {//接收设备发来信息System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 接收到消息内容:" + objMsgJsonStr);//转发消息到WsCacheUtil.wsChannelGroup.writeAndFlush(new TextWebSocketFrame(objMsgJsonStr.toString()));}}

server/websocket/MyWsServerHandler.java & websocket数据处理

  • websocket数据需要转换后使用,可以支持文本消息,本案例中使用json字符串,方便对象传输
  • channelRead转发数据,当收到数据后发送给下位机,主要通过内容中channel控制
public class MyWsServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {...//wsif (msg instanceof WebSocketFrame) {WebSocketFrame webSocketFrame = (WebSocketFrame) msg;//关闭请求if (webSocketFrame instanceof CloseWebSocketFrame) {handshaker.close(ctx.channel(), (CloseWebSocketFrame) webSocketFrame.retain());return;}//ping请求if (webSocketFrame instanceof PingWebSocketFrame) {ctx.channel().write(new PongWebSocketFrame(webSocketFrame.content().retain()));return;}//只支持文本格式,不支持二进制消息if (!(webSocketFrame instanceof TextWebSocketFrame)) {throw new Exception("仅支持文本格式");}String request = ((TextWebSocketFrame) webSocketFrame).text();System.out.println("服务端收到:" + request);InfoProtocol infoProtocol = JSON.parseObject(request, InfoProtocol.class);//socket转发消息String channelId = infoProtocol.getChannelId();Channel channel = CacheUtil.cacheClientChannel.get(channelId);if (null == channel) return;channel.writeAndFlush(MsgUtil.buildMsg(infoProtocol));//websocket消息反馈发送成功ctx.writeAndFlush(MsgUtil.buildWsMsgText(ctx.channel().id().toString(), "向下位机传达消息success!"));}}}

web/NettyController.java & 控制层方便获取服务端信息

  • 控制层提供了查询服务列表、链接设备信息、以及主动触发信息发送
  • 另外如果需要将服务端的启动关闭进行手动控制,可以在这里面提供方法供调用
@Controller
public class NettyController {private Logger logger = LoggerFactory.getLogger(NettyController.class);@RequestMapping("/index")public String index() {return "index";}@RequestMapping("/queryNettyServerList")@ResponseBodypublic Collection<ServerInfo> queryNettyServerList() {try {Collection<ServerInfo> serverInfos = CacheUtil.serverInfoMap.values();logger.info("查询服务端列表。{}", JSON.toJSONString(serverInfos));return serverInfos;} catch (Exception e) {logger.info("查询服务端列表失败。", e);return null;}}@RequestMapping("/queryDeviceList")@ResponseBodypublic Collection<Device> queryDeviceList() {try {Collection<Device> deviceInfos = CacheUtil.deviceGroup.values();logger.info("查询设备链接列表。{}", JSON.toJSONString(deviceInfos));return deviceInfos;} catch (Exception e) {logger.info("查询设备链接列表失败。", e);return null;}}@RequestMapping("/doSendMsg")@ResponseBodypublic EasyResult doSendMsg(String reqStr) {try {logger.info("向设备发送信息[可以通过另外一个Web服务调用本接口发送信息]:{}", reqStr);InfoProtocol infoProtocol = MsgUtil.getMsg(reqStr);String channelId = infoProtocol.getChannelId();Channel channel = CacheUtil.cacheClientChannel.get(channelId);channel.writeAndFlush(MsgUtil.buildMsg(infoProtocol));return EasyResult.buildSuccessResult();} catch (Exception e) {logger.error("向设备发送信息失败:{}", reqStr, e);return EasyResult.buildErrResult(e);}}}

Application.java & 启动层

  • 通过继承CommandLineRunner方法,在服务就绪后启动socket服务
  • 启动后需要循环验证是否启动完成
@SpringBootApplication
@ComponentScan("org.itstack.demo.ark")
public class Application implements CommandLineRunner {private Logger logger = LoggerFactory.getLogger(Application.class);@Value("${netty.socket.port}")private int nettyServerPort;@Value("${netty.websocket.port}")private int nettyWsServerPort;//默认线程池private static ExecutorService executorService = Executors.newFixedThreadPool(2);public static void main(String[] args) {SpringApplication.run(Application.class, args);}@Overridepublic void run(String... args) throws Exception {//启动NettyServer-socketlogger.info("启动NettyServer服务,启动端口:{}", nettyServerPort);NettyServer nettyServer = new NettyServer(new InetSocketAddress(nettyServerPort));Future<Channel> future = executorService.submit(nettyServer);Channel channel = future.get();if (null == channel) {throw new RuntimeException("netty server-s open error channel is null");}while (!channel.isActive()) {logger.info("启动NettyServer服务,循环等待启动...");Thread.sleep(500);}logger.info("启动NettyServer服务,完成:{}", channel.localAddress());CacheUtil.serverInfoMap.put(nettyServerPort, new ServerInfo("NettySocket", NetUtil.getHost(), nettyServerPort, new Date()));//启动NettyServer-websocketlogger.info("启动NettyWsServer服务,启动端口:{}", nettyWsServerPort);WsNettyServer wsNettyServer = new WsNettyServer(new InetSocketAddress(nettyWsServerPort));Future<Channel> wsFuture = executorService.submit(wsNettyServer);Channel wsChannel = wsFuture.get();if (null == wsChannel) {throw new RuntimeException("netty server-ws open error channel is null");}while (!wsChannel.isActive()) {logger.info("启动NettyWsServer服务,循环等待启动...");Thread.sleep(500);}logger.info("启动NettyWsServer服务,完成:{}", wsChannel.localAddress());CacheUtil.serverInfoMap.put(nettyServerPort, new ServerInfo("NettyWsSocket", NetUtil.getHost(), nettyServerPort, new Date()));}}

webapp/arkWs/js/ws.js & 链接websocket服务端

socket = new WebSocket("ws://localhost:7398/websocket");socket.onmessage = function(event){var msg = JSON.parse(event.data);console.info(msg);$("#msgBox").val($("#msgBox").val() + event.data + "\r\n");};

案例测试

  1. 分别启动如下内容;

    1. Application.java,Plugins/spring-boot/spring-boot:run
    2. ApiTest.java,右键启动模拟下位机
  2. 打开服务端链接;http://localhost:8080/ http://localhost:8080/arkWs/arkWsControlCenter.html

  3. 发送模拟信息,观察执行结果;

    2019-12-01 15:11:49.965  INFO 7620 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'2019-12-01 15:11:49.965  INFO 7620 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started2019-12-01 15:11:49.980  INFO 7620 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 15 ms2019-12-01 15:11:51.157  INFO 7620 --- [nio-8080-exec-3] o.itstack.demo.ark.web.NettyController   : 查询设备链接列表。[{"channelId":"281d1279","connectTime":1575184302964,"ip":"127.0.0.1","number":"74de0967-c0b4-4426-a9d1-183feaff2acf","port":3972}]2019-12-01 15:11:51.162  INFO 7620 --- [io-8080-exec-10] o.itstack.demo.ark.web.NettyController   : 查询服务端列表。[{"ip":"10.13.70.50","openDate":1575184290501,"port":7397,"typeInfo":"NettyWsSocket"}]2019-12-01 15:11:58.476  INFO 7620 --- [ntLoopGroup-7-1] o.i.d.a.s.websocket.MyWsServerHandler    : 链接报告开始2019-12-01 15:11:58.476  INFO 7620 --- [ntLoopGroup-7-1] o.i.d.a.s.websocket.MyWsServerHandler    : 链接报告信息:有一客户端链接到本服务端2019-12-01 15:11:58.476  INFO 7620 --- [ntLoopGroup-7-1] o.i.d.a.s.websocket.MyWsServerHandler    : 链接报告IP:0:0:0:0:0:0:0:12019-12-01 15:11:58.476  INFO 7620 --- [ntLoopGroup-7-1] o.i.d.a.s.websocket.MyWsServerHandler    : 链接报告Port:73982019-12-01 15:11:58.476  INFO 7620 --- [ntLoopGroup-7-1] o.i.d.a.s.websocket.MyWsServerHandler    : 链接报告完毕服务端收到:{"channelId":"281d1279","msgType":2,"msgObj":{"stateType":"1"}}2019-12-01 15:12:05 接收到消息内容:{"msgObj":{"stateMsg":"温度信息:91.31334894176383°C","stateType":1,"channelId":"93c1120a"},"msgType":3,"channelId":"93c1120a"}服务端收到:{"channelId":"281d1279","msgType":2,"msgObj":{"stateType":"1"}}2019-12-01 15:12:05 接收到消息内容:{"msgObj":{"stateMsg":"温度信息:44.83794772946285°C","stateType":1,"channelId":"93c1120a"},"msgType":3,"channelId":"93c1120a"}
    

综上总结

  1. 在使用springboot与netty结合,开发一个简便的服务端还是很方便的,而且在集合一些springcloud的服务,可以使项目工程更加完善。
  2. 可以尝试做一些设备控制服务,在我们不在家的时候也可以通过一个h5链接控制家里的设备,比如快到家将热水器打开。
  3. 本案例还偏向于模拟,在实际开发过程中还是会出现很多业务问题需要解决,尤其是服务端与下位机的通信,需要编写编码器与解码器。

websocket与下位机通过netty方式通信传输行为信息相关推荐

  1. 3.2-上位机与下位机的“私有协议”通信构架设计

    一.前言 在上一章中已经介绍了协议设计和封包设计.那么怎样把这些设计优美的落实为代码的形式呢.使用几个函数就可以实现,但是不够优美和实用,因此本章主要介绍一个协议层构架.使用构架的优点如下: 所有协议 ...

  2. Labview通过RS422通讯接口PC与下位机通信

    前言:最近做的一个关于串口通信的小项目:具体功能就是实现PC发送指令控制下位机的转动,下位机收到指令并返回响应指令给上位机,完成一问一答式串口通信.之前处理的是电脑发送数据的格式问题,这次解决的是另外 ...

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

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

  4. 【QT Creator学习记录】(一)上位机与下位机串口通信

    QT实现串口通信步骤以及问题记录,小白文,大佬轻锤,欢迎指错. [串口通信参考文章]这篇更为详细,部分代码是从这扒的 下面是UI界面,主要需求:通过串口或网口方式收发数据,读取下位机状态以及对其进行控 ...

  5. 安卓开发板之串口通信,通过modbus Rtu协议控制下位机

    安卓开发板之串口通信,通过modbus Rtu协议控制下位机 1.环境准备 2.编写串口操作核心类 3.编写测试类 前言:因为公司最近有个人脸识别门禁的项目,这个项目主要业务是实现远程人脸注册,管理员 ...

  6. C#编程简单模拟与下位机的串口通信

    在学校里的团队里,遇到需要与下位机实现串口通信的项目,假期里查了很多资料,在此发一篇加深一下印象 需要: 电脑,vs软件(visual studio 2010) Serial Port Tester( ...

  7. 上位机和MYSQL连接_上位机下位机串口通信设计详解

    串行接口是一种可以将接受来自CPU的并行数据字符转换为连续的串行数据流发送出去,同时可将接受的串行数据流转换为并行的数据字符供给CPU的器件.一般完成这种功能的电路,我们称为串行接口电路. 串口通信结 ...

  8. 下位机和上位机通信 学习体会心得

    使用 sim 卡的  下位机 所处的环境是在 移动/联通   的局域网中 上位机 处于 电信 即 互联网中 想要将 包  放到我们的电脑上即上位机  ,必须通过  移动/联通 的网关 发送到 电信的网 ...

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

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

  10. 【CNC——第6篇】PMAC上位机编程基础篇(上位机和下位机如何通信)

    拓展链接: PAMC官网:DELTA TAU. 官网手册:手册大全 PMAC官网: PCOMM32PRO用户手册 PMAC 的内部变量 内部变量分为四种,I 变量为电机等常用基本控制变量,P 变量为全 ...

最新文章

  1. 第十七篇:信息抽取Information Extraction
  2. 鸿蒙系统第五批公测,鸿蒙系统即将迎来二轮公测,nova机型增添其中
  3. 看门狗寄存器c语言代码_「正点原子NANO STM32F103开发板资料连载」第十一章 看门狗实验...
  4. ElementUI项目优化上线
  5. java质因数的分解_Java实现分解任意输入数的质因数算法示例
  6. Fintech生态报告:区块链是金融业革新的王牌技术
  7. Network in Network 算法解析
  8. oozie mysql5.6_oozie服务安装及使用
  9. 正定矩阵(Positive Definite Matrices)、半正定矩阵(Positive Semidefinite Matrices)
  10. RainMeter学习3
  11. Ubuntu18.04 笔记本合上盖子时不进入休眠
  12. 【笔记】识别手写数字神经网络
  13. Linux安装nvm的方法
  14. vs2017支持所有版本VC
  15. 全球仅10%的开发者从不加班;淘宝回应用户账号被禁用 980 年;苹果宣布首次以官方形式参与天猫618折扣活动...
  16. PHP网络在线学习云课堂源码在线教育学习云课堂源码
  17. 世界各地主流,非主流搜索引擎以及功能性搜索引擎一览
  18. 社区电商平台运营中常遇到的一些问题
  19. 手写SHA1加密算法(Java版)
  20. MySQL由于找不到msvcp140.dll无法继续执行代码

热门文章

  1. Android串口通信之概念介绍
  2. JVM虚拟机基础知识(JVM位置、类加载生命周期、堆、元空间、jvm常用参数)
  3. uc_pass.php,discuz 论坛UCenter无法登录,闪退的终极8种解决办法
  4. 吴伯凡-认知方法论-给思维一个支点
  5. 【概率论与数理统计(研究生课程)】知识点总结6(抽样分布)
  6. 复杂性研究面临的难题
  7. 20172301 2017-2018-2 《程序设计与数据结构》第六周学习总结
  8. Metric study
  9. 【IP SSL】内网IP SSL证书Nginx部署
  10. c语言指数公式_c语言指数函数(c语言如何表示指数函数)