1.使用netty实现可配置化的NIO通讯服务器

2.要求支持多种通讯协议以及长短链接,如http,https,TCP,TLS,MQTT等

3.支持私有协议拓展开发

以上因为是个人demo项目,所以未做其他方面的需求,比如压力测试,集群部署等,这些自己也需要在下下个阶段继续研究(下个阶段主要研究dubbo和完善自己的demo项目)。

1.Main类

首先我们少不了一个Main类,这个是netty启动的类,直接看代码。




package com . omen . netty . server ;




import org.apache.log4j.Logger ;


import org.springframework.beans.factory.annotation.Autowired ;


import org.springframework.context.support.FileSystemXmlApplicationContext ;




import com.omen.netty.server.sysPojo.SystemInfo ;




public class Main {




private static Logger log = Logger . getLogger ( Main . class );


@Autowired


private SystemInfo systemInfo ;




static {


// 先加载spring


log . info ( "准备载入spring..." );


String url = "../classes/omen.xml" ;


// 保存context


SystemInfo . setCtx ( new FileSystemXmlApplicationContext ( url ));


log . info ( "载入spring 完毕..." );


}




public static void main ( String [] args ) throws Exception {


IServer iServer = ( IServer ) SystemInfo . getCtx (). getBean ( "basicServer" );


iServer . start ();


}




}





com.omen.netty.server.Main.java



这里在启动main方法的时候会首先去加载​​spring​​配置文件,同时保存context。期间应用了一个systemInfo对象为该系统的系统配置




package com . omen . netty . server . sysPojo ;




import io.netty.channel.EventLoopGroup ;


import io.netty.channel.nio.NioEventLoopGroup ;




import org.apache.log4j.Logger ;


import org.springframework.beans.factory.BeanFactory ;


import org.springframework.context.ApplicationContext ;


import org.springframework.context.support.FileSystemXmlApplicationContext ;




import com.omen.netty.utils.StringUtil ;




