服务端NettyServer:
①根据前面说到的Netty架构,对于服务端,我们也需要先新建两个NioEventLoopGroup,分别是boos和worker,boos是负责进行连接请求的接收accept事件,而worker则是负责业务处理
//创建bossGroup和workerGroup,这两个都是无限循环 //只是处理连接请求accept,含有的子线程有多少个呢,NioEventLoop个数,默认是cpu的核数*2 NioEventLoopGroup bossGroup = new NioEventLoopGroup(1); //真正的和客户端进行业务处理,含有的子线程有多少个呢,NioEventLoop个数,默认是cpu的核数*2 NioEventLoopGroup workerGroup = new NioEventLoopGroup();
②创建完上面两个线程组之后,则需要创建一个服务端启动对象,配置启动参数
ServerBootstrap serverBootstrap = new ServerBootstrap();
下面使用链式编程来进行配置:
serverBootstrap.group(bossGroup, workerGroup)//设置两个线程组 .channel(NioServerSocketChannel.class)//使用nioserversocketchannel作为通道实现 .option(ChannelOption.SO_BACKLOG, 128)//设置线程队列等待连接个数 .childOption(ChannelOption.SO_KEEPALIVE, true)//设置保持连接状态 .childHandler(new ChannelInitializer<SocketChannel>() {//创建通道测试对象(匿名对象)piepline里面设置处理器 //这里会进行客户端业务处理 @Override protected void initChannel(SocketChannel ch) throws Exception { //这里加入了自定义的handler ChannelPipeline pipeline = ch.pipeline().addLast(new NettyServerHandler());//通过chnnel可以得到pipeline } });//给workergruop的eventgroup设置处理器
※这里比较重要的是最后一步的childHandler,这里的childHandler就是给我们的workerloop实际处理业务逻辑的pipeline配置一个handler,handler可以定制化逻辑。
//绑定一个端口,并且同步,生成一个channelfuture对象,后面会详聊,简单的认为是立马返回的对象, //这里就是启动服务器,因为绑定了端口 ChannelFuture channelFuture = serverBootstrap.bind(6668).sync(); //对关闭通道进行监听,当有关闭通道的消息的时候,才会进行close channelFuture.channel().closeFuture().sync(); finally { //优雅的关闭 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); }
上面就是netty简单的实现了服务端,下面看下自定义的handler是怎样实现的:
//自定义的适配器需要继承netty自己的适配器,这样我们自定义的handler才能称为handler
public class NettyServerHandler extends ChannelInboundHandlerAdapter
Netty自己的handler有几个主要的方法: (1)channelRead,这个方法当有数据读写的时候,会触发,可以读取客户端的消息
channelRead(ChannelHandlerContext ctx, Object msg)
里面有两个参数传入,一个是ChannelHandlerContext--上下文对象,包含了管道pipeline,通道channel,地址remoteAdress等信息
一个是Object,就是客户端发送过来的对象,因为数据是在channel中,所以我们最好使用buffer来进行接收,因此一般我们都需要对msg进行强转
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("server ctx:"+ ctx); System.out.println("看看channel和pipeline关系"); Channel channel = ctx.channel(); ChannelPipeline pipeline = ctx.pipeline();//本质是双向链表,出栈入栈问题 //因为现在数据在channel,所以我们最好用buffer接收---这里的buffer跟nio的buffer有区别 //强转 ByteBuf buf= (ByteBuf)msg; System.out.println("读取到的数据为:"+ buf.toString(CharsetUtil.UTF_8)); // System.out.println("客户端地址:"+ctx.channel().remoteAddress()); }
(2)channelReadComplete,数据读取完毕之后,需要做的业务操作,回消息
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//把数据write到缓冲区,同时进行flush,就是把缓存数据写到管道
//一般来讲,我们需要对发送的数据进行编码
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端",CharsetUtil.UTF_8));
}
(3)exceptionCaught,发生异常的handler
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.channel().close(); }
服务端NettyClient:
①客户端跟服务端有些许区别,第一个就是不需要两个loopgroup了,不需要使用boosgroup了,所以只需要新建一个loopgroup来处理业务就行了
NioEventLoopGroup eventgruop = new NioEventLoopGroup();
②第二点就是不是使用serverBootstrap,而是使用bootstrap
//客户端使用的是bootstrap Bootstrap bootstrap = new Bootstrap();
③设置启动参数
//设置相关参数
bootstrap.group(eventgruop)//设置线程组
.channel(NioSocketChannel.class)//设置客户端通道的实现类
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyClientHandler());//加入自己的处理器
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync(); System.out.println("客户端开始连接"); //给通道关闭进行监听---当发生了通道关闭消息的时候会进行关闭 channelFuture.channel().closeFuture().sync();
finally { //优雅的关闭 eventgruop.shutdownGracefully(); }
下面就是客户端的自定义handler:
(1)channelActive,当通道就绪,就会触发该方法
@Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("client:"+ctx); //发送消息 ctx.writeAndFlush(Unpooled.copiedBuffer("Hello Server,miao", CharsetUtil.UTF_8)); }
(2)channelRead,当通道有读取事件时,会触发
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf= (ByteBuf) msg; //服务器回复的消息 System.out.println("客户端收到的消息:"+buf.toString(CharsetUtil.UTF_8)); System.out.println("服务器的地址:"+ctx.channel().remoteAddress()); }
(3)exceptionCaught,当有异常的就会触发
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // cause.printStackTrace(); ctx.channel().close(); }