本片以使用为主,概念请移动百度
不做过多解释,备注的很详细
项目基于SpringBoot
1. pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--netty-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.31.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--优化编码-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.49</version>
</dependency>
2. 服务端
核心类 NettyServer.java
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 lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
/**
* @author 司马缸砸缸了
* @description NETTY服务端
* @date 2019/8/1 10:10
*/
@Service
@Slf4j
public class NettyServer {
//boss事件轮询线程组 ,处理连接事件
private EventLoopGroup bossGroup = new NioEventLoopGroup();
//worker事件轮询线程组, 用于数据处理
private EventLoopGroup workerGroup = new NioEventLoopGroup();
@Autowired
private NettyServerInitializer nettyServerInitializer;
@Value("${netty.port}")
private Integer port;
/**
* 开启Netty服务
*
* @return
*/
public void start() {
try {
//启动类
ServerBootstrap serverBootstrap = new ServerBootstrap();
//设置参数,组配置
serverBootstrap.group(bossGroup, workerGroup)
//socket参数,当服务器请求处理程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。
// 如果未设置或所设置的值小于1,Java将使用默认值50。
//
// 服务端可连接队列数,对应TCP/IP协议listen函数中backlog参数
.option(ChannelOption.SO_BACKLOG, 128)
// 设置TCP长连接,一般如果两个小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文
// 建议长连接的时候打开,心跳检测
// .childOption(ChannelOption.SO_KEEPALIVE, true)
// 构造channel通道工厂//bossGroup的通道,只是负责连接
.channel(NioServerSocketChannel.class)
// 设置通道处理者ChannelHandlerworkerGroup的处理器
.childHandler(nettyServerInitializer);
// 绑定端口,开始接收进来的连接
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
log.info("netty服务启动: [port:" + port + "]");
// 等待服务器socket关闭
// 应用程序会一直等待,直到channel关闭
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
log.error("netty服务启动异常-" + e.getMessage());
} finally {
// 优雅的关闭服务端
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
初始化类 NettyServerInitializer.java
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.CharsetUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* @author 司马缸砸缸了
* @description service初始化配置
* @date 2019/8/1 10:30
*/
@Component
public class NettyServerInitializer extends ChannelInitializer<SocketChannel> {
@Autowired
private NettyServerHandler nettyServerHandler;
@Autowired
private AcceptorIdleStateTrigger idleStateTrigger;
/**
* 初始化channel
*/
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 分隔符解码器,处理半包。
// maxFrameLength 表示一行最大的长度
// Delimiters.lineDelimiter(),以/n,/r/n作为分隔符
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
// 自定义心跳检测
// 1)readerIdleTime:为读超时时间(即多长时间没有接受到客户端发送数据)
// 2)writerIdleTime:为写超时时间(即多长时间没有向客户端发送数据)
// 3)allIdleTime:所有类型的超时时间
pipeline.addLast(new IdleStateHandler(5,0,0, TimeUnit.SECONDS));
ch.pipeline().addLast(idleStateTrigger);
pipeline.addLast(nettyServerHandler);
}
}
处理类 NettyServerHandler.java
import com.alibaba.fastjson.JSON;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.concurrent.EventExecutorGroup;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;
import java.util.Map;
/**
* @author 司马缸砸缸了
* @description 服务端处理器
* @date 2019/8/1 10:42
*/
@Slf4j
@Component
// 这个注解适用于标注一个channel handler可以被多个channel安全地共享
// 也可以使用new NettyServerHandler()方式解决
@ChannelHandler.Sharable
public class NettyServerHandler extends SimpleChannelInboundHandler<String> {
/**
* String 也可以是Object类型
*
* @param ctx
* @param msg
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
log.info("接收到的消息:{}", msg);
StringBuilder sb = null;
Map<String, Object> result = null;
try {
// 报文解析处理
sb = new StringBuilder();
result = JSON.parseObject(msg);
sb.append(result);
sb.append("解析成功");
sb.append("\n");
ctx.writeAndFlush(sb);
} catch (Exception e) {
String errorCode = "-1\n";
ctx.writeAndFlush(errorCode);
log.error("报文解析失败: " + e.getMessage());
}
}
/**
* 客户端去和服务端连接成功时触发
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = insocket.getAddress().getHostAddress();
log.info("收到客户端[ip:" + clientIp + "]连接");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 当出现异常就关闭连接
ctx.close();
//把客户端的通道关闭
ctx.channel().close();
}
}
心跳配置类 AcceptorIdleStateTrigger.java
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @author yangyang7_kzx
* @date 2019/8/1 17:51
* @description 服务端心跳检查
*/
@Slf4j
@Component
@ChannelHandler.Sharable
public class AcceptorIdleStateTrigger extends ChannelInboundHandlerAdapter {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
if (idleStateEvent.state() == IdleState.READER_IDLE) {
log.info("已经5秒未收到客户端的消息了!");
//向服务端送心跳包
String heartbeat = "{\"msg\":\"server heart beat\"}\n";
//发送心跳消息,并在发送失败时关闭该连接
ctx.writeAndFlush(heartbeat).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}
} else {
super.userEventTriggered(ctx, evt);
}
}
/*
//备注 , 状态
switch (event.state()){
case READER_IDLE:
eventType = "读空闲";
break;
case WRITER_IDLE:
eventType = "写空闲";
break;
case ALL_IDLE:
eventType ="读写空闲";
break;
}*/
}
3. 客户端
核心类 NettyClient.java
import com.it.cloud.itagent.client.NettyClientInitializer;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.NoArgsConstructor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 多次发送消息
*
* @author 司马缸砸缸了
* @description 客户端TEST
* @date 2019/8/1 10:53
*/
@NoArgsConstructor
public class NettyClient {
private EventLoopGroup group = new NioEventLoopGroup();
// 定时线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
/**
* 连接方法
*/
public void connect() {
Channel channel = null;
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class);
// 将小的数据包包装成更大的帧进行传送,提高网络的负载,即TCP延迟传输
bootstrap.option(ChannelOption.TCP_NODELAY, true);
// 设置TCP的长连接,默认的 keepalive的心跳时间是两个小时
// bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
bootstrap.handler(new NettyClientInitializer());
channel = bootstrap.connect("127.0.0.1", 2222).sync().channel();
// 处理
handler(channel);
// 应用程序会一直等待,直到channel关闭
channel.closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
/**
* 业务逻辑
*
* @param channel
*/
private void handler(Channel channel) {
//如果任务里面执行的时间大于 period 的时间,下一次的任务会推迟执行。
//本次任务执行完后下次的任务还需要延迟period时间后再执行
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println("====定时任务开始====");
// 发送json字符串
String msg = "{\"name\":\"admin\",\"age\":27}\n";
channel.writeAndFlush(msg);
}
}, 2, 10, TimeUnit.SECONDS);
}
/**
* 主动关闭
*/
public void close() {
group.shutdownGracefully();
}
/**
* 测试入口
*
* @param args
*/
public static void main(String[] args) {
NettyClient nettyClient = new NettyClient();
nettyClient.connect();
}
}
初始化类 NettyClientInitializer.java
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
/**
* @author 司马缸砸缸了
* @description 客户端通道初始化
* @date 2019/8/1 10:57
*/
public class NettyClientInitializer extends ChannelInitializer<SocketChannel> {
/**
* 初始化channel
*/
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new NettyClientHandler());
}
}
处理类 NettyClientHandler.java
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* @author 司马缸砸缸了
* @description 客户端处理
* @date 2019/8/1 10:58
*/
public class NettyClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("收到服务端消息: " + msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
4. 服务端启动类
整合springboot,实现CommandLineRunner 重写run,启动应用时启动netty server
import com.it.cloud.itagent.server.NettyServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class NettyApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(NettyApplication.class, args);
}
@Autowired
private NettyServer nettyServer;
@Override
public void run(String... args) {
nettyServer.start();
}
}
5. 测试
- 先启动Springboot,NettyApplication.java
- 再启动客户端NettyClient.java
服务端控制台:
客户端控制台:
源码地址
传送门
开源项目,持续不断更新中,喜欢请 Star~
项目推荐
IT-CLOUD :IT服务管理平台,集成基础服务,中间件服务,监控告警服务等