通過前面的文章我們可以瞭解Nio以及Nio中使用的Reactor模式和多路複用器,這是非常必要的因爲netty是java編寫的一個通訊框架,底層也是使用jdk提供的api來和操作系統交互的,所以這麼來說前面兩章其實也是學習了netty最底層的實現,下面來看看netty實現通訊……
一、客戶端
package com.test3;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* 客戶端
* @author whd
* @date 2017年9月24日 上午12:26:34
*/
public class Client {
public void start(String host, int port) {
// Netty中的線程池,管理者eventLoop,之前說過一個EventLoop對應着一個Thread線程
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 引導類,這個類負責Netty客戶端啓,在啓動時相關類的初始化
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup);
// 設置底層通訊方式,可以是BioSocketChannel也可以是EpollSocketChannel
// 這就是Netty的好處之一,我們變化不同的通訊方式的時候只需要修改很少的一部分,或者使用一個簡單的配置文件就OK了,幾乎不用修改代碼。
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.SO_KEEPALIVE, true); // 其實就是設置tcp的相關屬性
bootstrap.handler(new InitializerChannel()); // 設置channel處理類
// 向指定的ip+port進行tcp連接
ChannelFuture future = bootstrap.connect(host, port).sync();
future.channel().closeFuture().sync();
} catch (Exception e) {
System.out.println(e.getMessage());
;
} finally {
// 連接的優雅關閉,和java線程池shutdown相似,在調用關閉方法後不接受其他請求但也不會立刻關閉知道已有任務執行完纔會關閉!!!
workerGroup.shutdownGracefully();
}
}
// channelHandler的初始化,這個類中添加的就是Netty的請求處理類。
// 也就是之前我們學習的Netty的ChannelHandler、ChannelPipeline
// ,我們知道netty中所有的請求事件處理類都在容器ChannelPipeline中。
// 這裏就是是channel相關的……
private class InitializerChannel extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// TODO Auto-generated method stub
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ClientHandler());
}
}
public static void main(String[] args) {
String ip = "127.0.0.1";
int port = 8080;
if (null != args && args.length == 2) {
ip = args[0];
port = Integer.valueOf(args[1]);
}
Client client = new Client();
client.start(ip, port);
}
}
二、客戶端處理類
package com.test3;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* 客戶端對請求響應事件的處理
* @author whd
* @date 2017年9月24日 上午12:27:22
*/
public class ClientHandler extends ChannelHandlerAdapter {
// client 連接 server server返回消息觸發該方法
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
byte[] req = "client active".getBytes();
ByteBuf buf = Unpooled.buffer(req.length);
buf.writeBytes(req);
ctx.writeAndFlush(buf);// 將請求消息發送給服務端
}
// 該channel有讀事件發生時觸發該方法
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "UTF-8");
System.out.println(body);
}
// 當讀事件結束後觸發該方法
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();// 將消息發送隊列中的消息寫入到SocketChannel中發送給對方。
ctx.close();
}
// 出現異常觸發該方法
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
三、服務端
package com.test3;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* 服務端
* @author whd
* @date 2017年9月24日 上午12:27:08
*/
public class Server {
public void bind(int port) throws InterruptedException {
// 配置NIO線程組
EventLoopGroup bossGroup = new NioEventLoopGroup();// 連接管理線程池
EventLoopGroup workerGroup = new NioEventLoopGroup();// 具體業務處理線程池
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.option(ChannelOption.SO_BACKLOG, 10);
bootstrap.childHandler(new ChildChannelHandler());
// 綁定端口,同步等待成功
ChannelFuture future = bootstrap.bind(port).sync();
// 等待服務端監聽端口關閉,等待服務端鏈路關閉之後main函數才退出
future.channel().closeFuture().sync();
} finally {
// 優雅退出,釋放線程池資源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ServerHandler());
}
}
public static void main(String[] args) throws InterruptedException {
int port = 8080;
if (args != null && args.length > 0) {
port = Integer.parseInt(args[0]);
}
new Server().bind(port);
}
}
四、服務端處理類
package com.test3;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* 對客戶端請求事件的處理
* @author whd
* @date 2017年9月24日 上午12:27:53
*/
public class ServerHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];// 獲得緩衝區可讀的字節數
buf.readBytes(req);
String body = new String(req, "UTF-8");
System.out.println("from client info:" + body);
String currentTime = "server time " + new Date(System.currentTimeMillis()).toString();
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
ctx.write(resp);// 性能考慮,僅將待發送的消息發送到緩衝數組中,再通過調用flush方法,寫入channel中
buf.release();
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();// 將消息發送隊列中的消息寫入到SocketChannel中發送給對方。
ctx.close();
}
}
ok上面代碼就是一個Netty通訊框架的基本demo在不考慮tcp粘包拆包、傳輸文件大小以及序列化等問題的情況下可以使用這個demo進行socket通訊了……
和前一篇java Nio實現socket通訊demo相比這裏的代碼量比較少,而且我們不用從服務器中循環遍歷獲取觸發的事件、不用判斷channel中的事件類型、不用從inputstream中獲取byte在進行轉化,這些所有的通用的操作netty都給我們做了,我們只需要寫具體業務就OK而且在類型轉化部分以及底層的傳輸類型改變都很簡單,傳輸類型的改變我們在client代碼中有註釋而類型的轉化即編碼解碼在後面的代碼中也會體現出來
一、客戶端
package com.test;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
/**
*
* @author whd
* @date 2017年9月24日 上午1:11:42
*/
public class HelloWorldClient {
static final String HOST = System.getProperty("host", "127.0.0.1");
static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));
static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));
public void start() throws Exception {
// Configure the client.
// 線程池,用來處理請求資源
// 其實這種方式就是開啓了不同的傳輸方式,如果是nio傳輸則說明使用的是nio方式也就是select或poll方式而如果是EpollEventLoopGroup
// 則使用的是linux的epoll方式
EventLoopGroup work = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(work).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_KEEPALIVE, true).handler(new InitializerChannel());
// 獲取通道連接
ChannelFuture future = b.connect(HOST, PORT).sync();
future.channel().closeFuture().sync();
} finally {
work.shutdownGracefully();
}
}
// 初始化事件處理類,之前的demo中只有我們自己寫的一個處理類,但這裏有另外兩個字符串解碼器和字符串編碼器,他們都添加了ChannelPipeline中,而且是有順序的編碼器解碼器是在我們自己寫的處理器之前
// 字符串編碼器,解碼器是netty提供的公用處理類,類似的還有很多到後面我們在來看,這裏寫這個只是想說netty框架的設計確實方便了開發,我們需要的功能只需要添加到ChannelPipeline中就可以……
private class InitializerChannel extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast("decoder", new StringDecoder());
p.addLast("encoder", new StringEncoder());
p.addLast(new HelloWorldClientHandler());
}
}
public static void main(String[] args) {
}
}
二、客戶端處理類
package com.test;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
*
* @author whd
* @date 2017年9月24日 上午1:12:33
*/
public class HelloWorldClientHandler extends ChannelHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
// 因爲我們使用了字符串編碼器,所以這裏可以直接寫字符串,netty的字符串編碼器會幫我們把string轉變爲ByteBuf,在netty中使用ByteBuf交互的而不是ByteBuffer
// 而最底層是使用的ByteBuffer,所以netty會吧ByteBuf轉變爲ByteBuffer的……
ctx.channel().writeAndFlush("Hello Netty Server ,I am a common client");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println(msg.toString());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
三、服務端
package com.test;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.net.InetSocketAddress;
/**
*
* @author whd
* @date 2017年9月24日 上午1:11:58
*/
public class HelloWorldServer {
private int port;
public HelloWorldServer(int port) {
this.port = port;
}
public void start() {
// 多路複用的話其實是底層操作系統幫我們乾的,會調用epoll來幫我們做的,當有請求來的時候會調用callback函數之前是對所有的channel進行的select而現在是事件驅動的方式實現的。
EventLoopGroup bossGroup = new NioEventLoopGroup(10); // 多路複用線程池,指定線程個數,這個eventloop用來處理請求事件是串行執行
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 線程池,進行工作的,這個是使用了默認的線程池
try {
// serverBootstrap就是一個netty啓動類,用來實現啓動管理所有部分
ServerBootstrap sbs = new ServerBootstrap().group(bossGroup, workerGroup);
sbs.channel(NioServerSocketChannel.class);// 設置通道,這個通道就是和javanio中的serverscoketchannel一樣是進行端口監聽的
sbs.localAddress(new InetSocketAddress(port)).childHandler(new InitializerChannel())
.option(ChannelOption.SO_BACKLOG, 128) // option設置tcp協議的一些參數,backlog設置tcp隊列的長度,如果請求存儲長度超出這個長度則,不接受這個請求
.childOption(ChannelOption.SO_KEEPALIVE, true); // 設置長連接
// 綁定端口,開始接收進來的連接
ChannelFuture future = sbs.bind(port).sync();
future.channel().closeFuture().sync(); // 同步關閉
} catch (Exception e) {
// 關閉連接池,釋放資源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class InitializerChannel extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// socketChannel就是進行客戶端的連接通道的獲取
// 配置了三個處理器,解碼器、編碼器、和自定義的處理器
ch.pipeline().addLast("decoder", new StringDecoder());
ch.pipeline().addLast("encoder", new StringEncoder());
// 具體定義的處理器,處理客戶的需求,當請求來的時候回觸發這個處理器中的相關
ch.pipeline().addLast(new HelloWorldServerHandler());
}
}
public static void main(String[] args) throws Exception {
int port;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
} else {
port = 8080;
}
new HelloWorldServer(port).start();
}
}
四、服務端處理類
package com.test;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* 具體處理器,這個是用來做程序相關處理的
* netty是事件驅動的異步觸發的,所有的請求都是eventloop來處理的一個eventloop對應一個thread
*
* @author whd
* @date 2017年9月24日 上午1:12:07
*/
public class HelloWorldServerHandler extends ChannelHandlerAdapter {
// 讀事件的觸發
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("from client info " + msg.toString());
ctx.write("hello client this info from server");
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("服務端接收客戶端請求");
}
// 異常事件的觸發
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
// ctx中保存着channel 和channel的事件,ctx.close()方法就是關閉客戶端連接
ctx.close();
}
// 讀取結束後觸發這個事件
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
ctx.close();
}
}
OK 這個demo你能看到netty給拱的字符編碼器和解碼器的使用,以及ChannelPipeline 中添加處理類的方便性,從這些你可以看到使用框架的好處,框架就是封裝底層的和業務無關的細節只對外提供業務相關的api或擴展接口已實現快速開發以及簡化開發技術,其實這些已經在netty中有所體現,還有比如tcp的粘包拆包netty也有提供,對http協議和websocket的支持都有提供,確實很方便開發,而且能快速開發出穩定高效的功能模塊,但問題來了如果只是瞭解框架的api和使用這些api不瞭解框架底層的實現則出了問題一臉懵逼不知如何解決、第二做性能優化也無從下手,框架給的默認值一般的大衆化的,但我們要按照我們自己的需要進行參數調整優化,所以我們有必要深入學習框架底層的實現原理,從下一篇開始我們就進入netty源碼學習。
哦對了我們的demo和源碼學習都是基於Netty5.+版本的……
netty的jar包下載:點擊打開鏈接