前言

最近做一个项目:

  1. 大概需求: 多个温度传感器不断向java服务发送温度数据,该传感器采用socket发送数据;该数据以$符号开头和结尾,最后将处理的数据存入数据库;
  2. 我想到的处理方式:采用netty来接收和处理数据,然后用mybatis将处理后的数据存入数据库;

我在这之前从来没使用过netty,在网上倒是看到不少关于netty的文章,如今就趁着这个项目写一下我所学到的东西和遇到的问题,又是怎么去解决的;

接下来的几篇文章都是围绕着这个项目来写的;本篇主要写netty的入门demo;

正文

代码部分

新建一个maven项目

首先在pom.xml中导入:

 <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha1</version>
</dependency>

服务端

1. DiscardServer类,netty的服务端

public class DiscardServer {
public void run(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
System.out.println("准备运行端口:" + port);
try {
ServerBootstrap b = new ServerBootstrap();
b = b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childHandler(new ChildChannelHandler());
//绑定端口,同步等待成功
ChannelFuture f = b.bind(port).sync();
//等待服务监听端口关闭
f.channel().closeFuture().sync();
} finally {
//退出,释放线程资源
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new DiscardServer().run(8080);
}
}

2. ChildChannelHandler类:


public class ChildChannelHandler extends ChannelInitializer<SocketChannel> { protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new DiscardServerHandler());
}
}

3. DiscardServerHandler类

在这里是继承的ChannelHandlerAdapter类,当然还可以继承其他的类,例如SimpleChannelInboundHandler,ChannelInboundHandlerAdapter都可以

public class DiscardServerHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { try {
ByteBuf in = (ByteBuf) msg;
System.out.println("传输内容是");
System.out.println(in.toString(CharsetUtil.UTF_8));
ByteBuf resp= Unpooled.copiedBuffer("收到信息$".getBytes());
ctx.writeAndFlush(resp);
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 出现异常就关闭
cause.printStackTrace();
ctx.close();
} }

启动netty服务;

好了,到这里就能开始接收数据了;

客服端

1.TimeClient类

public class TimeClient {
public void connect(int port,String host)throws Exception{
//配置客户端
System.out.println(port+"--"+host);
EventLoopGroup eventLoopGroup=new NioEventLoopGroup();
try {
Bootstrap b=new Bootstrap();
b.group(eventLoopGroup).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY,true)
.handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new TimeClientHandler());
}
});
//绑定端口,同步等待成功
ChannelFuture f = b.connect(host,port).sync();
//等待服务监听端口关闭
f.channel().closeFuture().sync();
}finally {
//优雅退出,释放线程资源
eventLoopGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new TimeClient().connect(8090,"localhost");
}
}

2.TimeClientHandler 类

public class TimeClientHandler extends ChannelHandlerAdapter {
private byte[] req;
public TimeClientHandler(){
req="$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$".getBytes();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf message=null;
for(int i=0;i<100;i++){
message=Unpooled.buffer(req.length);
message.writeBytes(req);
ctx.writeAndFlush(message);
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
ByteBuf in = (ByteBuf) msg;
System.out.println(in.toString(CharsetUtil.UTF_8));
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 出现异常就关闭
cause.printStackTrace();
ctx.close();
} }

在channelActive类中向服务端发送100次消息

先启动服务端,再启动客户端;

测试结果一:

服务端:

传输内容是
$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$$tmb00035ET3318/08/22 11:5704026.7
传输内容是
5,027.31,20.00,20.00$$tmb00035ET3318/08/22

客户端:

8080--localhost
收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息收到信息

由于内容太多,就不都贴出来了j,直接写结果吧:

  1. 客户端发送100次数据,但是服务端只收到了28次,然后服务端向客户端返回28次数据,客户端却只收到一次;
  2. 可以发现服务端接收的数据不是完整接收的,这里出现了拆包,粘包的问题

这里就不讨论拆包,粘包了,百度一大堆,相信你也能看明白;

解决粘包,拆包的问题

解决拆包粘包的方法有很多:

  1. 消息定长,固定每个消息的固定长度
  2. 在消息末尾使用换行符对消息进行分割,或者使用其他特殊字符来对消息进行分割;
  3. 将消息分为消息头和消息体,消息头中包含标识消息总长度;
  4. 更复杂的,或者其他的协议。

由于我负责的这个项目户端发送是由$开始和结束的数据,返回的数据我也设置的$结束,所以我选择了第二种方法;

只需要在服务端的DiscardServerHandler中和客户端的ChannelInitializer中添加几行相同的代码就行了;

服务端:

public class ChildChannelHandler extends ChannelInitializer<SocketChannel> {

    protected void initChannel(SocketChannel socketChannel) throws Exception {
ByteBuf byteBuf= Unpooled.copiedBuffer("$".getBytes());
socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,byteBuf));
socketChannel.pipeline().addLast(new DiscardServerHandler());
}
}

客户端:

在如下的位置添加如下的代码:

 .handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ByteBuf byteBuf= Unpooled.copiedBuffer("$".getBytes());
socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,byteBuf));
socketChannel.pipeline().addLast(new TimeClientHandler());
}
});

测试结果

这里我就不发送100次数据了,值发送10次:

服务端:

传输内容是
tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00
传输内容是
tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00
传输内容是
tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00
传输内容是
tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00
传输内容是
tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00
传输内容是
tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00
传输内容是
tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00
传输内容是
tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00
传输内容是
tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00
传输内容是
tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00

客户端:

收到信息
收到信息
收到信息
收到信息
收到信息
收到信息
收到信息
收到信息
收到信息
收到信息

解决我所遇到的问题了;

