前言

最近做一个项目:

  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. 13.缓存、三级缓存、内存溢出、AsyncTask

    SharePreference工具类 /** * SharePreference封装 * */ public class PrefUtils { public static final String ...

  2. 微博第三方登录使用social_django实现显示登陆的用户名

    首先修改social_soce源码,将用户信息添加进cookie 将其修改为:  response =  backend.strategy.redirect(url)    payload = jwt ...

  3. Kali学习笔记35:使用VBScript、PowerShell、DEBUG传输文件

    其实VBScript只是一个工具 本质是开启http服务提供下载的 首先我们开启http服务:阿帕奇 然后给上传一个文件做演示: 接下来就是编写VBScript: 这些内容全部输入完成之后: 传输 早 ...

  4. hdf 5文件格式及python中利用h5py模块读写h5文件

    h5文件格式,HDF 的版本 5(HDF 版本 5不与 HDF 版本 4 及早期版本兼容).HDF是什么呢?就是Hierarchical Data Format,可以存储不同类型的图像和数码数据的文件 ...

  5. VIM -小技巧汇总

    一 代码自动对齐: 1 命令行模式下先输入gg 然后按=后输入大写的G,这样整个文件的代码就对齐了. 二 选中多行缩进 1.按v进入visual状态,选择多行,用>或<缩进或缩出 2. 通 ...

  6. java中Base64的加密工具封装

    Base64加密作为最简单普遍的加密方式(其实只能称为编码方式),应用场景众多比如秘钥,安全证书,也应用在其他的加密方式中或与其他加密方式进行嵌套使用 可以通过引用sun.misc来使用,也可以自己手 ...

  7. redis安装以及安全配置

    redis安装以及安全配置 1. 安装 sudo apt-get install redis-server 使用which查询redis执行体安装路径: which redis-server #/us ...

  8. 纯 Html 5的 简单 TreeView 保存下思路以后有机会再完善。

    details p{margin-left:30px;}details.root details{margin-left:13px;} </style> <details class ...

  9. postgresql数据库删除时提示回话 sessions using the database

    数据库命令行或者管理工具中执行删除数据库的命令, DROP DATABASE testdb; 的时候,可能会提示: ERROR: database "testdb" is bein ...

  10. Java核心技术及面试指南 异常部分的面试题归纳以及答案

    4.2.4.1 throw和throws有什么差别?异常(Exception)和错误(Error)有什么差别? throw语句表示抛出异常,由方法体内的语句处理.throws语句用在方法声明后面,表示 ...