http://cxytiandi.com/blog/detail/17345
Netty介紹
Netty是由JBOSS提供的一個java開源框架。Netty提供異步的、事件驅動的網絡應用程序框架和工具,用以快速開發高性能、高可靠性的網絡服務器和客戶端程序。
也就是說,Netty 是一個基於NIO的客戶、服務器端編程框架,使用Netty 可以確保你快速和簡單的開發出一個網絡應用,例如實現了某種協議的客戶,服務端應用。Netty相當簡化和流線化了網絡應用的編程開發過程,例如,TCP和UDP的socket服務開發。
官網地址:http://netty.io/
使用場景
Netty之所以能成為主流的NIO框架,是因為它有下面的優點:
- NIO的類庫和API使用難度較高,Netty進行了封裝,容易上手
- 高性能,功能強大,支持多種編解碼功能,支持多種主流協議
- 成熟,穩定,已經在多個大型框架中使用(dubbo,RocketMQ,Hadoop,mycat,Spring5)
- …..
在幾年之前我上家公司用的是Mina來開發一個IM的系統,Mina也是一個很好的框架(http://mina.apache.org/)。
如今很多的框架都改成用Netty來做底層通訊了,我司現在還有一個代理框架用Mina寫的,等把Netty玩遛了可以重構了。
不知道大家看完了上面的介紹是不是已經知道Netty能用在什么場景了,下面我結合一個我之前做過的事情來進行詳細的說明,當然只是使用場景的一方面而已。
之前做抓取的時候,有一些小型的網站,頁面結構比較復雜,還需要登錄等操作,這種就不能用統一的抓取系統去抓取,只能通過寫腳本的方式針對具體的網站做抓取,抓取必備的一個條件就是代理IP,為了方便抓取,特意封裝了一個抓取的SDK,提供了抓取的方法,內置了切換代理。
我們有一個代理池服務,通過一個網址去獲取能使用的代理IP,在剛開始用的Http請求去獲取代理IP,由於抓取量比較大,通過Http請求去獲取代理IP效率不行,后面用Netty改造了獲取IP這部分,通過Netty來獲取數據,解決了實時獲取的性能問題。
通過長連接的方式,避免了Http請求每次都要建立連接帶來的性能消耗問題,通過二進制的數據傳輸減少網絡開銷,性能更高。
簡單入門
我們編寫一個服務端和客戶端,客戶端往服務端發送一條消息,消息傳輸先用字符串進行傳遞,服務端收到客戶端發送的消息,然后回復一條消息。
首先編寫服務端代碼:
public class ImServer {
public void run(int port) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("decoder", new StringDecoder());
ch.pipeline().addLast("encoder", new StringEncoder());
ch.pipeline().addLast(new ServerStringHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
try {
ChannelFuture f = bootstrap.bind(port).sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
- 通過ServerBootstrap 進行服務的配置,和socket的參數可以通過ServerBootstrap進行設置。
- 通過group方法關聯了兩個線程組,NioEventLoopGroup是用來處理I/O操作的線程池,第一個稱為“boss”,用來accept客戶端連接,第二個稱為“worker”,處理客戶端數據的讀寫操作。當然你也可以只用一個NioEventLoopGroup同時來處理連接和讀寫,bootstrap.group()方法支持一個參數。
- channel指定NIO方式
- childHandler用來配置具體的數據處理方式 ,可以指定編解碼器,處理數據的Handler
- 綁定端口啟動服務
消息處理:
/**
* 消息處理
* @author yinjihuan
*
*/
public class ServerStringHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.err.println("server:" + msg.toString());
ctx.writeAndFlush(msg.toString() + "你好");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
啟動服務,指定端口為2222:
public static void main(String[] args) {
int port = 2222;
new Thread(() -> {
new ImServer().run(port);
}).start();
}
編寫客戶端連接邏輯:
public class ImConnection {
private Channel channel;
public Channel connect(String host, int port) {
doConnect(host, port);
return this.channel;
}
private void doConnect(String host, int port) {
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("decoder", new StringDecoder());
ch.pipeline().addLast("encoder", new StringEncoder());
ch.pipeline().addLast(new ClientStringHandler());
}
});
ChannelFuture f = b.connect(host, port).sync();
channel = f.channel();
} catch(Exception e) {
e.printStackTrace();
}
}
}
客戶端消息處理:
/**
* 當編解碼器為字符串時用來接收數據
* @author yinjihuan
*
*/
public class ClientStringHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println("client:" + msg.toString());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
客戶端啟動入口,然后發送消息給服務端:
public static void main(String[] args) {
String host = "127.0.0.1";
int port = 2222;
Channel channel = new ImConnection().connect(host, port);
channel.writeAndFlush("yinjihuan");
}
測試步驟如下:
- 首先啟動服務端
- 啟動客戶端,發送消息
- 服務端收到消息,控制台有輸出
server:yinjihuan
- 客戶端收到服務端回復的消息,控制台有輸出
client:yinjihuan你好
源碼參考:https://github.com/yinjihuan/netty-im
更多技術分享請關注微信公眾號:猿天地