Netty是業界最流行的NIO框架之一,它的健壯性、功能、性能和可擴展性在同類框架中都是首屈一指的,它已經得到了成百上千的商用項目的驗證。本文是Netty網絡編程的入門教程,從Netty開發環境的搭建到Netty入門實例編程。
Netty入門教程
1. Netty開發環境搭建
環境配置:
- JDK1.8
- IDEA 2020.1
- Netty 4.0.56
由於Netty是一個基於Java的NIO編程框架,我們需要實現下載安裝好JDK並且配置環境變量,在這裏我就不介紹JDK的安裝配置了,不懂的大家可以自行百度。關於編程所使用的工具,我使用的是IDEA,大家也可以選擇其它的環境,下面簡要介紹Netty開發環境搭建。
1.1. 下載Netty的jar包
訪問Netty的官網http://netty.io/,從Downloads標籤頁選擇下載安裝,將壓縮包下載完成之後,進行解壓,目錄結構如下圖所示:
在這個裏面我們可以找到各個模塊的jar包和源碼,由於我們直接以二進制類庫的方式使用Netty,所以只需要獲取netty-all-4.0.56.Final.jar即可,該jar包在all-in-one目錄下。
1.2 搭建Netty工程
使用IDEA創建普通的Java工程,創建相應的package和類,如下圖所示:
接下來我們在所建的java工程添加外部jar包,步驟爲:File->Project Structure -> Libraries ->點擊+ ->選擇java ->選擇netty-all-4.0.56.Final.jar添加即可,如下圖所示:
1.3 使用Maven工程
除了使用上述方法直接建立java工程然後導入jar包之外,我們還可以使用Maven工程,添加netty相應的依賴即可,下面給出netty的maven依賴,具體的maven工程構建可以百度。
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty</artifactId> <!-- Use 'netty-all' for 4.0 or above -->
<version>X.Y.Z.Q</version>
<scope>compile</scope>
</dependency>
2. Netty編程入門案例
本節我們將使用一個簡單的時間服務器的例子來演示如何使用Netty進行網絡編程。下面分服務端和客戶端分別進行介紹。
2.1 Netty服務端編程
下面首先給出代碼,然後再進行相應的解釋,具體代碼如下所示。
package netty.base;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* created by LMR on 2020/5/20
*/
public class TimeServer {
public static void main(String[] args) throws Exception{
int port = 8080;
new TimeServer().bind(port);
}
public void bind(int port) throws Exception{
//配置服務端NIO線程組
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChildChannelHandler());
//綁定端口,同步等待成功
ChannelFuture f = serverBootstrap.bind(port).sync();
//等待服務端監聽端口關閉
f.channel().closeFuture().sync();
}finally {
//優雅退出,釋放資源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new TimeServerHandler());
}
}
}
在bind方法中,首先創建了兩個NioEventLoopGroup實例。NioEventLoopGroup本子上是個線程組,專門用於網絡事件的處理,與Java的NIO編程中的Reactor類似。這裏的兩個實例,一個用於服務端接受客戶端連接,另一個用於進行SocketChannel的網絡讀寫。之後創建ServerBootstrao對象,器用於啓動NIO服務端,主要是爲了降低服務端的開發難度。接下來調用ServerBootstrap的group方法,將兩個NIO線程組當作參數傳入。然後設置創建的Channel爲NioServerSocketChannel,並且配置它的TCP參數,最後綁定I/O時間的處理類ChildChannelhandler。
配置完成之後,就會調用ServerBootstrap的bind方法綁定監聽端口,並且調用它的同步阻塞方法sync等待綁定按操作完成。完成之後會返回一個ChannelFuture ,主要用於異步操作的通知回調。接下來看看TimeServerHandler類是如何實現的。
package netty.base;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* created by LMR on 2020/5/20
*/
public class TimeServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
//將消息轉爲buf
ByteBuf buf=(ByteBuf) msg;
//創建一個buf長度的數組
byte [] requestbyte=new byte[buf.readableBytes()];
//將緩衝區的數據寫入到字節數組
buf.readBytes(requestbyte);
//根據字節數組創建字符串
String request=new String(requestbyte,"utf-8");
System.out.println("The time server receive order : " + request);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(request) ? new
java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER";
//響應給客戶端
ByteBuf resBuf= Unpooled.copiedBuffer(currentTime.getBytes());
ctx.write(resBuf) ;
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//刷新數據,將數據從緩衝區刷新到Channel中去
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//當異常發生時,關閉ChannelHandlerContext,釋放和ChannelHandlerContext相關聯的句柄等資源
ctx.close();
}
}
TimeServerHandler繼承ChannelInboundHandlerAdapter ,它用於對網絡事件進行讀寫操作,通常我們只需要關注channelRead和exceptionCaught方法。關於這兩個方法中的代碼,下上面已經有了詳細的註釋,在這裏就不再進行介紹。接下來介紹客戶端的實現代碼。
2.2 Netty客戶端編程
Netty客戶端的開發相比於服務端更簡單,下面來看具體的實現代碼。
package netty.base;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* created by LMR on 2020/5/20
*/
public class TimeClient {
public static void main(String[] args) throws Exception{
int port = 8080;
new TimeClient().connect("127.0.0.1", port);
}
public void connect(String host, int port) throws Exception{
NioEventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TimeClientHandler());
}
});
//發起異步連接操作
ChannelFuture f = bootstrap.connect(host, port).sync();
//等待客戶端連接關閉
f.channel().closeFuture().sync();
}finally {
group.shutdownGracefully();
}
}
}
首先在connect方法中創建客戶端處理I/O讀寫的NioEventLoopGroup線程組,然後繼續創建客戶端輔助啓動類Boostrap,隨後對其進行配置 。與服務端不同點在於,客戶端的Channel需要設置爲NioSocketChannel類型,然後爲其添加handler,這裏我們使用了匿名內部類(不懂得自行百度或者留言),實現initChannel方法,其作用是當創建NioSocketChannel成功之後,在初始化它的時候將它的ChannelHandler設置到ChannelPipeline中,用於處理網絡I/O事件。
下面我們看一下TimeClientHandler的代碼實現。
package netty.base;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* created by LMR on 2020/5/20
*/
public class TimeClientHandler extends ChannelInboundHandlerAdapter {
private final ByteBuf message;
public TimeClientHandler(){
//創建發送消息的字節數組
byte[] req = "QUERY TIME ORDER".getBytes();
//將字節數組寫入緩衝區
message = Unpooled.buffer(req.length);
message.writeBytes(req);
}
//當客戶端和服務端TCP鏈路建立成功之後會調用此方法
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//將消息從緩衝區刷新到Channel
ctx.writeAndFlush(message);
}
//當客戶端接受到服務端傳來的消息時執行
@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("Now is : " + body);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
在客戶端處理類中需要注意的就是上述代碼中重寫的三個方法,關於方法的調用場景和實現代碼都進行了詳細的註釋,在這裏就不再解釋。
到這裏,我們就完成了Netty的服務端和客戶端的編寫,我們可以看出,使用Netty進行NIO網絡編程要遠比Java原生的NIO編程簡單。
2.3 運行結果
服務端運行結果:
客戶端運行結果:
寫在後面的話:
本書中的案例來自於《Netty權威指南》,但由於Netty版本不同,具體的實現代碼與原書有所不同。
如果喜歡的話希望點贊收藏,關注我,將不間斷更新博客。
希望熱愛技術的小夥伴私聊,一起學習進步
來自於熱愛編程的小白