從Java1.4開始, Java引入了non-blocking IO,簡稱NIO。NIO與傳統socket最大的不同就是引入了Channel和多路復用selector的概念。傳統的socket是基於stream的,它是單向的,有InputStream表示read和OutputStream表示寫。而Channel是雙工的,既支持讀也支持寫,channel的讀/寫都是面向Buffer。 NIO中引入的多路復用Selector機制(如果是linux系統,則應用的epoll事件通知機制)可使一個線程同時監聽多個Channel上發生的事件。 雖然Java NIO相比於以往確實是一個大的突破,但是如果要真正上手進行開發,且想要開發出好的一個服務端網絡程序,那么你得要花費一點功夫了,畢竟Java NIO只是提供了一大堆的API而已,對於一般的軟件開發人員來說只能呵呵了。因此,社區中就涌現了很多基於Java NIO的網絡應用框架,其中以Apache的Mina,以及Netty最為出名。
一、Netty實現Socket
1、Netty服務端示例:
EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap(); // (2)
b.group(bossGroup, workerGroup) // (3)
.channel(NioServerSocketChannel.class) // (4)
.handler(new LoggingHandler()) // (5)
.childHandler(new ChannelInitializer<SocketChannel>() { // (6)
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new DiscardServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128) // (7)
.childOption(ChannelOption.SO_KEEPALIVE, true); // (8)
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(port).sync(); // (9)
// Wait until the server socket is closed.
// In this example, this does not happen, but you can do that to gracefully
// shut down your server.
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
上面這段代碼展示了服務端的一個基本步驟:
(1)、初始化用於Acceptor的主"線程池"以及用於I/O工作的從"線程池";
(2)、初始化ServerBootstrap實例, 此實例是netty服務端應用開發的入口;
(3)、通過ServerBootstrap的group方法,設置(1)中初始化的主從"線程池";
(4)、指定通道channel的類型,由於是服務端,故而是NioServerSocketChannel;
(5)、設置ServerSocketChannel的處理器(此處不詳述,后面的系列會進行深入分析)
(6)、設置子通道也就是SocketChannel的處理器, 其內部是實際業務開發的"主戰場"
(7)、配置ServerSocketChannel的選項
(8)、配置子通道也就是SocketChannel的選項
(9)、綁定並偵聽某個端口
2、Netty客戶示例:
public class TimeClient {
public static void main(String[] args) throws Exception {
String host = args[0];
int port = Integer.parseInt(args[1]);
EventLoopGroup workerGroup = new NioEventLoopGroup(); // (1)
try {
Bootstrap b = new Bootstrap(); // (2)
b.group(workerGroup); // (3)
b.channel(NioSocketChannel.class); // (4)
b.option(ChannelOption.SO_KEEPALIVE, true); // (5)
b.handler(new ChannelInitializer<SocketChannel>() { // (6)
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TimeClientHandler());
}
});
// Start the client.
ChannelFuture f = b.connect(host, port).sync(); // (7)
// Wait until the connection is closed.
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
}
}
}
客戶端的開發步驟和服務端都差不多:
(1)、初始化用於連接及I/O工作的"線程池";
(2)、初始化Bootstrap實例, 此實例是netty客戶端應用開發的入口;
(3)、通過Bootstrap的group方法,設置(1)中初始化的"線程池";
(4)、指定通道channel的類型,由於是客戶端,故而是NioSocketChannel;
(5)、設置SocketChannel的選項(此處不詳述,后面的系列會進行深入分析);
(6)、設置SocketChannel的處理器, 其內部是實際業務開發的"主戰場";
(7)、連接指定的服務地址;
二、Netty實現SSLSocket
netty創建服務端時,在初始化channl時,把handler加入ChannelPipeline時,在最開始那個handler設為SslHandler:
SSLContext sslContext = SslUtil.createSSLContext(type ,path ,password); ///SslUtil自定義類
SSLEngine sslEngine = sslContext.createSSLEngine(); sslEngine.setUseClientMode(false); /// 是否使用客戶端模式 sslEngine.setNeedClientAuth(false); ////是否需要驗證客戶端
pipeline.addLast("ssl", new SslHandler(sslEngine));
SslUtil類:
private static volatile SSLContext sslContext = null;
public static SSLContext createSSLContext(String type ,String path ,String password) throws Exception {
if(null == sslContext){
synchronized (SslUtil.class) {
if(null == sslContext){
KeyStore ks = KeyStore.getInstance(type); /// "JKS"
InputStream ksInputStream = new FileInputStream(path); /// 證書存放地址
ks.load(ksInputStream, password.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password.toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), null, null);
}
}
}
return sslContext;
}
參考博客:
[1]Netty4.x 源碼實戰系列(一): 深入理解ServerBootstrap 與 Bootstrap (1)
[2]Netty 實現SSL安全連接(wss://)
[3]Netty筆記之三:Netty實現Socket編程
[4]Netty框架學習之(一):Netty框架簡介
[5]深入理解Netty框架