Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
也就是说,Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。
“快速”和“简单”并不用产生维护性或性能上的问题。Netty 是一个吸收了多种协议(包括FTP、SMTP、HTTP等各种二进制文本协议)的实现经验,并经过相当精心设计的项目。最终,Netty 成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性。
--百度百科
一,包引入
无论什么模块,第一步都是maven包引入,有很多种jar包,我们用 netty-all
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.32.Final</version>
</dependency>
二,服务端
2.1 因为用的线程启动的netty,所以我们在启动类启动:
@Autowired
private NettyServer nettyServer;
@Bean
public void run() throws Exception {
InetSocketAddress address = new InetSocketAddress(DefaultConstants.SOCKET_IP, DefaultConstants.SOCKET_PORT);
log.info("netty服务器启动地址: {}", DefaultConstants.SOCKET_IP);
nettyServer.start(address);
}
2.2 然后我们写 nettyServer.java
package com.julinkiot.ThreeFactory.netty.service;
import com.julinkiot.ThreeFactory.netty.client.NettyServerChannelInitializer;
import io.netty.bootstrap.ServerBootstrap;
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 org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Resource;
import java.net.InetSocketAddress;
/**
* @ClassName DiscardServer
* @Author MaBin
* @Date 2021/12/27 12:09
* @Description DiscardServer todo
* @Version 1.0
*/
@Slf4j
@Component
public class NettyServer {
public void start(InetSocketAddress address) {
//配置服务端的NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap()
.group(bossGroup, workerGroup) // 绑定线程池
.channel(NioServerSocketChannel.class)
.localAddress(address)
.childHandler(new NettyServerChannelInitializer())//编码解码
.option(ChannelOption.SO_BACKLOG, 128) //服务端接受连接的队列长度,如果队列已满,客户端连接将被拒绝
.childOption(ChannelOption.SO_KEEPALIVE, true); //保持长连接,2小时无数据激活心跳机制
// 绑定端口,开始接收进来的连接
ChannelFuture future = bootstrap.bind(address).sync();
log.info("netty服务器开始监听端口:" + address.getPort());
//关闭channel和块,直到它被关闭
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
2.3 DefaultConstants
package com.julinkiot.ThreeFactory.netty.client;
/**
* @ClassName DefaultConstants
* @Author MaBin
* @Date 2021/12/27 13:23
* @Description DefaultConstants todo 默认参数 配置类
* @Version 1.0
*/
public class DefaultConstants {
public static final Integer SOCKET_PORT = 8888;
public static final String SOCKET_IP = "127.0.0.1";
}
2.4 NettyServerHandler.java
package com.julinkiot.ThreeFactory.netty.client;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
import java.util.concurrent.ConcurrentHashMap;
/**
* @ClassName NettyServerHandler
* @Author MaBin
* @Date 2021/12/27 13:21
* @Description NettyServerHandler todo
* @Version 1.0
*/
@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 管理一个全局map,保存连接进服务端的通道数量
*/
private static final ConcurrentHashMap<ChannelId, ChannelHandlerContext> CHANNEL_MAP = new ConcurrentHashMap<>();
/**
* @param ctx
* @author xiongchuan on 2019/4/28 16:10
* @DESCRIPTION: 有客户端连接服务器会触发此函数
* @return: void
*/
@Override
public void channelActive(ChannelHandlerContext ctx) {
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = insocket.getAddress().getHostAddress();
int clientPort = insocket.getPort();
//获取连接通道唯一标识
ChannelId channelId = ctx.channel().id();
System.out.println();
//如果map中不包含此连接,就保存连接
if (CHANNEL_MAP.containsKey(channelId)) {
log.info("客户端【" + channelId + "】是连接状态,连接通道数量: " + CHANNEL_MAP.size());
} else {
//保存连接
CHANNEL_MAP.put(channelId, ctx);
log.info("客户端【" + channelId + "】连接netty服务器[IP:" + clientIp + "--->PORT:" + clientPort + "]");
log.info("连接通道数量: " + CHANNEL_MAP.size());
}
}
/**
* @param ctx
* @author xiongchuan on 2019/4/28 16:10
* @DESCRIPTION: 有客户端终止连接服务器会触发此函数
* @return: void
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) {
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = insocket.getAddress().getHostAddress();
ChannelId channelId = ctx.channel().id();
//包含此客户端才去删除
if (CHANNEL_MAP.containsKey(channelId)) {
//删除连接
CHANNEL_MAP.remove(channelId);
System.out.println();
log.info("客户端【" + channelId + "】退出netty服务器[IP:" + clientIp + "--->PORT:" + insocket.getPort() + "]");
log.info("连接通道数量: " + CHANNEL_MAP.size());
}
}
/**
* @param ctx
* @author xiongchuan on 2019/4/28 16:10
* @DESCRIPTION: 有客户端发消息会触发此函数
* @return: void
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println();
log.info("加载客户端报文......");
log.info("【" + ctx.channel().id() + "】" + " :" + msg);
/**
* 下面可以解析数据,保存数据,生成返回报文,将需要返回报文写入write函数
*
*/
//响应客户端
this.channelWrite(ctx.channel().id(), msg);
}
/**
* @param msg 需要发送的消息内容
* @param channelId 连接通道唯一id
* @author xiongchuan on 2019/4/28 16:10
* @DESCRIPTION: 服务端给客户端发送消息
* @return: void
*/
public void channelWrite(ChannelId channelId, Object msg) throws Exception {
ChannelHandlerContext ctx = CHANNEL_MAP.get(channelId);
if (ctx == null) {
log.info("通道【" + channelId + "】不存在");
return;
}
if (msg == null || msg == "") {
log.info("服务端响应空的消息");
return;
}
//将客户端的信息直接返回写入ctx
ctx.write(msg);
//刷新缓存区
ctx.flush();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
String socketString = ctx.channel().remoteAddress().toString();
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.READER_IDLE) {
log.info("Client: " + socketString + " READER_IDLE 读超时");
ctx.disconnect();
} else if (event.state() == IdleState.WRITER_IDLE) {
log.info("Client: " + socketString + " WRITER_IDLE 写超时");
ctx.disconnect();
} else if (event.state() == IdleState.ALL_IDLE) {
log.info("Client: " + socketString + " ALL_IDLE 总超时");
ctx.disconnect();
}
}
}
/**
* @param ctx
* @author xiongchuan on 2019/4/28 16:10
* @DESCRIPTION: 发生异常会触发此函数
* @return: void
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println();
ctx.close();
log.info(ctx.channel().id() + " 发生了错误,此连接被关闭" + "此时连通数量: " + CHANNEL_MAP.size());
//cause.printStackTrace();
}
}
2.5 NettyServerChannelInitializer.java
package com.julinkiot.ThreeFactory.netty.client;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
/**
* @ClassName NettyServerChannelInitializer
* @Author MaBin
* @Date 2021/12/27 13:20
* @Description NettyServerChannelInitializer todo
* @Version 1.0
*/
public class NettyServerChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
channel.pipeline().addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
channel.pipeline().addLast(new NettyServerHandler());
}
}
三、客户端
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
/**
* @ClassName test
* @Author MaBin
* @Date 2021/12/27 13:50
* @Description test todo
* @Version 1.0
*/
public class test {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost", 8888);
OutputStream outputStream = socket.getOutputStream();
PrintWriter printWriter = new PrintWriter(outputStream);
while (true) {
printWriter.write("$tmb00035ET3318/08/22 11:5804029.94,027.25,20.00,20.00$");
printWriter.flush();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// socket.shutdownOutput();
// socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
参考文章:
springboot整合netty_月夜风雨磊的博客-CSDN博客_springboot整合netty目录前言正文代码新建一个springboot项目,在pom文件中添加netty依赖:新建netty服务netty调用所需的服务类springboot启动类测试我在整合过程中遇到的问题总结前言上一篇讲了netty的一个入门的demo;项目上我也把数据处理做好了,就要开始存数据库了;我用的mybatis框架,如果单独使用还是觉得比较麻烦,所以就用了springboot+mybatis+netty;本篇主要讲netty与springboot的整合,以及我在这个过程中遇到的问题,又https://blog.csdn.net/qq_42855293/article/details/109351780?spm=1001.2014.3001.5502netty实战:SpringBoot集成netty实现客户端服务端交互_AI X的博客-CSDN博客_springboot集成nettySpringBoot集成netty实现客户端服务端交互在springboot中集成netty。我在代码里用到了lombok的@data和@Slf4j注解,主要用来简化get set方法和输出日志。我们先加入netty的依赖下面是我的netty代码结构server端server启动类server初始化类server处理类启动类在springboot启动类中加入服务端的启动代码,让netty服务跟着项...
https://blog.csdn.net/weixin_42906256/article/details/89704785