前言
在家閒着沒事,刷題也懶得刷,頹廢了一週左右發現還是需要開始學習了。
本來想着實習到四月中就溜溜球呢,這下疫情比較嚴重,可能還得繼續呆在公司裏了。
這注定是一個十分冷清的春節,十幾天來,都是呆在牀上度過的。本來很想寫寫實習的感受,emmm,其實總體感覺很棒,頭條的待遇應該來說算是國內前列了。
我所在的組內主要是寫Go和Python的,其實我個人是很喜歡JAVA的,這是我比較不喜歡的一點。我還是主要寫Go,學了幾個公司提供的框架,比如Gin,gorm,kite等等,這些東西其實都是千篇一律,學會了直接就用了,或者沒學會看着文檔直接用也行。
emmm,覺得學習到的很重要的一點就是技術層面上也算是有一些見識,比如公司的大數據平臺,在海量數據裏面搜索的解決方案,還有高併發程序,主要都是和大數據相關的問題,AI倒是沒有見到,也學習了一些很重要的後端技術,比如中間件Kafka的使用,Redis的使用,MySQL的優化,微服務架構體系等。個人學到的最多還是Redis,很喜歡這個東西。
基本上就是這樣,畢竟也才學了不到一個月,希望後邊能夠學到更多的技術,身邊大佬雲集倒是真的。廢話不多說,開始正題。
Netty
是什麼 首先我個人的認識就是一個JAVA實現NIO的框架。(如果不瞭解什麼是BIO/NIO/AIO,可以自行學習)。在高併發的程序裏面,NIO是十分必要的。當然現在高版本JDK提供了NIO的實現方式,不採用任何框架,直接調用JDK提供的方法也可以實現NIO,但是這些方法不是很好用,主要是比較複雜,所以大佬們把它封裝了一下,開發了一個NIO框架。
用在何處 Netty的使用很廣泛,可以說是現在最主流的JAVA NIO框架。比如阿里的Dubbo 裏面用到了NIO,還有十分著名的搜索引擎:ES(esticsearch),這是一個做全文搜索的強有力的搜索引擎,常用在日誌搜索,全文檢索匹配等。ES用Netty做了底層的通信。
Echo 服務
Echo服務是一種類似於心跳檢測的服務,用來檢測系統是不是在工作,能不能做出相應。
服務端的編寫
主函數
package EchoService;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class EchoServer {
private int port;
public EchoServer(int port){
this.port=port;
}
public void run() throws InterruptedException {
// 兩個線程組 配置服務端線程組
EventLoopGroup bossGroup= new NioEventLoopGroup();
EventLoopGroup workGroup=new NioEventLoopGroup();
try {
// 需要一個啓動的引導類
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new EchoServerHandler());
}
});
System.out.println("Echo 服務啓動ing");
// 綁定端口,同步等待
ChannelFuture channelFuture = serverBootstrap.bind(this.port).sync();
// 等待服務端監聽端口關閉
channelFuture.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
// 釋放線程池
workGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
int port=8080;
if(args.length>0){
port=Integer.parseInt(args[0]);
}
new EchoServer(port).run();
}
}
這裏面的核心就是兩個線程組,bossGroup和workGroup。bossGroup該線程組是用來接受TCP連接的,這個線程組裏面的線程主要是用來做連接使用,當然有時候也會做一些鑑權。當連接建立好,業務邏輯的處理都是交給workGroup來做。然後是一些啓動的配置,新建一個ServerBootstrap類,這個一看名字就知道了,是用來啓動服務器的類,然後定義這個類的管道類型,定義爲Nio類型,最後一步就是添加Handler,這個Handler就是用來做業務邏輯處理的。接下來是Handler的編寫:
處理類
package EchoService;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf data=(ByteBuf)msg;
System.out.println("當前收到的數據是:"+data.toString(CharsetUtil.UTF_8));
ctx.writeAndFlush(data);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("讀取成功");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 捕獲到異常時候的處理,這個時候需要記錄日誌什麼的。
cause.printStackTrace();
ctx.close();
}
}
這個Handler需要繼承ChannelInboundHandlerAdapter這個類,看類名大概就知道這是一個輸入流處理類,當Server得到輸入的時候,把輸入流交給這個Handler處理。Handler得到這個輸入流,來實現自己的業務邏輯。這裏其實我們就沒有關心IO是怎麼分配的,不用自己完成IO的多路複用,這個Netty幫我們實現了,只需要關心自己的業務邏輯。繼承這個類之後,實現裏面的一些必要的方法,這個根據自己的需求,比如read方法,這個就是輸入的數據,readComplete,這個是讀入結束之後該做的事情,還有就是發生異常時應該做的事情。這個函數分類的很詳細,耦合性很低,可以自己自由組合,來實現自己的業務。
完成了這些,就可以用了,打開終端,使用telnet來發送包:
可以看到基本的效果。
客戶端的編寫
客戶端就是建立建立連接,發送數據。其實基本的步驟和服務端差不多,這點和BIO的Socket編程類似。
客戶端主函數
package EchoService;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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 java.net.InetSocketAddress;
public class EchoClient {
private String host;
private int port;
public EchoClient(String host,int port){
this.host=host;
this.port=port;
}
// 對應的連接邏輯
public void start(){
EventLoopGroup group=new NioEventLoopGroup();
try{
Bootstrap bootstrap=new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host,port))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new EchoClientHandler());
}
});
// 連接到服務端,connect 是異步連接,使用同步等待
ChannelFuture channelFuture=bootstrap.connect().sync();
// 阻塞直到客戶端通道關閉
channelFuture.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
// 優雅的退出
group.shutdownGracefully();
}
}
public static void main(String[] args) {
new EchoClient("127.0.0.1",8080).start();
}
}
客戶端的線程組只有一個,這個很容易理解,因爲需要建立TCP連接得所有連接被分成了一組。然後就是客戶端的啓動Bootstrap類,這個需要指定:remoteAddress。然後添加handler。
客戶端處理類
package EchoService;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
public class EchoClientHandler extends SimpleChannelInboundHandler {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
ByteBuf data=(ByteBuf)o;
System.out.println("client recive:"+data.toString());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客戶端啓動");
ctx.writeAndFlush(Unpooled.copiedBuffer("Hello,World", CharsetUtil.UTF_8));
}
// 監聽回調
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("接收成功");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
這個向服務端發送數據被寫在了channelActive函數中,這個函數定義的是當管道建立好連接需要做的事情,顯然管道建立好連接時需要做的就是發送數據,所以調用writeAndFlush方法來完成數據的發送,這個方法其實就是把寫數據,然後刷新到管道里。
完成以後,先啓動服務端,再啓動客戶端就可以完成通信。
一個使用Netty的簡單的Demo就完成了。