前言

最近做一个项目:

  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. UnityTips:使用反射调用内部方法拓展编辑器

    大家都知道Unity是一个C/C++的游戏引擎,C#只是Unity提供的脚本层.因此大部分功能都是通过C#来调用底层的C++代码的.而一些朋友可能不知道的是,其实Unity的C#代码中也有很多方法是我 ...

  2. 第54节:Java当中的IO流(中)

    Java当中的IO流(中) 删除目录 // 简书作者:达叔小生 import java.io.File; public class Demo{ public static void main(Stri ...

  3. dubbo实用知识点总结(三)

    1. 服务降级 2. 优雅停机 3. 主机绑定 4. 访问日志 5. Multicast注册中心 6. zookeeper注册中心 7. 推荐用法 8. 容量规划 9. 基准测试工具包

  4. PHP中的自动加载

      自动加载? 或许你已经对自动加载有所了解.简单描述一下:自动加载就是我们在new一个class的时候,不需要手动去写require来导入这个class.php文件,程序自动帮我们加载导入进来.这是 ...

  5. Python编程Day4——if判断、while循环、for循环

    一.if判断 语法一: if条件: 代码块1 代码块2 代码块3 示例: sex='female' age=18 is_beautiful=True if sex =='female'and age& ...

  6. PythonDay02——编程语言、python介绍以及安装解释器、运行程序的两种方式、变量

    一.编程语言 1.1 机器语言:直接用计算机能理解的二进制指令编写程序,直接控制硬件 1.2 汇编语言:用英文标签取代二进制指令去编写程序,本质也是直接控制硬件 1.3 高级语言:用人能理解的表达方式 ...

  7. tensorflow 1.0 学习:用CNN进行图像分类

    tensorflow升级到1.0之后,增加了一些高级模块: 如tf.layers, tf.metrics, 和tf.losses,使得代码稍微有些简化. 任务:花卉分类 版本:tensorflow 1 ...

  8. Struts标签<bean:write><logic:iterate></logic:equal>的组合使用小例

    form表单中的一个下拉列表控件的代码如下 <select name="taskname" id="taskname" class="selec ...

  9. mstsc远程报:这可能是由于CredSSP 加密Oracle修正的两种完美解决方法

    win10很完美,用的也很舒服!当然人无完人,也总有不尽如人意的时候.比如说我们经常用的远程mstsc,就出现了一个坑,既然出现坑了,我们就得把坑解决掉吧!下面就记录一下这个坑的解决方法. 本文地址: ...

  10. C++版 - 剑指offer 面试题31:连续子数组的最大和 题解

    剑指offer:连续子数组的最大和 提交网址: http://www.nowcoder.com/practice/459bd355da1549fa8a49e350bf3df484?tpId=13&am ...