大家好,今天来为大家分享ssl自签网站源码分享是什么的一些知识点,和自定义ssl证书的问题解析,大家要是都明白,那么可以忽略,如果不太清楚的话可以看看本篇文章,相信很大概率可以解决您的问题,接下来我们就一起来看看吧!
思维导图
前言
本文主要讲述Netty框架的一些特性以及重要组件,希望看完之后能对Netty框架有一个比较直观的感受,希望能帮助读者快速入门Netty,减少一些弯路。
一、Netty概述
官方的介绍:
Nettyisanasynchronousevent-drivennetworkapplicationframeworkforrapiddevelopmentofmaintainablehighperformanceprotocolservers&clients.
Netty是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。
二、为什么使用Netty
从官网上介绍,Netty是一个网络应用程序框架,开发服务器和客户端。也就是用于网络编程的一个框架。既然是网络编程,Socket就不谈了,为什么不用NIO呢?
2.1NIO的缺点
对于这个问题,之前我写了一篇文章《NIO入门》对NIO有比较详细的介绍,NIO的主要问题是:
NIO的类库和API繁杂,学习成本高,你需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等。需要熟悉Java多线程编程。这是因为NIO编程涉及到Reactor模式,你必须对多线程和网络编程非常熟悉,才能写出高质量的NIO程序。臭名昭著的epollbug。它会导致Selector空轮询,最终导致CPU100%。直到JDK1.7版本依然没得到根本性的解决。
2.2Netty的优点
相对地,Netty的优点有很多:
API使用简单,学习成本低。功能强大,内置了多种解码编码器,支持多种协议。性能高,对比其他主流的NIO框架,Netty的性能最优。社区活跃,发现BUG会及时修复,迭代版本周期短,不断加入新的功能。Dubbo、Elasticsearch都采用了Netty,质量得到验证。
三、架构图
上面这张图就是在官网首页的架构图,我们从上到下分析一下。
绿色的部分Core核心模块,包括零拷贝、API库、可扩展的事件模型。
橙色部分ProtocolSupport协议支持,包括Http协议、webSocket、SSL(安全套接字协议)、谷歌Protobuf协议、zlib/gzip压缩与解压缩、LargeFileTransfer大文件传输等等。
红色的部分TransportServices传输服务,包括Socket、Datagram、HttpTunnel等等。
以上可看出Netty的功能、协议、传输方式都比较全,比较强大。
四、永远的HelloWord
首先搭建一个HelloWord工程,先熟悉一下API,还有为后面的学习做铺垫。以下面这张图为依据:
4.1引入Maven依赖
使用的版本是4.1.20,相对比较稳定的一个版本。
<dependency>\n<groupId>io.netty</groupId>\n<artifactId>netty-all</artifactId>\n<version>4.1.20.Final</version>\n</dependency>
4.2创建服务端启动类
publicclassMyServer{\npublicstaticvoidmain(String[]args)throwsException{\n//创建两个线程组boosGroup、workerGroup\nEventLoopGroupbossGroup=newNioEventLoopGroup();\nEventLoopGroupworkerGroup=newNioEventLoopGroup();\ntry{\n//创建服务端的启动对象,设置参数\nServerBootstrapbootstrap=newServerBootstrap();\n//设置两个线程组boosGroup和workerGroup\nbootstrap.group(bossGroup,workerGroup)\n//设置服务端通道实现类型\n.channel(NioServerSocketChannel.class)\n//设置线程队列得到连接个数\n.option(ChannelOption.SO_BACKLOG,128)\n//设置保持活动连接状态\n.childOption(ChannelOption.SO_KEEPALIVE,true)\n//使用匿名内部类的形式初始化通道对象\n.childHandler(newChannelInitializer<SocketChannel>(){\n@Override\nprotectedvoidinitChannel(SocketChannelsocketChannel)throwsException{\n//给pipeline管道设置处理器\nsocketChannel.pipeline().addLast(newMyServerHandler());\n}\n});//给workerGroup的EventLoop对应的管道设置处理器\nSystem.out.println(&34;);\n//绑定端口号,启动服务端\nChannelFuturechannelFuture=bootstrap.bind(6666).sync();\n//对关闭通道进行监听\nchannelFuture.channel().closeFuture().sync();\n}finally{\nbossGroup.shutdownGracefully();\nworkerGroup.shutdownGracefully();\n}\n}\n}
4.3创建服务端处理器
/**\n*自定义的Handler需要继承Netty规定好的HandlerAdapter\n*才能被Netty框架所关联,有点类似SpringMVC的适配器模式\n**/\npublicclassMyServerHandlerextendsChannelInboundHandlerAdapter{\n\n@Override\npublicvoidchannelRead(ChannelHandlerContextctx,Objectmsg)throwsException{\n//获取客户端发送过来的消息\nByteBufbyteBuf=(ByteBuf)msg;\nSystem.out.println(&34;+ctx.channel().remoteAddress()+&34;+byteBuf.toString(CharsetUtil.UTF_8));\n}\n\n@Override\npublicvoidchannelReadComplete(ChannelHandlerContextctx)throwsException{\n//发送消息给客户端\nctx.writeAndFlush(Unpooled.copiedBuffer(&34;,CharsetUtil.UTF_8));\n}\n\n@Override\npublicvoidexceptionCaught(ChannelHandlerContextctx,Throwablecause)throwsException{\n//发生异常,关闭通道\nctx.close();\n}\n}
4.4创建客户端启动类
publicclassMyClient{\n\npublicstaticvoidmain(String[]args)throwsException{\nNioEventLoopGroupeventExecutors=newNioEventLoopGroup();\ntry{\n//创建bootstrap对象,配置参数\nBootstrapbootstrap=newBootstrap();\n//设置线程组\nbootstrap.group(eventExecutors)\n//设置客户端的通道实现类型\n.channel(NioSocketChannel.class)\n//使用匿名内部类初始化通道\n.handler(newChannelInitializer<SocketChannel>(){\n@Override\nprotectedvoidinitChannel(SocketChannelch)throwsException{\n//添加客户端通道的处理器\nch.pipeline().addLast(newMyClientHandler());\n}\n});\nSystem.out.println(&34;);\n//连接服务端\nChannelFuturechannelFuture=bootstrap.connect(&34;,6666).sync();\n//对通道关闭进行监听\nchannelFuture.channel().closeFuture().sync();\n}finally{\n//关闭线程组\neventExecutors.shutdownGracefully();\n}\n}\n}
4.5创建客户端处理器
publicclassMyClientHandlerextendsChannelInboundHandlerAdapter{\n\n@Override\npublicvoidchannelActive(ChannelHandlerContextctx)throwsException{\n//发送消息到服务端\nctx.writeAndFlush(Unpooled.copiedBuffer(&34;,CharsetUtil.UTF_8));\n}\n\n@Override\npublicvoidchannelRead(ChannelHandlerContextctx,Objectmsg)throwsException{\n//接收服务端发送过来的消息\nByteBufbyteBuf=(ByteBuf)msg;\nSystem.out.println(&34;+ctx.channel().remoteAddress()+&34;+byteBuf.toString(CharsetUtil.UTF_8));\n}\n}
4.6测试
先启动服务端,再启动客户端,就可以看到结果:
MyServer打印结果:
MyClient打印结果:
五、Netty的特性与重要组件
5.1taskQueue任务队列
如果Handler处理器有一些长时间的业务处理,可以交给taskQueue异步处理。怎么用呢,请看代码演示:
publicclassMyServerHandlerextendsChannelInboundHandlerAdapter{\n\n@Override\npublicvoidchannelRead(ChannelHandlerContextctx,Objectmsg)throwsException{\n//获取到线程池eventLoop,添加线程,执行\nctx.channel().eventLoop().execute(newRunnable(){\n@Override\npublicvoidrun(){\ntry{\n//长时间操作,不至于长时间的业务操作导致Handler阻塞\nThread.sleep(1000);\nSystem.out.println(&34;);\n}catch(Exceptione){\ne.printStackTrace();\n}\n}\n});\n}\n}
我们打一个debug调试,是可以看到添加进去的taskQueue有一个任务。
5.2scheduleTaskQueue延时任务队列
延时任务队列和上面介绍的任务队列非常相似,只是多了一个可延迟一定时间再执行的设置,请看代码演示:
ctx.channel().eventLoop().schedule(newRunnable(){\n@Override\npublicvoidrun(){\ntry{\n//长时间操作,不至于长时间的业务操作导致Handler阻塞\nThread.sleep(1000);\nSystem.out.println(&34;);\n}catch(Exceptione){\ne.printStackTrace();\n}\n}\n},5,TimeUnit.SECONDS);//5秒后执行
依然打开debug进行调试查看,我们可以有一个scheduleTaskQueue任务待执行中
5.3Future异步机制
在搭建HelloWord工程的时候,我们看到有一行这样的代码:
ChannelFuturechannelFuture=bootstrap.connect(&34;,6666);
很多操作都返回这个ChannelFuture对象,究竟这个ChannelFuture对象是用来做什么的呢?
ChannelFuture提供操作完成时一种异步通知的方式。一般在Socket编程中,等待响应结果都是同步阻塞的,而Netty则不会造成阻塞,因为ChannelFuture是采取类似观察者模式的形式进行获取结果。请看一段代码演示:
//添加监听器\nchannelFuture.addListener(newChannelFutureListener(){\n//使用匿名内部类,ChannelFutureListener接口\n//重写operationComplete方法\n@Override\npublicvoidoperationComplete(ChannelFuturefuture)throwsException{\n//判断是否操作成功\nif(future.isSuccess()){\nSystem.out.println(&34;);\n}else{\nSystem.out.println(&34;);\n}\n}\n});
5.4Bootstrap与ServerBootStrap
Bootstrap和ServerBootStrap是Netty提供的一个创建客户端和服务端启动器的工厂类,使用这个工厂类非常便利地创建启动类,根据上面的一些例子,其实也看得出来能大大地减少了开发的难度。首先看一个类图:
可以看出都是继承于AbstractBootStrap抽象类,所以大致上的配置方法都相同。
一般来说,使用Bootstrap创建启动器的步骤可分为以下几步:
在上一篇文章《Reactor模式》中,我们就讲过服务端要使用两个线程组:
bossGroup用于监听客户端连接,专门负责与客户端创建连接,并把连接注册到workerGroup的Selector中。workerGroup用于处理每一个连接发生的读写事件。
一般创建线程组直接使用以下new就完事了:
EventLoopGroupbossGroup=newNioEventLoopGroup();\nEventLoopGroupworkerGroup=newNioEventLoopGroup();
有点好奇的是,既然是线程组,那线程数默认是多少呢?深入源码:
//使用一个常量保存\nprivatestaticfinalintDEFAULT_EVENT_LOOP_THREADS;\n\nstatic{\n//NettyRuntime.availableProcessors()*2,cpu核数的两倍赋值给常量\nDEFAULT_EVENT_LOOP_THREADS=Math.max(1,SystemPropertyUtil.getInt(\n&34;,NettyRuntime.availableProcessors()*2));\n\nif(logger.isDebugEnabled()){\nlogger.debug(&34;,DEFAULT_EVENT_LOOP_THREADS);\n}\n}\n\nprotectedMultithreadEventLoopGroup(intnThreads,Executorexecutor,Object…args){\n//如果不传入,则使用常量的值,也就是cpu核数的两倍\nsuper(nThreads==0?DEFAULT_EVENT_LOOP_THREADS:nThreads,executor,args);\n}
通过源码可以看到,默认的线程数是cpu核数的两倍。假设想自定义线程数,可以使用有参构造器:
//设置bossGroup线程数为1\nEventLoopGroupbossGroup=newNioEventLoopGroup(1);\n//设置workerGroup线程数为16\nEventLoopGroupworkerGroup=newNioEventLoopGroup(16);
这个方法用于设置通道类型,当建立连接后,会根据这个设置创建对应的Channel实例。
使用debug模式可以看到
通道类型有以下:
NioSocketChannel:异步非阻塞的客户端TCPSocket连接。
NioServerSocketChannel:异步非阻塞的服务器端TCPSocket连接。
常用的就是这两个通道类型,因为是异步非阻塞的。所以是首选。
OioSocketChannel:同步阻塞的客户端TCPSocket连接。
OioServerSocketChannel:同步阻塞的服务器端TCPSocket连接。
稍微在本地调试过,用起来和Nio有一些不同,是阻塞的,所以API调用也不一样。因为是阻塞的IO,几乎没什么人会选择使用Oio,所以也很难找到例子。我稍微琢磨了一下,经过几次报错之后,总算调通了。代码如下:
//server端代码,跟上面几乎一样,只需改三个地方\n//这个地方使用的是OioEventLoopGroup\nEventLoopGroupbossGroup=newOioEventLoopGroup();\nServerBootstrapbootstrap=newServerBootstrap();\nbootstrap.group(bossGroup)//只需要设置一个线程组boosGroup\n.channel(OioServerSocketChannel.class)//设置服务端通道实现类型\n\n//client端代码,只需改两个地方\n//使用的是OioEventLoopGroup\nEventLoopGroupeventExecutors=newOioEventLoopGroup();\n//通道类型设置为OioSocketChannel\nbootstrap.group(eventExecutors)//设置线程组\n.channel(OioSocketChannel.class)//设置客户端的通道实现类型
NioSctpChannel:异步的客户端Sctp(StreamControlTransmissionProtocol,流控制传输协议)连接。
NioSctpServerChannel:异步的Sctp服务器端连接。
本地没启动成功,网上看了一些网友的评论,说是只能在linux环境下才可以启动。从报错信息看:SCTPnotsupportedonthisplatform,不支持这个平台。因为我电脑是window系统,所以网友说的有点道理。
首先说一下这两个的区别。
option()设置的是服务端用于接收进来的连接,也就是boosGroup线程。
childOption()是提供给父管道接收到的连接,也就是workerGroup线程。
搞清楚了之后,我们看一下常用的一些设置有哪些:
SocketChannel参数,也就是childOption()常用的参数:
SO_RCVBUFSocket参数,TCP数据接收缓冲区大小。TCP_NODELAYTCP参数,立即发送数据,默认值为Ture。SO_KEEPALIVESocket参数,连接保活,默认值为False。启用该功能时,TCP会主动探测空闲连接的有效性。
ServerSocketChannel参数,也就是option()常用参数:
SO_BACKLOGSocket参数,服务端接受连接的队列长度,如果队列已满,客户端连接将被拒绝。默认值,Windows为200,其他为128。
由于篇幅限制,其他就不列举了,大家可以去网上找资料看看,了解一下。
ChannelPipeline是Netty处理请求的责任链,ChannelHandler则是具体处理请求的处理器。实际上每一个channel都有一个处理器的流水线。
在Bootstrap中childHandler()方法需要初始化通道,实例化一个ChannelInitializer,这时候需要重写initChannel()初始化通道的方法,装配流水线就是在这个地方进行。代码演示如下:
//使用匿名内部类的形式初始化通道对象\nbootstrap.childHandler(newChannelInitializer<SocketChannel>(){\n@Override\nprotectedvoidinitChannel(SocketChannelsocketChannel)throwsException{\n//给pipeline管道设置自定义的处理器\nsocketChannel.pipeline().addLast(newMyServerHandler());\n}\n});
处理器Handler主要分为两种:
ChannelInboundHandlerAdapter(入站处理器)、ChannelOutboundHandler(出站处理器)
入站指的是数据从底层javaNIOChannel到Netty的Channel。
出站指的是通过Netty的Channel来操作底层的javaNIOChannel。
ChannelInboundHandlerAdapter处理器常用的事件有:
注册事件fireChannelRegistered。连接建立事件fireChannelActive。读事件和读完成事件fireChannelRead、fireChannelReadComplete。异常通知事件fireExceptionCaught。用户自定义事件fireUserEventTriggered。Channel可写状态变化事件fireChannelWritabilityChanged。连接关闭事件fireChannelInactive。
ChannelOutboundHandler处理器常用的事件有:
端口绑定bind。连接服务端connect。写事件write。刷新时间flush。读事件read。主动断开连接disconnect。关闭channel事件close。
还有一个类似的handler(),主要用于装配parent通道,也就是bossGroup线程。一般情况下,都用不上这个方法。
提供用于服务端或者客户端绑定服务器地址和端口号,默认是异步启动。如果加上sync()方法则是同步。
有五个同名的重载方法,作用都是用于绑定地址端口号。不一一介绍了。
//释放掉所有的资源,包括创建的线程\nbossGroup.shutdownGracefully();\nworkerGroup.shutdownGracefully();
会关闭所有的childChannel。关闭之后,释放掉底层的资源。
5.5Channel
Channel是什么?不妨看一下官方文档的说明:
AnexustoanetworksocketoracomponentwhichiscapableofI/Ooperationssuchasread,write,connect,andbind
翻译大意:一种连接到网络套接字或能进行读、写、连接和绑定等I/O操作的组件。
如果上面这段说明比较抽象,下面还有一段说明:
Achannelprovidesauser:
thecurrentstateofthechannel(e.g.isitopen?isitconnected?),theconfigurationparametersofthechannel(e.g.receivebuffersize),theI/Ooperationsthatthechannelsupports(e.g.read,write,connect,andbind),andtheChannelPipelinewhichhandlesallI/Oeventsandrequestsassociatedwiththechannel.
翻译大意:
channel为用户提供:
通道当前的状态(例如它是打开?还是已连接?)channel的配置参数(例如接收缓冲区的大小)channel支持的IO操作(例如读、写、连接和绑定),以及处理与channel相关联的所有IO事件和请求的ChannelPipeline。
booleanisOpen();//如果通道打开,则返回true\nbooleanisRegistered();//如果通道注册到EventLoop,则返回true\nbooleanisActive();//如果通道处于活动状态并且已连接,则返回true\nbooleanisWritable();//当且仅当I/O线程将立即执行请求的写入操作时,返回true。
以上就是获取channel的四种状态的方法。
获取单条配置信息,使用getOption(),代码演示:
ChannelConfigconfig=channel.config();//获取配置参数\n//获取ChannelOption.SO_BACKLOG参数,\nIntegersoBackLogConfig=config.getOption(ChannelOption.SO_BACKLOG);\n//因为我启动器配置的是128,所以我这里获取的soBackLogConfig=128
获取多条配置信息,使用getOptions(),代码演示:
ChannelConfigconfig=channel.config();\nMap<ChannelOption<?>,Object>options=config.getOptions();\nfor(Map.Entry<ChannelOption<?>,Object>entry:options.entrySet()){\nSystem.out.println(entry.getKey()+&34;+entry.getValue());\n}\n/**\nSO_REUSEADDR:false\nWRITE_BUFFER_LOW_WATER_MARK:32768\nWRITE_BUFFER_WATER_MARK:WriteBufferWaterMark(low:32768,high:65536)\nSO_BACKLOG:128\n以下省略…\n*/
写操作,这里演示从服务端写消息发送到客户端:
@Override\npublicvoidchannelRead(ChannelHandlerContextctx,Objectmsg)throwsException{\nctx.channel().writeAndFlush(Unpooled.copiedBuffer(&34;,CharsetUtil.UTF_8));\n}
客户端控制台:
//收到服务端/127.0.0.1:6666的消息:这波啊,这波是肉蛋葱鸡~
连接操作,代码演示:
ChannelFutureconnect=channelFuture.channel().connect(newInetSocketAddress(&34;,6666));//一般使用启动器,这种方式不常用
通过channel获取ChannelPipeline,并做相关的处理:
//获取ChannelPipeline对象\nChannelPipelinepipeline=ctx.channel().pipeline();\n//往pipeline中添加ChannelHandler处理器,装配流水线\npipeline.addLast(newMyServerHandler());
5.6Selector
在NioEventLoop中,有一个成员变量selector,这是nio包的Selector,在之前《NIO入门》中,我已经讲过Selector了。
Netty中的Selector也和NIO的Selector是一样的,就是用于监听事件,管理注册到Selector中的channel,实现多路复用器。
5.7PiPeline与ChannelPipeline
在前面介绍Channel时,我们知道可以在channel中装配ChannelHandler流水线处理器,那一个channel不可能只有一个channelHandler处理器,肯定是有很多的,既然是很多channelHandler在一个流水线工作,肯定是有顺序的。
于是pipeline就出现了,pipeline相当于处理器的容器。初始化channel时,把channelHandler按顺序装在pipeline中,就可以实现按序执行channelHandler了。
在一个Channel中,只有一个ChannelPipeline。该pipeline在Channel被创建的时候创建。ChannelPipeline包含了一个ChannelHander形成的列表,且所有ChannelHandler都会注册到ChannelPipeline中。
5.8ChannelHandlerContext
在Netty中,Handler处理器是有我们定义的,上面讲过通过集成入站处理器或者出站处理器实现。这时如果我们想在Handler中获取pipeline对象,或者channel对象,怎么获取呢。
于是Netty设计了这个ChannelHandlerContext上下文对象,就可以拿到channel、pipeline等对象,就可以进行读写等操作。
通过类图,ChannelHandlerContext是一个接口,下面有三个实现类。
实际上ChannelHandlerContext在pipeline中是一个链表的形式。看一段源码就明白了:
//ChannelPipeline实现类DefaultChannelPipeline的构造器方法\nprotectedDefaultChannelPipeline(Channelchannel){\nthis.channel=ObjectUtil.checkNotNull(channel,&34;);\nsucceededFuture=newSucceededChannelFuture(channel,null);\nvoidPromise=newVoidChannelPromise(channel,true);\n//设置头结点head,尾结点tail\ntail=newTailContext(this);\nhead=newHeadContext(this);\n\nhead.next=tail;\ntail.prev=head;\n}
下面我用一张图来表示,会更加清晰一点:
5.9EventLoopGroup
我们先看一下EventLoopGroup的类图:
其中包括了常用的实现类NioEventLoopGroup。OioEventLoopGroup在前面的例子中也有使用过。
从Netty的架构图中,可以知道服务器是需要两个线程组进行配合工作的,而这个线程组的接口就是EventLoopGroup。
每个EventLoopGroup里包括一个或多个EventLoop,每个EventLoop中维护一个Selector实例。
我们不妨看一段DefaultEventExecutorChooserFactory的源码:
privatefinalAtomicIntegeridx=newAtomicInteger();\nprivatefinalEventExecutor[]executors;\n\n@Override\npublicEventExecutornext(){\n//idx.getAndIncrement()相当于idx++,然后对任务长度取模\nreturnexecutors[idx.getAndIncrement()&executors.length-1];\n}
这段代码可以确定执行的方式是轮询机制,接下来debug调试一下:
它这里还有一个判断,如果线程数不是2的N次方,则采用取模算法实现。
@Override\npublicEventExecutornext(){\nreturnexecutors[Math.abs(idx.getAndIncrement()%executors.length)];\n}\n\n
能力有限,如果有什么错误或者不当之处,请大家批评指正,一起学习交流!
本文为阿里云原创内容,未经允许不得转载。
文章到此结束,如果本次分享的ssl自签网站源码分享是什么和自定义ssl证书的问题解决了您的问题,那么我们由衷的感到高兴!