public class SystemInfo {




private static Logger log = Logger . getLogger ( SystemInfo . class );






public SystemInfo ( String protocolType , Integer port , Integer channelType ) throws Exception {




/**


* 基础校验


*/


if ( StringUtil . isEmpty ( protocolType )){


throw new Exception ( "protocolType is not setted" );


}




if ( port == null ){


throw new Exception ( "port is not setted" );


}




if ( channelType == null ){


throw new Exception ( "channelType is not setted" );


}




this . protocolType = protocolType . toUpperCase ();


this . port = port ;


this . isSsl = false ;


this . channelType = channelType ;


}




public SystemInfo ( String protocolType , Integer port , Integer channelType ,


Integer bossGroupSize , Integer workerGroupSize ) throws Exception {




/**


* 基础校验


*/


if ( StringUtil . isEmpty ( protocolType )){


throw new Exception ( "protocolType is not setted" );


}




if ( port == null ){


throw new Exception ( "port is not setted" );


}


if ( channelType == null ){


throw new Exception ( "channelType is not setted" );


}




this . protocolType = protocolType . toUpperCase ();


this . port = port ;


this . isSsl = false ;


this . channelType = channelType ;


this . bossGroupSize = bossGroupSize ;


this . workerGroupSize = workerGroupSize ;


}






public SystemInfo ( String protocolType , Integer port , Integer channelType , boolean isSsl , String jksPath ,


String jksPwd ) throws Exception {




/**


* 基础校验


*/


if ( StringUtil . isEmpty ( protocolType )){


throw new Exception ( "protocolType is not setted" );


}




if ( port == null ){


throw new Exception ( "port is not setted" );


}


if ( isSsl ){


if ( StringUtil . isEmpty ( jksPath )){


throw new Exception ( "jksPath type is not setted" );


}


if ( StringUtil . isEmpty ( jksPwd )){


throw new Exception ( "jksPwd type is not setted" );


}


}




if ( channelType == null ){


throw new Exception ( "channelType is not setted" );


}




this . protocolType = protocolType . toUpperCase ();


this . port = port ;


this . isSsl = isSsl ;


this . jksPath = jksPath ;


this . jksPwd = jksPwd ;


this . channelType = channelType ;


}




public SystemInfo ( String protocolType , Integer port , Integer channelType , boolean isSsl , String jksPath ,


String jksPwd , Integer bossGroupSize , Integer workerGroupSize ) throws Exception {




/**


* 基础校验


*/


if ( StringUtil . isEmpty ( protocolType )){


throw new Exception ( "protocolType is not setted" );


}




if ( port == null ){


throw new Exception ( "port is not setted" );


}


if ( isSsl ){


if ( StringUtil . isEmpty ( jksPath )){


throw new Exception ( "jksPath type is not setted" );


}


if ( StringUtil . isEmpty ( jksPwd )){


throw new Exception ( "jksPwd type is not setted" );


}


}




if ( channelType == null ){


throw new Exception ( "channelType is not setted" );


}




this . protocolType = protocolType . toUpperCase ();


this . port = port ;


this . isSsl = isSsl ;


this . jksPath = jksPath ;


this . jksPwd = jksPwd ;


this . channelType = channelType ;


this . bossGroupSize = bossGroupSize ;


this . workerGroupSize = workerGroupSize ;


}




private static String protocolType ;




private static Integer port ;




private static Boolean isSsl ;




private static String jksPath ;




private static String jksPwd ;




private static Integer channelType ;




private static ApplicationContext ctx ;




private static EventLoopGroup bossGroup ;




private static EventLoopGroup workerGroup ;




private static Integer bossGroupSize ;




private static Integer workerGroupSize ;






public static void printSysInfo (){


log . info ( "**************SYSTEM INFO******************" );


log . info ( "protocolType : " + protocolType );


log . info ( "port : " + port );


log . info ( "channelType : " + channelType + " (0=NIO 1=OIO)" );


log . info ( "isSsl : " + isSsl );


if (! StringUtil . isEmpty ( jksPath ))


log . info ( "jksPath : " + jksPath );


if (! StringUtil . isEmpty ( jksPwd ))


log . info ( "jksPwd : " + jksPwd );


if ( bossGroupSize != null )


log . info ( "bossGroupSize : " + bossGroupSize );


if ( workerGroupSize != null )


log . info ( "workerGroupSize: " + workerGroupSize );


log . info ( "**************SYSTEM INFO******************" );


}




public static void shutDownGraceFully (){


bossGroup . shutdownGracefully ();


workerGroup . shutdownGracefully ();


}








public static String getProtocolType () {


return protocolType ;


}




public static void setProtocolType ( String protocolType ) {


SystemInfo . protocolType = protocolType ;


}




public static Integer getPort () {


return port ;


}




public static void setPort ( Integer port ) {


SystemInfo . port = port ;


}




public static Boolean getIsSsl () {


return isSsl ;


}




public static void setIsSsl ( Boolean isSsl ) {


SystemInfo . isSsl = isSsl ;


}




public static String getJksPath () {


return jksPath ;


}




public static void setJksPath ( String jksPath ) {


SystemInfo . jksPath = jksPath ;


}




public static String getJksPwd () {


return jksPwd ;


}




public static void setJksPwd ( String jksPwd ) {


SystemInfo . jksPwd = jksPwd ;


}




public static Integer getChannelType () {


return channelType ;


}




public static void setChannelType ( Integer channelType ) {


SystemInfo . channelType = channelType ;


}




public static ApplicationContext getCtx () {


return ctx ;


}




public static void setCtx ( ApplicationContext ctx ) {


SystemInfo . ctx = ctx ;


}




public static Integer getBossGroupSize () {


return bossGroupSize ;


}




public static void setBossGroupSize ( Integer bossGroupSize ) {


SystemInfo . bossGroupSize = bossGroupSize ;


}




public static EventLoopGroup getBossGroup () {


return bossGroup ;


}




public static void setBossGroup ( EventLoopGroup bossGroup ) {


SystemInfo . bossGroup = bossGroup ;


}




public static EventLoopGroup getWorkerGroup () {


return workerGroup ;


}




public static void setWorkerGroup ( EventLoopGroup workerGroup ) {


SystemInfo . workerGroup = workerGroup ;


}




public static Integer getWorkerGroupSize () {


return workerGroupSize ;


}




public static void setWorkerGroupSize ( Integer workerGroupSize ) {


SystemInfo . workerGroupSize = workerGroupSize ;


}




public static void main ( String [] args ) {




}






}





