前言
本章將會介紹如何使用Netty搭建一個TCP服務器,本系列不會詳細介紹Netty本身框架。
TCP 協議
傳輸控制協議(TCP,Transmission Control Protocol)是一種面向連接的、可靠的、基於字節流的傳輸層通信協議。
Netty 支持
導入依賴包
// gradle
compile group: 'io.netty', name: 'netty-all', version: '4.1.50.Final'
// maven
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.50.Final</version>
</dependency>
Server 服務端
創建事件線程組
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(6);
bossGroup 裏面初始化了一條線程(這裏就是NioEventLoop,包含一個NIO SELECTOR,一個隊列,一條線程),其作用就是負責處理所有客戶端連接的請求,創建新連接(channel),然後註冊到workerGroup 的其中一個NioEventLoop。之後該channel的所有讀/寫操作都由與Channel綁定的NioEventLoop來處理。
workerGroup 如上所說,主要是負責處理讀/寫操作,這裏初始化了6條線程(NioEventLoop)去執行。
Channel的整個生命週期都由同一個NioEventLoop處理。
服務啓動器
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//添加自定義處理器
socketChannel.pipeline()
.addLast(new StringEncoder())
.addLast(new StringDecoder())
.addLast(new TcpNettyHandler());
}
});
- serverBootstrap:爲服務啓動引導器,可以設置處理器,啓動端口,綁定IP以及Channel配置等等。
- group:綁定線程組。
- NioServerSocketChannel:NIO模式。
- option(ChannelOption.SO_BACKLOG, 1024):標識當服務器請求處理線程全滿時,用於臨時存放已完成三次握手的請求的隊列的最大長度。
- childOption(ChannelOption.SO_KEEPALIVE, true):啓用心跳保活機制
這裏說明下option和childOption的區別(下面childHandler同理):
- option主要是作用於boss線程,用於處理新連接。
- childOption主要作用與worker線程,也就是已創建的channel。
handler處理器
以上都是對Channel的配置,到這裏,纔是處理數據的關鍵。
編解碼器
上面有提到,TCP是基於字節流的傳輸協議,因此我們必須要用到編解碼器來處理字節流。本文我們當其是字符串處理。
Netty提供了很多協議編解碼器,無需我們自己去編寫。
StringDecoder:字符串解碼器,它會把輸入的字節流解碼成字符串。
StringEncoder:字符串編碼器,它會把輸出的String編碼爲字節流。
源碼可見,它們其實也是屬於handler。
TcpServerNettyHandler
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* Created by chenws on 2020/3/24.
*/
@ChannelHandler.Sharable
public class TcpServerNettyHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg);
//把消息寫到緩衝區
ctx.write("I get the message " + msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
//刷新緩衝區,把消息發出去
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//異常處理,如果該handler不處理,將會傳遞到下一個handler
cause.printStackTrace();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush("connect success!");
}
}
該handler繼承了SimpleChannelInboundHandler,它是繼承了ChannelInboundHandlerAdapter,其實就是幫我們轉化了數據的類型,體現在泛型上。這裏我們指定了< String >。
- channelRead0:當channel接收到數據時,該方法就會被執行。
- channelReadComplete:本次數據傳輸接收完後,該方法就會被執行。
- exceptionCaught:在接收數據過程中發生異常,該方法會被執行,如果不實現,則異常會傳遞到下一個handler。
- channelActive:channel建立成功後,該方法就會被執行。
那這裏有個問題,channelReadComplete什麼時候觸發呢?換種說法也就是Netty是怎麼知道本次傳輸已經讀完呢?有兩個條件,符合其一即會執行:
- read 返回0字節
- read 傳遞給它的緩衝區有1024個字節要填充,但最終少於1024個字節被填充。
Bind
ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
最後一步就是綁定端口,默認綁定本機的IP 地址。
至此,TCP Netty的服務端已經完成,下面來看看客戶端。
Client 客戶端
大部分代碼的邏輯都跟server服務端差不多,這也是netty的強大之處,下面我就直接貼
EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1);
try{
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast(new StringEncoder())
.addLast(new StringDecoder())
.addLast(new TcpClientNettyHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
eventLoopGroup.shutdownGracefully();
}
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class TcpClientNettyHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
log.info("接受來自服務器的消息:{}", msg);
ctx.write(msg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//刷出通道
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}
主要區別是不再用ServerBootstrap,改用Bootstrap來啓動客戶端。代碼沒有什麼難度,這裏就不展開來說了。
總結
到此爲止,Netty結合TCP已經介紹完,總的來說,Netty已經幫我們做了很多事情,如果對Netty感興趣的,可以隨時關注我的博客。
源碼可以見:Netty-Learn