经过前面的netty学习,大概了解了netty各个组件的概念和作用,开始自己瞎鼓捣netty和我们常用的项目的整合(很简单的整合)
项目准备
工具:IDEA2017
jar包导入:maven
项目框架:springboot+netty
项目操作
右键创建一个maven项目,项目名称: hetangyuese-netty-03(项目已上传github)
项目完整结构
maven导包
io.netty
netty-all
4.1.15.Final
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-devtools
true
// 之所以没版本,我是在parent项目中配置了maven的全局版本,只能在顶级项目中配置
org.springframework.boot
spring-boot-starter-parent
1.5.6.RELEASE
org.slf4j
slf4j-api
1.7.7
ch.qos.logback
logback-core
1.1.7
ch.qos.logback
logback-classic
1.1.7
编码
springboot启动类 HetangyueseApplication
因为需要集成netty启动类不再是继承SpringBootServletInitializer类修改为实现CommandLineRunner(CommandLineRunner项目启动后执行)
package com.hetangyuese.netty;
import com.hetangyuese.netty.controller.HtServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @program: netty-root
* @description: 启动类
* @author: hetangyuese
* @create: 2019-10-28 16:47
**/
@SpringBootApplication
public class HetangyueseApplication implements CommandLineRunner {
@Autowired
private HtServer htServer; // Netty服务端类
public static void main(String[] args) {
SpringApplication.run(HetangyueseApplication.class, args);
}
@Override
public void run(String... strings) throws Exception {
// 调用netty服务端启动方法
htServer.start(9000);
}
}
Netty启动类
package com.hetangyuese.netty.controller;
import com.hetangyuese.netty.channel.HtServerChannel;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @program: netty-root
* @description: ht服务类
* @author: hetangyuese
* @create: 2019-10-28 17:28
**/
@Component
public class HtServer {
private Logger log = LoggerFactory.getLogger(HtServer.class);
/**
* Netty服务端启动类
*/
private ServerBootstrap serverBootstrap;
/**
* 服务通道
*/
@Autowired
private HtServerChannel htServerChannel;
/**
* Netty日志处理类,可以打印出入站出站的日志,方便排查
* 需搭配 channelPipeline.addLast(new LoggingHandler(LogLevel.INFO));
* 使用
*/
private ChannelHandler logging = new LoggingHandler();
/**
*
* @param port 启动端口号
*/
public void start(int port) {
log.debug("htServer start port:{}", port);
// 主线程组 用于处理连接
EventLoopGroup boss = new NioEventLoopGroup(1);
// 工作线程组用于处理业务逻辑
EventLoopGroup work = new NioEventLoopGroup();
try {
serverBootstrap = getServerBootstrap();
// 配置服务端启动类
serverBootstrap.group(boss, work)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_REUSEADDR, true)
.handler(logging)
.childHandler(htServerChannel);
// 服务端绑定端口并持续等待
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
// 通道持续阻塞等待直到关闭了服务
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
// 输出错误日志
log.error("netty server start happened exception e:{}", e);
} finally {
// 关闭线程组
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
/**
* 初始化启动类
* @return
*/
public ServerBootstrap getServerBootstrap() {
if (null == serverBootstrap) {
serverBootstrap = new ServerBootstrap();
}
return serverBootstrap;
}
}
管道类(channel、pipeline)
package com.hetangyuese.netty.channel;
import com.hetangyuese.netty.handler.HtServerHandler;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @program: netty-root
* @description: 配置管道
* @author: hetangyuese
* @create: 2019-10-28 17:35
**/
@Service
public class HtServerChannel extends ChannelInitializer {
@Autowired
private HtServerHandler htServerHandler;
@Override
protected void initChannel(Channel ch) throws Exception {
// 通道流水线 管理channelHandler的有序执行
ChannelPipeline channelPipeline = ch.pipeline();
// netty日志
channelPipeline.addLast(new LoggingHandler(LogLevel.INFO));
// 字符串解码器 接收到数据直接转为string 这里没有弄自定义和其他的解码器
channelPipeline.addLast(new StringDecoder());
// 业务逻辑处理类
channelPipeline.addLast(htServerHandler);
}
}
业务逻辑类(handler)
服务端ChannelPipeline中有许多的ChannelHandler, 如果每个都实例化一个ChannelHandler,在大量的客户端连接的时候将会产生大量的ChannelHandler实例,为了解决这个问题netty中可以通过@ChannelHandler.Sharable注解实现共享实例,由这一个实例去处理客户端连接
package com.hetangyuese.netty.handler;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
/**
* @program: netty-root
* @description: 处理类
* @author: hetangyuese
* @create: 2019-10-28 17:39
**/
@ChannelHandler.Sharable
@Service
public class HtServerHandler extends ChannelInboundHandlerAdapter {
private Logger log = LoggerFactory.getLogger(HtServerHandler.class);
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.debug("channel已注册");
ctx.writeAndFlush(Unpooled.copiedBuffer("xixixix".getBytes()));
}
/**
* 服务端接收到的数据
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.debug("htServer receive" + (String)msg);
}
/**
* 服务端接收完毕事件
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer("Htserver readComplete".getBytes()));
}
/**
* 异常捕获事件
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
配置文件(application.yml、logback.xml)
application.yml文件
spring:
profiles:
active: prod
-----------------------------------------------------------
application-prod.yml
server:
port: 8081
%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger -%msg%n
${LOG_HOME}/netty03.%d{yyyy-MM-dd}.log
30
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
0
512
启动(客户端我就不贴代码了)
// 服务端
2019-10-29 16:10:20 [restartedMain] DEBUG com.hetangyuese.netty.controller.HtServer -htServer start port:9000
2019-10-29 16:10:48 [nioEventLoopGroup-3-1] DEBUG com.hetangyuese.netty.handler.HtServerHandler -channel已注册
2019-10-29 16:10:48 [nioEventLoopGroup-3-1] DEBUG com.hetangyuese.netty.handler.HtServerHandler -htServer receivehello!_My name is hanleilei !_What is your name !_How are you? !_
// 客户端
服务端返回str: xixixix
服务端返回str: Htserver readComplete
总结
学习了netty的基础知识后,了解到很多rpc框架都运用了netty,看了下dubbo的netty源码部分,也能明白每一步的用途,接下来自己琢磨写个rpc框架试试,学无止境!!!