導讀
原創(chuàng)文章哆键,轉(zhuǎn)載請注明出處模软。
本文源碼地址:netty-source-code-analysis
為什么用netty衰絮?因為dubbo吻育、sofa-rpc做个、rocketMQ、jetty等知名的的項目都在用,就是這么簡單拨与。平時我們在遇到技術難題的時候契邀,第1個想法是什么呢:看看別人怎么搞的。我們在和同事爭論技術方案的時候乃戈,最常說的一句話是什么:XXX(某知名框架/某知名公司)也是這么做的。思路沒問題,看看別人怎么做的策泣,尤其是知名權威的項目/公司是怎么做的,借鑒一下抬吟,這是最快速的解決問題的方式萨咕。
1 用netty 寫個NIO Hello World
1.1 netty版本的NIO服務端
/**
* 歡迎關注公眾號“種代碼“,獲取博主微信深入交流
*
* @author wangjianxin
*/
public class HelloNettyServer {
public static void main(String[] args) throws InterruptedException {
//相當于com.zhongdaima.netty.analysis.part0.jdk.nio.NioServerConnector中的線程
EventLoopGroup boss = new NioEventLoopGroup(1);
//相當于com.zhongdaima.netty.analysis.part0.jdk.nio.NioServerHandler中的線程
EventLoopGroup worker = new NioEventLoopGroup(1);
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
byte[] bytes = new byte[((ByteBuf) msg).readableBytes()];
((ByteBuf) msg).readBytes(bytes);
String name = new String(bytes);
//打印客戶端發(fā)來的數(shù)據(jù)
System.out.println(name);
ctx.writeAndFlush(Unpooled.wrappedBuffer(("hello, " + name).getBytes()));
}
});
}
});
//綁定到8000端口
ChannelFuture future = serverBootstrap.bind(8000).sync();
//等待channel關閉
future.channel().closeFuture().syncUninterruptibly();
}
}
- NioEventLoopGroup:就是一組線程火本,boss就相當于上一篇中
com.zhongdaima.netty.analysis.part0.jdk.nio.NioServerConnector
中的線程危队,而worker就相當于上一篇com.zhongdaima.netty.analysis.part0.jdk.nio.NioServerHandler
中的線程,這里我們把線程數(shù)量設置為1钙畔。
serverBootstrap.bind(8000).sync():綁定到8000端口茫陆,相當于上一篇中
com.zhongdaima.netty.analysis.part0.jdk.nio.HelloNioServer
綁定到8000端口。serverBootstrap.childHandler():這里我們添加了一個ChannelInitializer擎析,并在initChannel中添加了一個匿名handler簿盅,這個匿名handler就是我們的主要業(yè)務邏輯,接收到客戶端發(fā)來的請求,并添加上“hello, ”發(fā)送回去桨醋。相當于上一篇中的
com.zhongdaima.netty.analysis.part0.jdk.nio.NioServerHandler
棚瘟。
1.2 netty版本的NIO客戶端
/**
* 歡迎關注公眾號“種代碼“,獲取博主微信深入交流
*
* @author wangjianxin
*/
public class HelloNettyClient {
private static final int CLIENTS = 2;
private static final EventLoopGroup EVENT_LOOP_GROUP = new NioEventLoopGroup(1);
public static void main(String[] args) throws InterruptedException {
//用來保存兩個客戶端
Channel[] clients = new Channel[CLIENTS];
for (int i = 0; i < CLIENTS; i++) {
Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class)
.group(EVENT_LOOP_GROUP)
.handler(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
byte[] bytes = new byte[((ByteBuf) msg).readableBytes()];
((ByteBuf) msg).readBytes(bytes);
//打印服務端返回數(shù)據(jù)
System.out.println(new String(bytes));
}
});
//連接8000端口
clients[i] = bootstrap.connect(new InetSocketAddress(8000)).sync().channel();
}
while (true) {
for (int i = 0; i < clients.length; i++) {
//循環(huán)向服務端發(fā)送“zhongdaima" + 客戶端編號
clients[i].writeAndFlush(Unpooled.wrappedBuffer(("zhongdaima" + i).getBytes()));
}
Thread.sleep(1000);
}
}
}
該客戶端創(chuàng)建了兩條連接喜最,然后循環(huán)向服務端發(fā)送“zhongdaima” + 客戶端編號偎蘸,接收到服務端數(shù)據(jù)后打印。比較簡單瞬内,不再贅述迷雪。
2 netty vs jdk
與上一節(jié)用jdk原生api寫的demo比,大家看了用netty寫的demo之后有什么感受虫蝶?哇章咧,很清爽,很簡潔秉扑,比用jdk原生api寫的那一坨代碼友好太多了慧邮。這里用“一坨”來形容jdk原生api demo一點都不為過。
而netty也不僅僅是NIO舟陆,netty對于BIO误澳、NIO、AIO(異步io秦躯,netty5)抽象出了一套共同的api忆谓,我們只需要稍微修改代碼就可以將我們程序切換為BIO、NIO踱承、AIO中的一種倡缠。
2.1 為什么不用jdk原生api
代碼復雜:大家已經(jīng)看到了,代碼太多茎活。
學習成本高:使用jdk原生api需要熟練掌握Selector昙沦、ByteBuffer等,需要具備非常高超的能力才能寫出健壯代碼载荔。
api復雜不友好:jdk ByteBuffer類的api不夠友好盾饮,必須非常小心才不會出錯。
沒有配套設施:我們知道TCP是基于流的協(xié)議懒熙,而我們的應用發(fā)送數(shù)據(jù)是基于幀的丘损,也就說我們需要手動從TCP流中拆出我們的“Reqeust”、“Response”等工扎,非常復雜徘钥。
2.2 為什么選擇netty
代碼簡潔:大家已經(jīng)看到了,與jdk原生api相比肢娘,代碼量如何一看便知呈础。
學習成本低:netty對jdk api封裝后舆驶,屏蔽了某些概念,如Selector猪落。demo豐富贞远,按照netty文檔學習2小時輕松上手畴博。
api更簡單友好:netty的ByteBuf類比jdk的ByteBuffer好用太多笨忌,用了就知道。無需和Selector打交道俱病,由netty自動完成官疲。
周邊配套完善:netty并不是簡單地封裝了jdk的io api,還提供了豐富的周邊配套亮隙,例如豐富的內(nèi)置編解碼器(http途凫、redis等等)開箱即用。而且還有Abstract類型的編解碼器接口溢吻,輕松構建私有協(xié)議編解碼器维费。使用內(nèi)置的IdleStateHandler輕松完成長連接心跳功能。
知名項目/知名大廠都在用:群眾的眼睛是雪亮的促王,歷經(jīng)眾多項目的考驗犀盟,穩(wěn)定性高。
3 我用netty
我在兩年前入職轉(zhuǎn)轉(zhuǎn)蝇狼,負責公司RPC框架開發(fā)阅畴,當時公司的RPC框架客戶端真的是用jdk原生nio寫的。我很佩服前輩能用jdk原生api寫代碼迅耘,這么多年了代碼依然有bug贱枣,不優(yōu)雅。終于在入兩個月后我忍不住用netty重構了颤专,我刪除了差不多20個類纽哥,新增了個6個類完成了這次重構工作,約摸估計減少上千行代碼栖秕〈核看著清爽的代碼,心情很美麗累魔。
4 總結
上面都說了這么多摔笤,還用說啥,一起開始學習吧垦写,關注我吕世,及時獲取最新文章。
關于作者
王建新梯投,轉(zhuǎn)轉(zhuǎn)架構部資深Java工程師命辖,主要負責服務治理况毅、RPC框架、分布式調(diào)用跟蹤尔艇、監(jiān)控系統(tǒng)等尔许。愛技術、愛學習终娃,歡迎聯(lián)系交流味廊。