这边采用的是4.*版本的netty 稳定
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.25.Final</version>
</dependency>
1.我们这边搭建的是Netty的服务端
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.AdaptiveRecvByteBufAllocator;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @author KeShuai
* @Auther: KeShuai
* @Date: 2020/08/11/10:41
* @Description:
*/
public class BootNettyServer {
public void bind(int port) throws Exception {
/**
* 配置服务端的NIO线程组
* NioEventLoopGroup 是用来处理I/O操作的Reactor线程组
* bossGroup:用来接收进来的连接,workerGroup:用来处理已经被接收的连接,进行socketChannel的网络读写,
* bossGroup接收到连接后就会把连接信息注册到workerGroup
* workerGroup的EventLoopGroup默认的线程数是CPU核数的二倍
*/
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
/**
* ServerBootstrap 是一个启动NIO服务的辅助启动类
*/
ServerBootstrap serverBootstrap = new ServerBootstrap();
/**
* 设置group,将bossGroup, workerGroup线程组传递到ServerBootstrap
*/
serverBootstrap = serverBootstrap.group(bossGroup, workerGroup);
/**
* ServerSocketChannel是以NIO的selector为基础进行实现的,用来接收新的连接,这里告诉Channel通过NioServerSocketChannel获取新的连接
*/
serverBootstrap = serverBootstrap.channel(NioServerSocketChannel.class);
/**
* option是设置 bossGroup,childOption是设置workerGroup
* netty 默认数据包传输大小为1024字节, 设置它可以自动调整下一次缓冲区建立时分配的空间大小,避免内存的浪费 最小 初始化 最大 (根据生产环境实际情况来定)
* 使用对象池,重用缓冲区
*/
serverBootstrap = serverBootstrap.option(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(64, 10496, 6291456));
serverBootstrap = serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(64, 10496, 6291456));
/**
* 设置 I/O处理类,主要用于网络I/O事件,记录日志,编码、解码消息
*/
serverBootstrap = serverBootstrap.childHandler(new BootNettyChannelInitializer<SocketChannel>());
System.out.println("netty server start success!");
/**
* 绑定端口,同步等待成功
*/
ChannelFuture f = serverBootstrap.bind(port).sync();
ChannelFuture f1 = serverBootstrap.bind(8778).sync();
if (f.isSuccess()) {
System.out.println("启动 8777 成功");
}
if (f1.isSuccess()) {
System.out.println("启动 8778 成功");
}
/**
* 等待服务器监听端口关闭
*/
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
} finally {
/**
* 退出,释放线程池资源
*/
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
这边加入netty的编码器进行数据加解码操作,我们这边定义的是用分隔符进行对报文解析
//加入分隔符解码器(结束符)
ByteBuf delimiter = Unpooled.copiedBuffer("_$".getBytes());
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(102410246,delimiter));
因为共享硬件发送的报文全量数据太长 这边客户端定义了个分隔符 服务端对检测到的分隔符 裁剪取出全量数据。不然会产生TCP黏包拆包的问题 可以百度一下TCP黏包拆包。
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
/**
* @Auther: KeShuai
* @Date: 2020/08/11/10:42
* @Description:
*/
public class BootNettyChannelInitializer<SocketChannel> extends ChannelInitializer<Channel> {
@Override
protected void initChannel(Channel ch) throws Exception {
//加入分隔符解码器(结束符)
ByteBuf delimiter = Unpooled.copiedBuffer("_$".getBytes());
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024*1024*6,delimiter));
//加载 解码数据
ch.pipeline().addLast("encoder", new StringEncoder());
// 属于ChannelInboundHandler,依照顺序执行
ch.pipeline().addLast("decoder", new StringDecoder());
/**
* 自定义ChannelInboundHandlerAdapter
*/
ch.pipeline().addLast(new BootNettyChannelInboundHandlerAdapter());
}
/**
* 自定义ChannelInboundHandlerAdapter
*/
ch.pipeline().addLast(new BootNettyChannelInboundHandlerAdapter());
上图解码器这块就是加入处理器
刚开始搭建主要是用端口号区分是电脑发送 还是设备发送
采用String接收解析成JSON 在对设备发送的报文命令解析自动回复发送代码,或者转发给电脑端 。
电脑端发送给设备端命令开关区分
以下是处理器代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.gson.JsonArray;
import com.qykfa.dto.AttrListDto;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import net.sf.json.JsonConfig;
/**
* @author KeShuai
* @Auther: KeShuai
* @Date: 2020/08/11/10:44
* @Description:
*/
public class BootNettyChannelInboundHandlerAdapter extends ChannelInboundHandlerAdapter {
/**
* 存链接客户端的map
*/
public static Map<String, ChannelHandlerContext> map = new HashMap<String, ChannelHandlerContext>();
public static String dnFlag = "8778";
public static String sbFlag = "8777";
/**
* 从客户端收到新的数据时,这个方法会在收到消息时被调用
*
* @param ctx
* @param msg
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception, IOException
{
//获取连接的端口号
String servicePortSub=ctx.channel().localAddress().toString();
String servicePort=servicePortSub.substring(servicePortSub.length()-4, servicePortSub.length());
//设备->电脑
if (sbFlag.equals(servicePort)){
//对数据解析代码 例子
// JSONObject json = new JSONObject();
// json.put("oject",msg);
// System.out.println(json);
// if (json.getJSONObject("oject").get("msgType")!=null){
// System.out.println(json.getJSONObject("oject").get("msgType"));
// JSONArray array = json.getJSONObject("oject").getJSONArray("attrList");
// System.out.println(json.getJSONObject("oject").getJSONArray("attrList").size());
// List list = JSONArray.toList(array, new AttrListDto(), new JsonConfig());
// System.out.println("list :" + list.size());
//
// int num = 1;
// for (AttrListDto dto:list) {
//
// System.out.println(num + " ssss "+dto.getId());
//
// num++;
// }
// }
map.get("用户标识对应").channel().writeAndFlush(msg);
}
//电脑->设备
if (dnFlag.equals(servicePort)){
//设备接收需要JSON格式
JSONObject json= JSONObject.fromObject(msg);
map.get("用户标识对应").channel().writeAndFlush(json.toString());
}
}
/**
* 从客户端收到新的数据、读取完成时调用
*
* @param ctx
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws IOException
{
System.out.println("channelReadComplete");
ctx.flush();
}
/**
* 当出现 Throwable 对象才会被调用,即当 Netty 由于 IO 错误或者处理器在处理事件时抛出的异常时
*
* @param ctx
* @param cause
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws IOException
{
System.out.println("exceptionCaught");
cause.printStackTrace();
ctx.close();//抛出异常,断开与客户端的连接
}
/**
* 客户端与服务端第一次建立连接时 执行
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception, IOException
{
super.channelActive(ctx);
ctx.channel().read();
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = insocket.getAddress().getHostAddress();
String servicePortSub=ctx.channel().localAddress().toString();
String servicePort=servicePortSub.substring(servicePortSub.length()-4, servicePortSub.length());
//获取客户端的请求地址 取到的值为客户端的 ip+端口号
//设备请求地址(个人将设备的请求地址当作 map 的key)
String url=ctx.channel().remoteAddress().toString();
System.out.println(url);
//如果不为空就不存
if(map.get(clientIp)!=null){
}else{//否则就将当前的设备ip+端口存进map
map.put(clientIp+servicePort, ctx);
}
//此处不能使用ctx.close(),否则客户端始终无法与服务端建立连接
System.out.println("channelActive:"+clientIp+servicePort);
}
/**
* 客户端与服务端 断连时 执行
*
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception, IOException
{
super.channelInactive(ctx);
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = insocket.getAddress().getHostAddress();
ctx.close(); //断开连接时,必须关闭,否则造成资源浪费,并发量很大情况下可能造成宕机
System.out.println("channelInactive:"+clientIp);
}
/**
* 服务端当read超时, 会调用这个方法
*
* @param ctx
* @param evt
* @throws Exception
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception, IOException
{
super.userEventTriggered(ctx, evt);
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = insocket.getAddress().getHostAddress();
ctx.close();//超时时断开连接
System.out.println("userEventTriggered:"+clientIp);
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception{
System.out.println("channelRegistered");
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception{
System.out.println("channelUnregistered");
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception{
System.out.println("channelWritabilityChanged");
}
public String convertByteBufToString(ByteBuf buf) {
String str;
if(buf.hasArray()) { // 处理堆缓冲区
str = new String(buf.array(), buf.arrayOffset() + buf.readerIndex(), buf.readableBytes());
} else { // 处理直接缓冲区以及复合缓冲区
byte[] bytes = new byte[buf.readableBytes()];
buf.getBytes(buf.readerIndex(), bytes);
str = new String(bytes, 0, buf.readableBytes());
}
return str;
}
}
@SpringBootApplication
@EnableAsync
public class SocketServerApplication extends SpringBootServletInitializer implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(SocketServerApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SocketServerApplication.class);
}
@Async
@Override
public void run(String... args) throws Exception {
/**
* 使用异步注解方式启动netty服务端服务
*/
new BootNettyServer().bind(8777);
}
}
以上是Netty服务器 初始化搭建 第一次接触硬件4G远程通信这块 代码比较初略 可能考虑的还不够周全后续还在更改,还望大家可以指出错误或者有更好的搭建方法一起探讨。后续还有手机 Websocket链接进来这块加上 工厂 策略设计模式,netty+kafka实时等
下文 :SpringBoot+Netty 结合物联网通信 加入WebSocket协议(2)