com.omen.netty.server.sysPojo.SystemInfo.java



systemInfo里面涵盖了多个构造函数,这个在后面会有解释(想通过spring直接配置系统参数),目前有个更好的想法就是通过配置map去完成配置系统参数,但是目前还没改好,改好以后会更新代码。

IServer为server接口




package com . omen . netty . server ;






public interface IServer {




public void start () throws Exception ;




public void stop () throws Exception ;




public void restart () throws Exception ;


}





com.omen.netty.server.IServer.java



我们的实现类




package com . omen . netty . server ;




import io.netty.channel.Channel ;


import io.netty.channel.ChannelFutureListener ;




import org.springframework.beans.factory.annotation.Autowired ;




import com.omen.netty.server.factory.ServerChannelFactory ;


import com.omen.netty.server.sysPojo.SystemInfo ;




public class BasicServer implements IServer {




private Channel acceptorChannel ;




@Autowired


ServerChannelFactory serverChannelFactory ;








@Override


public void start () throws Exception {


try {


acceptorChannel = serverChannelFactory . createAcceptorChannel ();


acceptorChannel . closeFuture (). sync ();


} finally {


//优雅退出,释放线程组资源


SystemInfo . shutDownGraceFully ();


}


}




@Override


public void stop () throws Exception {


try {


if ( acceptorChannel != null )


acceptorChannel . close (). addListener ( ChannelFutureListener . CLOSE );


} finally {


//优雅退出,释放线程组资源


SystemInfo . shutDownGraceFully ();


}


}




@Override


public void restart () throws Exception {


stop ();


start ();


}




}





com.omen.netty.server.BasicServer.java



在我们的实现类中,start方法和stop方法最终都会调用systemInfo的shutDownGracefully以便释放资源。其中start当中会等待服务端链路关闭以后main函数才退出。

2.几个工厂

先看代码

channel工厂




package com . omen . netty . server . factory ;




import io.netty.bootstrap.ServerBootstrap ;


import io.netty.channel.Channel ;


import io.netty.channel.ChannelFuture ;


import io.netty.channel.ChannelInitializer ;


import io.netty.channel.socket.SocketChannel ;




import org.apache.log4j.Logger ;




import com.omen.netty.exception.SysErrException ;


import com.omen.netty.server.sysPojo.ProtocolType ;


import com.omen.netty.server.sysPojo.SystemInfo ;




public class ServerChannelFactory {






private static Logger log = Logger . getLogger ( ServerChannelFactory . class );




public Channel createAcceptorChannel () throws SysErrException {




final ServerBootstrap serverBootstrap = ServerBootstrapFactory . createServerBootstrap ();


serverBootstrap . childHandler ( getChildHandler ());


log . info ( "创建Server..." );


try {


ChannelFuture channelFuture = serverBootstrap . bind ( SystemInfo . getPort ()). sync ();


channelFuture . awaitUninterruptibly ();


if ( channelFuture . isSuccess ()) {


SystemInfo . printSysInfo ();


return channelFuture . channel ();


} else {


String errMsg = "Failed to open socket! Cannot bind to port: " + SystemInfo . getPort ()+ "!" ;


log . error ( errMsg );


throw new SysErrException ( errMsg );


}




} catch ( Exception e ){


throw new SysErrException ( e );


}


}






private static ChannelInitializer < SocketChannel > getChildHandler () throws SysErrException {


if ( ProtocolType . HTTP . equals ( SystemInfo . getProtocolType ())


|| ProtocolType . HTTPS . equals ( SystemInfo . getProtocolType ())){


return ( ChannelInitializer < SocketChannel >) SystemInfo . getCtx (). getBean ( "httpServerInitializer" );


}




else if ( ProtocolType . TCP . equals ( SystemInfo . getProtocolType ()))


return ( ChannelInitializer < SocketChannel >) SystemInfo . getCtx (). getBean ( "tcpServerInitializer" );




else if ( ProtocolType . CUSTOM . equals ( SystemInfo . getProtocolType ()))


return ( ChannelInitializer < SocketChannel >) SystemInfo . getCtx (). getBean ( "customServerInitializer" );




else {


String errMsg = "undefined protocol:" + SystemInfo . getProtocolType ()+ "!" ;


throw new SysErrException ( errMsg );


}




}


}