总结

  1. 本来我只需要写服务端的代码的,但是为了更好的演示,所以我写了客户端
  2. 本篇文章主要就是使用netty发送和接收数据,还有就是拆包和粘包的问题,当然,netty还可以做其他很多的事情;
  3. netty针对对拆包粘包的问题有很多种解决办法:例如可以用LineBasedFrameDecoder和StringDecoder组合将信息已换行符来进行拆分;也可以用我上边的解决方法来解决以特殊字符结束的信息;
  4. 在解决拆包粘包信息的时候,注意信息是否符合定义的规则,不然会处理不了数据:例如我上边的例子,如果服务端在返回信息是不以$符结尾的话,客户端是打印不出来信息的,因为客户端会认为服务端还没有发送完信息,会一直等待,而且打印不出数据;
  5. 这篇文章只是我入门netty的一个小demo,对我还是很有帮助的,当然也希望对阅读者有那么一点点帮助;
  6. 有什么不对的地方还请指正,建议也是多多益善;
  7. 源码地址

netty入门demo(一)的更多相关文章

  1. Netty入门二:开发第一个Netty应用程序

    Netty入门二:开发第一个Netty应用程序 时间 2014-05-07 18:25:43  CSDN博客 原文  http://blog.csdn.net/suifeng3051/article/ ...

  2. Netty入门与实战教程总结分享

    前言:都说Netty是Java程序员必须要掌握的一项技能,带着不止要知其然还要知其所以然的目的,在慕课上找了一个学习Netty源码的教程,看了几章后着实有点懵逼.虽然用过Netty,并且在自己的个人网 ...

  3. Netty入门之客户端与服务端通信(二)

    Netty入门之客户端与服务端通信(二) 一.简介 在上一篇博文中笔者写了关于Netty入门级的Hello World程序.书接上回,本博文是关于客户端与服务端的通信,感觉也没什么好说的了,直接上代码 ...

  4. Netty入门之HelloWorld

    Netty系列入门之HelloWorld(一) 一. 简介 Netty is a NIO client server framework which enables quick and easy de ...

  5. 【SSH系列】初识spring+入门demo

    学习过了hibernate,也就是冬天,经过一个冬天的冬眠,当春风吹绿大地,万物复苏,我们迎来了spring,在前面的一系列博文中,小编介绍hibernate的相关知识,接下来的博文中,小编将继续介绍 ...

  6. Netty入门

    一.NIO Netty框架底层是对NIO的高度封装,所以想要更好的学习Netty之前,应先了解下什么是NIO - NIO是non-blocking的简称,在jdk1.4 里提供的新api,他的他的特性 ...

  7. 基于springboot构建dubbo的入门demo

    之前记录了构建dubbo入门demo所需的环境以及基于普通maven项目构建dubbo的入门案例,今天记录在这些的基础上基于springboot来构建dubbo的入门demo:众所周知,springb ...

  8. apollo入门demo实战(二)

    1. apollo入门demo实战(二) 1.1. 下载demo 从下列地址下载官方脚本和官方代码 https://github.com/nobodyiam/apollo-build-scripts ...

  9. netty入门(一)

    1. netty入门(一) 1.1. 传统socket编程 在任何时候都可能有大量的线程处于休眠状态,只是等待输入或者输出数据就绪,这可能算是一种资源浪费. 需要为每个线程的调用栈都分配内存,其默认值 ...

随机推荐

  1. ZJOI2019二试游记

    ZJOI2019二试游记 Day -2 今天就要去被虐了!开一篇占个坑.禁赛警告 Day -1 早上zzy,下午zzq,无限懵逼... 过来的时候Sooke,memset0,老K坐我旁边,瑟瑟发抖.. ...

  2. FFmpeg 学习(七):FFmpeg 学习整理总结

    一.FFmpeg 播放视频的基本流程整理 播放流程: video.avi(Container) -> 打开得到 Video_Stream -> 读取Packet -> 解析到 Fra ...

  3. 基于kNN的手写字体识别——《机器学习实战》笔记

    看完一节<机器学习实战>,算是踏入ML的大门了吧!这里就详细讲一下一个demo:使用kNN算法实现手写字体的简单识别 kNN 先简单介绍一下kNN,就是所谓的K-近邻算法: [作用原理]: ...

  4. Java学习图

  5. HTTP 协议常见首部字段

    首部字段 1.HTTP协议的请求和响应报文中必定包含HTTP首部.首部内容为客户端和服务器处理请求和响应提供了所必须的信息. 2.HTTP首部字段是由首部字段名和字段值构成,中间用冒号“:”隔开.字段 ...

  6. H5新增属性contenteditable(不用JS,实现div模拟textarea高度自增长)

    无意中看到一篇博客介绍了contenteditable这个属性——不需用JS,只需在div里加上contenteditable="true",即可实现div模拟textrarea( ...

  7. os模块及其API&属性

    模块: os os.path 所包含API列表: os.uname: 获取详细的系统信息 os.rename: 文件重命名 os.remove: 删掉文件 os.mkdir: 创建一个目录 os.rm ...

  8. JSON库的使用研究(二)

    Java 中哪个 JSON 库的解析速度是最快的? 这个问题有意义吗?各个JSON库的性能差距不大?呵呵,差距大不大,自己往下看吧! 这个问题我们应该分为以下四个维度进行研究: 1.序列化 2.反序列 ...

  9. 8.python面向对象编程

    基本概念 Class 类一个类即是对一类拥有相同属性的对象的抽象.蓝图.原型.在类中定义了这些对象的都具备的属性(variables(data)).共同的方法 Object 对象 一个对象即是一个类的 ...

  10. Django使用Channels实现WebSocket--下篇

    希望通过对这两篇文章的学习,能够对Channels有更加深入的了解,使用起来得心应手游刃有余 通过上一篇<Django使用Channels实现WebSocket--上篇>的学习应该对Cha ...