前言
在家闲着没事,刷题也懒得刷,颓废了一周左右发现还是需要开始学习了。
本来想着实习到四月中就溜溜球呢,这下疫情比较严重,可能还得继续呆在公司里了。
这注定是一个十分冷清的春节,十几天来,都是呆在床上度过的。本来很想写写实习的感受,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就完成了。