com.omen.netty.server.factory.ServerChannelFactory.java



ServerBootStrap工厂




package com . omen . netty . server . factory ;






import io.netty.bootstrap.ServerBootstrap ;


import io.netty.channel.EventLoopGroup ;


import io.netty.channel.nio.NioEventLoopGroup ;


import io.netty.channel.oio.OioEventLoopGroup ;


import io.netty.channel.socket.nio.NioServerSocketChannel ;


import io.netty.channel.socket.oio.OioServerSocketChannel ;




import com.omen.netty.server.sysPojo.ChannelType ;


import com.omen.netty.server.sysPojo.SystemInfo ;






/**


* @author liyidong


* @version 1.0


* @created 2014-12-16 上午11:14:33


* @function:工厂模式 server服务器 ServerBootstrap创建


*/


public class ServerBootstrapFactory {


private ServerBootstrapFactory () {


}


public static ServerBootstrap createServerBootstrap () throws UnsupportedOperationException {




ServerBootstrap serverBootstrap = new ServerBootstrap ();


switch ( SystemInfo . getChannelType ()) {


case ChannelType . NIO :


EventLoopGroup bossGroup = new NioEventLoopGroup ();


EventLoopGroup workerGroup = new NioEventLoopGroup ();


serverBootstrap . group ( bossGroup , workerGroup );


SystemInfo . setBossGroup ( bossGroup );


SystemInfo . setWorkerGroup ( workerGroup );


serverBootstrap . channel ( NioServerSocketChannel . class );




return serverBootstrap ;


//TODO


case ChannelType . OIO :


serverBootstrap . group ( new OioEventLoopGroup ());


serverBootstrap . channel ( OioServerSocketChannel . class );




return serverBootstrap ;


default :


throw new UnsupportedOperationException ( "Failed to create ServerBootstrap, " + SystemInfo . getChannelType () + " not supported!" );


}


}


}





com.omen.netty.server.factory.ServerBootstrapFactory.java



以上是两个关键类

在ServerBootstrapFactory中

我们创建了两个NioEventLoopGroup实例。NioEventLoopGroup是一个多线程的I/O操作事件循环池,Netty为各种传输方式提供了多种EventLoopGroup的实现。NioEventLoopGroup专门用于网络事件的处理,实际上它们就是Reactor线程组。这里创建两个的原因是一个用于服务端接受客服端的链接,另一个用于进行SocketChannel的网络读写。

我们还在ServerBootstrapFactory中创建了ServerBootstrap对象,它是netty用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度。

传送门,讲解ServerBootstrap工作原理

​ ​

接下来我们调用了ServerBootstrap的group方法,将两个NIO线程组当作入参传递到ServerBootstrap,借着设置创建的channel类型为NioServerSocketChannel,他的功能是监听网络事件类型并创建channel。

在ServerChannelFactory中

我们对ServerBootstrap绑定了I/O事件的处理类,它的作用类似于Reactor当中的handler,主要用于处理网络是I/O事件,例如记录日志、对消息进行编解码等。

ServerBootstrap配置完成以后,调用它的bind方法绑定监听端口,随后,调用它的同步阻塞方法sync等待绑定完成。完成之后netty会返回一个channelFuture,它的功能类主要用于异步操作的通知回调。