文章结构说明
- Maven引入
- Netty5简易使用
- Netty5的Spring封装
- Spring配置
- 程序打包、运行
Netty5+Maven+Spring环境搭建
环境说明
- IDEA
- jdk1.7+
- maven3
Maven引入
在IDEA中新建maven项目
可以用模板导入也可以不用 如下没有模板导入
File –> New –> Project. 如图
选择maven,选择jdk1.8 –> Next
填好maven三坐标 –> Next
填好项目信息 –> Finish
等项目自动创建成功,项目如图
pom文件现在还是空的笔者的习惯会补全项目的文件,如图
以上我们建立了一个空的maven项目,由于netty是一个非web应用,所有没有webapp的结构
在maven的pom文件中引入我们需要的依赖 如下
|
|
Netty5
先看一个Netty5的简单例子
Server启动类
1234567891011121314151617181920212223242526272829303132333435363738394041public class EchoServer {private int port;public EchoServer(int port) {this.port = port;}public void run() throws Exception {EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();// Server启动类b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)// 通道类型.childHandler(new ChannelInitializer<SocketChannel>() {public void initChannel(SocketChannel socketChannel)throws Exception {socketChannel.pipeline().addLast(new EchoServerHandler());}})// Server的处理类通常是chiledHandler,// 需要添加一个channel处理类的工厂方法ChannelInitializer,//通过覆盖initChannel方法,增添处理类.option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);ChannelFuture f = b.bind(port).sync(); //让主线程阻塞f.channel().closeFuture().sync();// 主线程退出时异步关闭通道操作} finally {workerGroup.shutdownGracefully();// 一定要加上bossGroup.shutdownGracefully();}}public static void main(String[] args) throws Exception {int port;if (args.length > 0) {port = Integer.parseInt(args[0]);} else {port = 8080;}new EchoServer(port).run();}}Server数据处理Handler
12345678910111213141516public class EchoServerHandler extends ChannelHandlerAdapter {public void channelRead(ChannelHandlerContext ctx, Object msg) {ctx.write(msg);}public void channelReadComplete(ChannelHandlerContext ctx) {ctx.flush();}public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)// Close the connection when an exception is raised.cause.printStackTrace();ctx.close();}}启动测试
- 运行Server类的main方法
- 终端输入命令
telnet localhost 8080
- 任何输入Server都会原样返回
如图
Netty面向Spring封装
为了将上述的过程通过Spring管理,需要对NettyServer做两件事
- 将NettyServer的配置POJO化,以方便Spring的Bean组装和管理
- 将NettyServer的管理面向接口,以方便Spring的接入
以下是具体的实现过程
类关系图
- Server、NettyServer是服务器的接口
- AbstractNettyServer是服务器的抽象类,封装部分通用实现
- NettyTcpServer是基于Tcp协议的具体实现类
- NettyConfig是面向Spring封装的Server配置类
- 通过ServerManager实现对Server生命周期的管理
- MyChannelInitializer是类似数据流处理Handler类工厂,在这里添加(多个)数据流处理Handler。
- EchoHandler是数据流处理的具体实现类,Server的具体业务逻辑在这里实现。
- Entrance是程序的启动入口
每个类的具体实现
NettyConfig class
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152public class NettyConfig {private NioEventLoopGroup bossGroup;private NioEventLoopGroup workerGroup;private int bossCount;private int workerCount;private int port = 12345;public NioEventLoopGroup getBossGroup() {if (null == bossGroup) {if (0 >= bossCount) {bossGroup = new NioEventLoopGroup();} else {bossGroup = new NioEventLoopGroup(bossCount);}}return bossGroup;}public void setBossGroup(NioEventLoopGroup bossGroup) {this.bossGroup = bossGroup;}public synchronized NioEventLoopGroup getWorkerGroup() {if (null == workerGroup) {if (0 >= workerCount) {workerGroup = new NioEventLoopGroup();} else {workerGroup = new NioEventLoopGroup(workerCount);}}return workerGroup;}public void setWorkerGroup(NioEventLoopGroup workerGroup) {this.workerGroup = workerGroup;}public int getBossCount() {return bossCount;}public void setBossCount(int bossCount) {this.bossCount = bossCount;}public int getWorkerCount() {return workerCount;}public void setWorkerCount(int workerCount) {this.workerCount = workerCount;}public int getPort() {return port;}public void setPort(int port) {this.port = port;}}Server interface
12345public interface Server {void start() throws Exception;void start(int port) throws Exception;void stop() throws Exception;}NettyServer interface
12345public interface NettyServer extends Server {ChannelInitializer<? extends Channel> getChannelInitializer();void setChannelInitializer(ChannelInitializer<? extends Channel> initializer);NettyConfig getNettyConfig();}AbstractNettyServer abstract class
123456789101112131415161718192021222324252627282930313233343536373839404142434445public abstract class AbstractNettyServer implements NettyServer {protected final NettyConfig nettyConfig;protected ChannelInitializer<? extends Channel> channelInitializer;public static final ChannelGroup ALL_CHANNELS = new DefaultChannelGroup("NADRON-CHANNELS", GlobalEventExecutor.INSTANCE);public AbstractNettyServer(NettyConfig nettyConfig, ChannelInitializer<? extends Channel> channelInitializer) {this.nettyConfig = nettyConfig;this.channelInitializer = channelInitializer;}public void start(int port) throws Exception {nettyConfig.setPort(port);start();}public void stop() throws Exception {ChannelGroupFuture futures = ALL_CHANNELS.close();try {futures.await();} catch (InterruptedException e) {e.printStackTrace();} finally {if (null != nettyConfig.getBossGroup()) {nettyConfig.getBossGroup().shutdownGracefully();}if (null != nettyConfig.getWorkerGroup()) {nettyConfig.getWorkerGroup().shutdownGracefully();}}}public ChannelInitializer<? extends Channel> getChannelInitializer() {return channelInitializer;}public NettyConfig getNettyConfig() {return nettyConfig;}protected EventLoopGroup getBossGroup() {return nettyConfig.getBossGroup();}protected EventLoopGroup getWorkerGroup() {return nettyConfig.getWorkerGroup();}}NettyServer class
12345678910111213141516171819202122232425262728public class NettyTcpServer extends AbstractNettyServer {private ServerBootstrap b;public NettyTcpServer(NettyConfig nettyConfig, ChannelInitializer<? extends Channel> channelInitializer) {super(nettyConfig, channelInitializer);}public void start() throws Exception {try {b = new ServerBootstrap();b.group(getBossGroup(), getWorkerGroup()).channel(NioServerSocketChannel.class).childHandler(getChannelInitializer()).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);ChannelFuture f = b.bind(nettyConfig.getPort()).sync();ALL_CHANNELS.add(f.channel());f.channel().closeFuture().sync();} finally {stop();}}public void setChannelInitializer(ChannelInitializer<? extends Channel> initializer) {this.channelInitializer = initializer;b.childHandler(initializer);}}MyChannelInitializer class
123456public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {protected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast("echoHandler", new EchoHandler());}}EchoHandler class
12345678910111213141516public class EchoHandler extends ChannelHandlerAdapter {public void channelRead(ChannelHandlerContext ctx, Object msg) {ctx.write(msg);}public void channelReadComplete(ChannelHandlerContext ctx) {ctx.flush();}public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)// Close the connection when an exception is raised.cause.printStackTrace();ctx.close();}}
Spring接管配置工作
笔者采用xml配置的方式,利于代码与配置的分离,也可以用配置类的方式
配置xml
1234567891011121314151617181920212223242526<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="com.nettyspring"/><bean id="tcpServer" class="com.nettyspring.first.example.server.NettyTcpServer" destroy-method="stop"><constructor-arg ref="tcpConfig"/><constructor-arg ref="myChannelInitializer"/></bean><bean id="myChannelInitializer" class="com.nettyspring.first.example.Initializer.MyChannelInitializer"></bean><bean id="tcpConfig" class="com.nettyspring.first.example.NettyConfig"><property name="bossGroup" ref="bossGroup"/><property name="workerGroup" ref="workerGroup"/><property name="port" value="10086"/></bean><bean id="bossGroup" class="io.netty.channel.nio.NioEventLoopGroup" destroy-method="shutdownGracefully"><constructor-arg type="int" index="0" value="2"/></bean><bean id="workerGroup" class="io.netty.channel.nio.NioEventLoopGroup" destroy-method="shutdownGracefully"><constructor-arg type="int" index="0"value="8"/></bean></beans>AppContext class
实现自己的AppContext,并在启动Server时调用,让Spring接管bean管理工作,由于上面配置了component-scan
,支持Spring注解的使用12345678910111213141516public class AppContext implements ApplicationContextAware {public static ApplicationContext applicationContext;public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {AppContext.applicationContext = applicationContext;}public static Object fetchBean(String name) {if (null == name) {return null;}if (null == applicationContext) {applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");}return applicationContext.getBean(name);}}ServerManager class
123456789101112131415public class ServerManager {private AbstractNettyServer nettyServer;public ServerManager() {nettyServer = (AbstractNettyServer) AppContext.fetchBean("tcpServer");}public void startServer(int port) throws Exception {nettyServer.start(port);}public void startServer() throws Exception {nettyServer.start();}public void stopServer() throws Exception {nettyServer.stop();}}Entrance class
123456789101112public class Entrance {public static void main(String... args) {ServerManager manager = new ServerManager();try {manager.startServer();} catch (Exception e) {e.printStackTrace();}}}
打包、运行
- 打包需要的maven插件12345678910111213141516171819202122232425262728293031323334353637383940414243444546<build><finalName>nettyFirst</finalName><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.6</source><target>1.6</target></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>1.2.1</version><executions><execution><phase>package</phase><goals><goal>shade</goal></goals><configuration><transformers><transformerimplementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"><resource>META-INF/spring.handlers</resource></transformer><transformerimplementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"><resource>META-INF/spring.schemas</resource></transformer><transformerimplementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"><mainClass>com.nettyspring.first.example.app.Entrance</mainClass></transformer></transformers></configuration></execution></executions></plugin></plugins></build>
shade 插件可以指定jar文件执行的入口类,<mainClass>
需自己指定
上面的两个transformer
的作用是将spring依赖包里的xml文件也打进去,如果没指定的话会出现schemas
找不到的情况
- 打包、执行
maven clean install
会在工程目录下生成target
目录,里面有生成的jar文件 。java -jar XXX.jar
- 在终端输入
telnet localhost 10086
10086 为spring的配置xml里配置的端口号,如图
上述工作完成后的目录结构如下
Netty5参考资料
文档
Netty5 示例程序
通过maven引入