1.项目目录
2.pom引入netty和protobuf
io.netty
netty-all
4.1.22.Final
com.google.protobuf
protobuf-java
3.5.1
3.先写个netty的启动类 NettyServer,其中@PostConstruct是springboot启动命令,当你运行项目是就会运行下面的代码,ServerBootstrap和InetSockerAddress则从第4步获取bean注入
/**
* netty服务端
* @author 85862
*
*/
@Component
public class NettyServer {
@Autowired
@Qualifier("serverBootstrap")
private ServerBootstrap b;
@Autowired
@Qualifier("tcpSocketAddress")
private InetSocketAddress tcpPort;
private ChannelFuture serverChannelFuture;
@PostConstruct
public void start() throws Exception {
System.out.println("Starting server at " + tcpPort);
serverChannelFuture = b.bind(tcpPort).sync();
}
@PreDestroy
public void stop() throws Exception {
serverChannelFuture.channel().closeFuture().sync();
}
public ServerBootstrap getB() {
return b;
}
public void setB(ServerBootstrap b) {
this.b = b;
}
public InetSocketAddress getTcpPort() {
return tcpPort;
}
public void setTcpPort(InetSocketAddress tcpPort) {
this.tcpPort = tcpPort;
}
}
4. 配置bean ,先在application.properties设置配置
#netty 服务端监听的端口
tcp.port=8091
# bossGroup的线程数
boss.thread.count=2
# worker的线程数
worker.thread.count=2
#是否使用长连接
so.keepalive=true
so.backlog=100
然后继续配置bean,其中主要看serverBootstrap,设置ServerChannelInitalizer,设置编码格式等,参考步骤5
/**
* netty配置类
* @author 85862
*
*/
@Configuration
public class NettyConfig {
//读取yml中配置
@Value("${boss.thread.count}")
private int bossCount;
@Value("${worker.thread.count}")
private int workerCount;
@Value("${tcp.port}")
private int tcpPort;
@Value("${so.keepalive}")
private boolean keepAlive;
@Value("${so.backlog}")
private int backlog;
@Autowired
private EventLoopGroup bossGroup;
@Autowired
private EventLoopGroup workerGroup;
@Autowired
private ServerChannelInitalizer serverChannelInitializer;
@SuppressWarnings("unchecked")
@Bean(name = "serverBootstrap")
public ServerBootstrap bootstrap() {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
// b.group(bossGroup(), workerGroup())
.channel(NioServerSocketChannel.class)
.childHandler(serverChannelInitializer);
Map, Object> tcpChannelOptions = tcpChannelOptions();
Set> keySet = tcpChannelOptions.keySet();
for (@SuppressWarnings("rawtypes")
ChannelOption option : keySet) {
b.option(option, tcpChannelOptions.get(option));
}
return b;
}
@Bean(name = "bossGroup", destroyMethod = "shutdownGracefully")
public NioEventLoopGroup bossGroup() {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(bossCount);
return bossGroup;
}
@Bean(name = "workerGroup", destroyMethod = "shutdownGracefully")
public NioEventLoopGroup workerGroup() {
NioEventLoopGroup workerGroup = new NioEventLoopGroup(workerCount);
return workerGroup;
}
@Bean(name = "tcpSocketAddress")
public InetSocketAddress tcpPort() {
return new InetSocketAddress(tcpPort);
}
@Bean(name = "tcpChannelOptions")
public Map, Object> tcpChannelOptions() {
Map, Object> options = new HashMap, Object>();
options.put(ChannelOption.SO_KEEPALIVE, keepAlive);
options.put(ChannelOption.SO_BACKLOG, backlog);
return options;
}
@Bean(name = "stringEncoder")
public StringEncoder stringEncoder() {
return new StringEncoder();
}
@Bean(name = "stringDecoder")
public StringDecoder stringDecoder() {
return new StringDecoder();
}
/**
* Necessary to make the Value annotations work.
*
* @return
*/
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
5.ChannelPipeline配置,编码格式采用protobuf
/**
* netty服务端ChannelPipeline配置(客户端的配置需要与服务端一致,编解码方式等)
* @author 85862
*
*/
@Component
public class ServerChannelInitalizer extends ChannelInitializer {
@Autowired
ServerHandler serverHandler;
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//服务端心跳监听事件间隔
pipeline.addLast(new IdleStateHandler(80, 0, 0, TimeUnit.SECONDS));
// 解码和编码,应和客户端一致
//传输的协议 Protobuf
pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast(new ProtobufDecoder(NettyMessage.Content.getDefaultInstance()));
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast(new ProtobufEncoder());
// pipeline.addLast("decoder", stringDecoder);
// pipeline.addLast("encoder", stringEncoder);
pipeline.addLast("handler", serverHandler);
}
}
6. handler,用于消息发送读取,channelActive服务端和客户端初次建立连接走的方法;channelRead当客户端有信息过来,服务端读取数据的方法;userEventTriggered用于处理心跳机制,在case ALL_IDLE自行定义方法
package com.xbstar.fuse.clientcenter.netty;
import com.xbstar.fuse.clientcenter.netty.protobuf.NettyMessage;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleStateEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* netty 服务端业务处理
* @author 85862
*/
@Component
@ChannelHandler.Sharable
public class ServerHandler extends ChannelInboundHandlerAdapter{
private static final Logger logger = LoggerFactory.getLogger(ServerHandler.class);
//初次建立连接
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
logger.info("\nRemoteAddress : " + ctx.channel().remoteAddress() + " active !");
//通知客户端连接已建立
NettyMessage.Content handShake = NettyMessage.Content.newBuilder().
setMethod(NettyConstant.REQUEST).
setOperateType(NettyConstant.HAND_SHAKE).
build();
logger.info("\nsend handshake to client:\n{}",handShake);
ctx.writeAndFlush(handShake);
super.channelActive(ctx);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
logger.info("\nreceive message from client: \n{}",msg);
if(!(msg instanceof NettyMessage.Content)) {
logger.error("\ninvalid message");
return;
}
NettyMessage.Content message = (NettyMessage.Content)msg;
String operateType = message.getOperateType();
NettyMessage.Content.Builder responseBuilder = NettyMessage.Content.newBuilder();
NettyMessage.Content response = null;//服务端返回值
switch (operateType) {
case NettyConstant.RULE_INIT:
break;
case NettyConstant.REQUEST_DATA_SEND:
break;
case NettyConstant.REQUEST_DATA_SEND_EMERGENT:
break;
case NettyConstant.REQUEST_OTHER_DATA_SAVE:
break;
default:
break;
}
}
/**
* 事件触发处理业务逻辑
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object eve) throws Exception {
if (eve instanceof IdleStateEvent) {//心跳超时处理
IdleStateEvent event = (IdleStateEvent) eve;
switch (event.state()) {
case READER_IDLE:
break;
case WRITER_IDLE:
break;
case ALL_IDLE:
logger.error("Netty server has lost client heartbeat within 5s");
// 如果通道失去心跳连接超过x次,服务端可以选择关闭该不活跃通道
// if (idle_count > x) {
// logger.info("Netty server disconnect an inactive client");
// ctx.channel().close();
// }
// idle_count++;
break;
default:
break;
}
} else {
super.userEventTriggered(ctx, eve);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
logger.error("服务端发送异常:{}",cause.getMessage());
/*ctx.close();*/
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
logger.error("\n失去客户端连接,Channel is disconnected");
super.channelInactive(ctx);
}
}
7. idea生成protobuf
https://www.cnblogs.com/liugh/p/7505533.html