Netty系列 (二) 如何搭建第一个demo

​ 这篇博主要讲的是如何搭建第一个Netty的demo,所以对一些Netty的类库和用法不会过多的描述。

1.Netty服务器端开发

​ 为了方便理解,这个demo没有采用匿名类的方式去写,实际上如果采用匿名类的编写,代码文件会少一点。虽然看起来可能麻烦些,初学的时候还是觉得分开写比较好。

1.1MyServer.java

​ 在代码的11~12行创建了两个NioEventLoopGroup实例,NioEventLoopGroup实个线程组,包含了一组NIO线程,专门用于网络事件的处理,实际上就是Reactor线程组。这里需要创建两个的原因是一个用于服务器端接受客户端的连接,另外一个用于进行SocketChannel的网络读写。

​ 第15行调用了ServerBootstrap的group方法 ,将两个NIO线程组当作入参传递到ServerBootstrap中,接着设置创建的channel 为NioServerSocketChannel,它的功能对应于JDK NIO类库中的ServerSocketChannel类。在这里我们new了一个MyServerInitializer(),稍后会介绍下这个是做什么的。

​ 最后绑定IO事件的处理类ChildChannelHandler(这里可以注意一下,当编写客户端是用的处理类和服务器端是不同的),它的作用类似于Reactor模式中的Handler类,主要用于处理网络IO事件(我们的编解码过程就是靠它完成)。

​ 服务器端其中辅助类配置完成后,调用bind方法绑定监听端口,然后调用sync等待绑定操作完成。解着Netty返回一个ChannelFuture ,主要用于异步操作的通知回调。

​ 第19行channelFuture.channel().closeFuture().sync();进行阻塞,等待服务器关闭之后main才会退出。

​ 第22~23行调用shutdownGracefully()方法退出,释放和shutdownGracefully有关的资源。

//Netty服务器端MyServer
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class MyServer {
	public static void main(String[] args) throws  Exception{
		//配置Server端的NIO线程组
		EventLoopGroup bossGroup=new NioEventLoopGroup();
		EventLoopGroup workergroup=new NioEventLoopGroup();
		try{
			ServerBootstrap serverBootstrap=new ServerBootstrap();
			serverBootstrap.group(bossGroup,workergroup).channel(NioServerSocketChannel.class).childHandler(new MyServerInitializer());
			//绑定端口,同步等待成功
			ChannelFuture channelFuture=serverBootstrap.bind(8080).sync();
			//等待服务端监听端口关闭
			channelFuture.channel().closeFuture().sync();
		}finally {
			//退出,释放线程池资源
			bossGroup.shutdownGracefully();
			workergroup.shutdownGracefully();
		}
	}
}
1.2 MyServerInitializer.java
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;

public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
	@Override
	protected void initChannel(SocketChannel ch) throws Exception {
		ChannelPipeline pipeline=ch.pipeline();

		pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));
		pipeline.addLast(new LengthFieldPrepender(4));
		pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
		pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
		pipeline.addLast(new MyServerHandler());
	}
}

​ 第16行使用了LengthFieldPrepender编码,LengthFieldBasedFrameDecoder解码的netty传输可以解决半包粘包问题,这个会在后面写一篇博客记录下,在此就不多描述了。

1.3 MyServerHandler
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.util.UUID;


public class MyServerHandler extends SimpleChannelInboundHandler<String> {
	@Override
	protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
		System.out.println(ctx.channel().remoteAddress()+","+msg);
		ctx.channel().writeAndFlush("from server"+ UUID.randomUUID());
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		ctx.close();
	}
}

​ MyServerHandler继承于SimpleChannelInboundHandlerpublic abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter

​ SimpleChannelInboundHandler又继承于ChannelInboundHandlerAdapter,ChannelInboundHandlerAdapter用于对网络事件进行读写操作,通常只关注channelRead0和exceptionCaught方法。

2.Netty客户端开发
2.1MyClient.java
//Netty客户端MyClient
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

public class MyClient {
	public static void main(String[] args) throws  Exception{
		EventLoopGroup eventLoopGroup=new NioEventLoopGroup();

		try{
			Bootstrap bootstrap=new Bootstrap();
			bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new MyClientInitializer());

			ChannelFuture channelFuture=bootstrap.connect("127.0.0.1",8080).sync();
			channelFuture.channel().closeFuture().sync();
		}finally {
			eventLoopGroup.shutdownGracefully();
		}
	}
}

​ 于服务器端不同的是第14行,它的Channel需要设置为NioSocketChannel,然后为其添加Handler。

2.2 MyClientHandler.java
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.time.LocalDateTime;


public class MyClientHandler extends SimpleChannelInboundHandler<String> {

	@Override
	protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
		System.out.println(ctx.channel().remoteAddress());
		System.out.println("client output:"+msg);
		ctx.writeAndFlush("from client:"+ LocalDateTime.now());
	}

	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		ctx.writeAndFlush("来自于客户端的问候");
	}


	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		ctx.close();
	}
}

​ 当客户端和服务器端TCP通路建立成功后,Netty的NIO线程会调用channelActive方法,发送问候给服务器端,调用ChannelHandlerContext的writeAndFlush的方法将请求消息发送给服务器端。

​ 当服务器端返回应答消息时,channelRead方法被调用,打印应答消息。

​ 第22~26行,当发生异常时,打印异常日志,释放客户端资源。

2.3 MyClientInitializer.java
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;

public class MyClientInitializer extends ChannelInitializer<SocketChannel> {

	@Override
	protected void initChannel(SocketChannel ch) throws Exception {
		ChannelPipeline pipeline=ch.pipeline();

		pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));
		pipeline.addLast(new LengthFieldPrepender(4));
		pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
		pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
		pipeline.addLast(new MyClientHandler());
	}
}

当创建NioSocketChannel成功后,再进行初始化时,将ChannelHandler设置到ChannelPipeline中,用于处理网络IO事件。

3.运行

​ 首先启动MyServer,然后启动MyClient。打印信息如下:

3.1 MyServer
/127.0.0.1:62326,来自于客户端的问候
/127.0.0.1:62326,from client:2019-09-25T11:33:39.612
/127.0.0.1:62326,from client:2019-09-25T11:33:39.615
/127.0.0.1:62326,from client:2019-09-25T11:33:39.616
/127.0.0.1:62326,from client:2019-09-25T11:33:39.616
/127.0.0.1:62326,from client:2019-09-25T11:33:39.617
/127.0.0.1:62326,from client:2019-09-25T11:33:39.618
/127.0.0.1:62326,from client:2019-09-25T11:33:39.619
......
3.2 MyClient
ef5-8484-91c31c7cc23c
/127.0.0.1:8080
client output:from server7fbb6e58-b176-4a37-a5ef-cc36a77cfb00
/127.0.0.1:8080
client output:from server80777926-723b-4973-880d-98b86b9c9f93
/127.0.0.1:8080
client output:from server2b911e76-5b89-4f71-94bb-fbb36ae4a310
/127.0.0.1:8080
client output:from server6d2fab67-2776-45b1-99b1-cedfaa60069b
/127.0.0.1:8080
client output:from server330d4c9a-1dd8-448f-ac4e-5effef9b7fb9
/127.0.0.1:8080
......
发布了58 篇原创文章 · 获赞 5 · 访问量 6294

猜你喜欢

转载自blog.csdn.net/weixin_40992982/article/details/101353476