Springboot+Netty搭建基于TCP协议的服务端

文章目录

    • 概要
    • pom依赖
    • Netty的server服务端类
    • Netty通道初始化
    • I/O数据读写处理
    • 测试发送消息 并 接收服务端回复
    • 异步启动Netty
    • 运行截图

概要

Netty是业界最流行的nio框架之一,它具有功能强大、性能优异、可定制性和可扩展性的优点
Netty的优点:
1.API使用简单,开发入门门槛低。
2.功能十分强大,预置多种编码解码功能,支持多种主流协议。
3.可定制、可扩展能力强,可以通过其提供的ChannelHandler进行灵活的扩展。
4.性能优异,特别在综合性能上的优异性。
5.成熟,稳定,适用范围广。
6.可用于智能GSM/GPRS模块的通讯服务端开发,使用它进行MQTT协议的开发。

好了,废话不多说了,上代码

pom依赖

        <!-- netty依赖 springboot2.x自动导入版本 --><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId></dependency>

Netty的server服务端类

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.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** Netty服务器(端口自行更换,默认端口10100)* @author wusiwee*/
@Service
@Slf4j
public class NettyServer {/*** 注入Netty通道初始化处理器*/private final NettyChannelInboundHandlerAdapter handlerAdapter;/*** 通过构造函数注入依赖* @param handlerAdapter 处理器*/@Autowiredpublic NettyServer(NettyChannelInboundHandlerAdapter handlerAdapter) {this.handlerAdapter = handlerAdapter;}/*** 启动Netty服务器* @throws Exception 如果启动过程中发生异常*/public void bind() throws Exception {// 定义bossGroup和workerGroup来处理网络事件// 用于接受客户端连接EventLoopGroup bossGroup = new NioEventLoopGroup(1);// 用于实际的业务处理操作EventLoopGroup workerGroup = new NioEventLoopGroup();try {// 创建ServerBootstrap实例来引导绑定和启动服务器ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(bossGroup, workerGroup)// 指定使用NIO的传输Channel.channel(NioServerSocketChannel.class)// 设置TCP接收缓冲区大小.option(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(64, 10496, 1048576)).childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(64, 10496, 1048576))// 设置自定义的Channel初始化器.childHandler(new NettyChannelInitializer(handlerAdapter));log.info("netty server start success!");// 绑定端口,并同步等待成功,即启动Netty服务ChannelFuture f = serverBootstrap.bind(10100).sync();// 等待服务端监听端口关闭f.channel().closeFuture().sync();} catch (InterruptedException e) {log.error("Netty server startup interrupted", e);Thread.currentThread().interrupt();} finally {// 优雅关闭事件循环组bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}

Netty通道初始化

import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.string.StringEncoder;
import org.springframework.stereotype.Component;/*** 通道初始化* @author wusiwee*/
@Component
public class NettyChannelInitializer<SocketChannel> extends ChannelInitializer<Channel> {/*** 注入,目的是在该 HandlerAdapter 可以正确的注入业务Service*/private final NettyChannelInboundHandlerAdapter handlerAdapter;public NettyChannelInitializer(NettyChannelInboundHandlerAdapter handlerAdapter) {this.handlerAdapter = handlerAdapter;}@Overrideprotected void initChannel(Channel ch) {ChannelPipeline pipeline = ch.pipeline();// 响应字符串pipeline.addLast(new StringEncoder());// 自定义ChannelInboundHandlerAdapterpipeline.addLast(handlerAdapter);}

I/O数据读写处理

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.time.LocalDateTime;
import java.util.Date;/*** I/O数据读写处理类*  客户端发送的消息 以及 回复客户端消息 均在此处*  @ChannelHandler.Sharable 此注解用于在多个 Channel 中重复使用同一个 Handler 实例* @author wusiwee*/
@Slf4j
@ChannelHandler.Sharable
@Component
public class NettyChannelInboundHandlerAdapter extends ChannelInboundHandlerAdapter{/*** 这里可以注入自己的service*/@Autowiredprivate IUserService iUserService;/*** 从客户端收到新的数据时,这个方法会在收到消息时被调用*/@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {ByteBuf in = (ByteBuf) msg;// 确保接收的数据长度足够,minimumLength 是所有字段长度的总和if (in.readableBytes() < MINIMUM_LENGTH) {ctx.writeAndFlush("报文长度过低,数据不完整"+"\n");return;}// 1,读取固定长度字符byte[] frameStart = new byte[4];in.readBytes(frameStart);String frameStartStr = new String(frameStart, java.nio.charset.StandardCharsets.UTF_8);log.info("1.解析:"+frameStartStr);ctx.writeAndFlush("I got it\n");}/*** 从客户端收到新的数据、读取完成时调用*/@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) {log.info("读取完成 channelReadComplete");ctx.flush();}/*** 当出现 Throwable 对象才会被调用,即当 Netty 由于 IO 错误或者处理器在处理事件时抛出的异常时*/@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {log.info("exceptionCaught");cause.printStackTrace();//抛出异常,断开与客户端的连接ctx.close();}/*** 客户端与服务端第一次建立连接时 执行*/@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {super.channelActive(ctx);ctx.channel().read();InetSocketAddress inSocket = (InetSocketAddress) ctx.channel().remoteAddress();String clientIp = inSocket.getAddress().getHostAddress();//此处不能使用ctx.close(),否则客户端始终无法与服务端建立连接log.info("客户端连接 channelActive{}", clientIp+" "+ctx.name());}/*** 客户端与服务端 断连时 执行*/@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {super.channelInactive(ctx);InetSocketAddress inSocket = (InetSocketAddress) ctx.channel().remoteAddress();String clientIp = inSocket.getAddress().getHostAddress();//断开连接时,必须关闭,否则造成资源浪费,并发量很大情况下可能造成宕机ctx.close();log.info("channelInactive{}", clientIp);}/*** 服务端当read超时, 会调用这个方法*/@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {super.userEventTriggered(ctx, evt);InetSocketAddress inSocket = (InetSocketAddress) ctx.channel().remoteAddress();String clientIp = inSocket.getAddress().getHostAddress();//超时 断开连接ctx.close();log.info("userEventTriggered{}", clientIp);}@Overridepublic void channelRegistered(ChannelHandlerContext ctx) {log.info("注册 channelRegistered");}@Overridepublic void channelUnregistered(ChannelHandlerContext ctx) {log.info("channelUnregistered");}@Overridepublic void channelWritabilityChanged(ChannelHandlerContext ctx) {log.info("channelWritabilityChanged");}
}

测试发送消息 并 接收服务端回复

@Testvoid contextLoads() {try {// 服务器地址String host = "127.0.0.1";// 服务器端口int port = 10100;// 要发送的消息String message = "7E7E010038401010123433004D02000B22";Socket socket = new Socket(host, port);// 获取输出流OutputStream outputStream = socket.getOutputStream();// 将字符串转换为字节数组byte[] data = message.getBytes();// 写入数据到输出流outputStream.write(data);// 刷新输出流,确保数据发送outputStream.flush();InputStream input = socket.getInputStream();//读取服务器返回的消息BufferedReader br = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));String mess = br.readLine();System.out.println("服务器回复:" + mess);input.close();outputStream.close();socket.close();}catch (Exception e){System.out.println("出现异常");}}

异步启动Netty

import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;/*** 启动类*/
@SpringBootApplication
@EnableAsync
public class NettyApplication implements ApplicationRunner{/*** 启动springboot*/public static void main( String[] args ) {SpringApplication.run(NettyApplication.class, args);}/*** 创建独立线程池*/private final ExecutorService executorService = new ThreadPoolExecutor(1, 1, 30, TimeUnit.SECONDS,new LinkedBlockingDeque<>(2),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());/*** 注入Netty消息处理器*/@Resourceprivate NettyChannelInboundHandlerAdapter handlerAdapter;@Overridepublic void run(ApplicationArguments args) throws Exception {// 使用线程池 异步启动Netty服务器executorService.submit(() -> {try {// 启动netty,绑定端口号new NettyServer(handlerAdapter).bind();} catch (Exception e) {// 异常处理System.out.println("启动netty出现异常:"+e.getMessage());}});}
}

运行截图

启动服务
回复客户端消息的代码片段
消息回复
测试发送
发送测试
客户端收到回复,断开连接
在这里插入图片描述

攀峰之高险,岂有崖颠;搏海之明辉,何来彼岸?前进不止,奋斗不息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/438207.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

超值福利,全是独家特制版软件,功能超凡且完全免费

闲话休提&#xff0c;直接为您呈现四款神仙级别的软件。 1、我的ABC软件工具箱 这款小巧而强大的批量处理办公助手——我的ABC软件工具箱&#xff0c;不仅界面清爽、无弹窗广告&#xff0c;更重要的是&#xff0c;它完全免费&#xff01;这款工具箱将成为您高效办公的得力助手…

【前端web入门第二天】02 表单-input标签-单选框-多选框

表单 文章目录: 1.input标签基本使用 1.1 input标签占位文本1.2 单选框 radio 1.3 多选框 checkbox 作用:收集用户信息。 使用场景: 登录页面注册页面搜索区域 1.input标签基本使用 input标签type属性值不同&#xff0c;则功能不同。 <input type"..."&g…

媒体邀约:怎么吸引总体目标受众?

新闻媒体影响力日益扩大。不论是公司、机构还是其他&#xff0c;都希望能够通过新闻媒体的曝光来吸引更多总体目标受众。要想真正吸引住总体目标受众并非易事&#xff0c;需要一定的方案和方法。下面我们就深入探究媒体邀约推广的真相&#xff0c;共享怎么吸引总体目标受众的方…

课时7:shell基础_shell简介

1.3.1 shell简介 学习目标 这一节&#xff0c;我们从 运维、shell语言、小结 三个方面来学习。 运维 简介 运维是什么&#xff1f;所谓的运维&#xff0c;其实就是公司的内部项目当中的一个技术岗位而已&#xff0c;它主要做的是项目的维护性工作。它所涉及的内容范围非常…

Python判断语句——if语句的基本格式

一、引言 在Python编程语言中&#xff0c;if语句是一种基本的控制流语句&#xff0c;用于根据特定条件执行不同的代码块。它的基本格式相对简单&#xff0c;使得Python代码清晰、易于阅读。下面&#xff0c;我们将深入探讨if语句的基本格式、用法和注意事项。 二、if语句的…

Apipost中API如何调用本地文件

近期版本更新中Apipost推出插件管理&#xff0c;可以直接在预、后执行脚本中调用本地的脚本文件 导入脚本 在「系统设置」—「插件管理」中打开目录将要执行的脚本文件拖入到文件夹下 执行脚本 需要获取请求参数&#xff1a; const requestData request.request_bodys; 在…

netty源码前置一:Nio

NIO算是实现Reactor设计模式&#xff08;单Selector 单工作线程&#xff09;底层window用的是select&#xff0c;linux用的是epoll 网络NIO代码实现&#xff1a; public NIOServer(int port) throws Exception {selector Selector.open();serverSocket ServerSocketChannel.…

H.264与H.265的主要差异

H.265仍然采用混合编解码&#xff0c;编解码结构域H.264基本一致&#xff0c; H.265与H.264的主要不同 编码块划分结构&#xff1a;采用CU (CodingUnit)、PU(PredictionUnit)和TU(TransformUnit)的递归结构。 并行工具&#xff1a;增加了Tile以及WPP等并行工具集以提高编码速…

【Linux】fork()函数

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

如何使用Everything随时随地远程访问本地电脑搜索文件

文章目录 前言1.软件安装完成后&#xff0c;打开Everything2.登录cpolar官网 设置空白数据隧道3.将空白数据隧道与本地Everything软件结合起来总结 前言 要搭建一个在线资料库&#xff0c;我们需要两个软件的支持&#xff0c;分别是cpolar&#xff08;用于搭建内网穿透数据隧道…

Java基础常见面试题总结(下)

常见的Exception有哪些&#xff1f; 常见的RuntimeException&#xff1a; ClassCastException //类型转换异常IndexOutOfBoundsException //数组越界异常NullPointerException //空指针ArrayStoreException //数组存储异常NumberFormatException //数字格式化异常ArithmeticE…

Python笔记14-实战小游戏飞机大战(上)

文章目录 功能规划安装pygame绘制游戏窗口添加玩家飞机图像屏幕上绘制飞船代码重构驾驶飞船全屏模式射击 本示例源码地址 点击下载 功能规划 玩家控制一艘最初出现在屏幕底部中央的飞船。玩家可以使用箭头键左右移动飞船&#xff0c;还可使用空格键射击。游戏开始时&#xff…