前言
编程学习的方法,我认为是以小见大,在理解一个东西之前一定要先会用,并用熟它,这样理解才会快。 就跟理解自行车一样,不会骑,然后先开始研究,最终可能会研究明白,但是毕竟还是事倍功半。 所以先构建一个可以使自己理解的项目,再一点一点学习原理是一种比较好的方式。
Server服务端
构建netty
的话,就是一个流程三件套,最基础的三个框架组件摆出来,然后在上面写代码,分别是:
- Server 启动类
- Initializer 实始化组件类
- Handle 请求处理类
劳记这一个流程三件套,基本netty的开发,你已经入门了,就是这么回事。
启动类
套路第一步,写一个启动类,这个是入口,netty服务的话,一般都是先启动服务端,再启动客户端。 这个好理解,如果服务端都不提供服务,客户端还有必要连接吗。当然如果是要启动着玩,就另说。
代码语言:javascript复制package com.liukai.netty.test02.server;
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;
/**
* 服务端
*
* @author liu kai
* @since 2020-01-03 00:40
*/
public class ServerNettyServer {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ServerNettyChannelInitializer02());
ChannelFuture channelFuture = serverBootstrap.bind(8889).sync();
System.out.println("Netty 服务端启动完毕");
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
bossGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
e.printStackTrace();
}
}
}
初始化
实始化必要组件,这也在其它的编常中也是非常常见的一种模式。
代码语言:javascript复制package com.liukai.netty.test02.server;
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;
/**
* 服务 Initializer
*
* @author liu kai
* @since 2020-01-03 23:52
*/
public class ServerNettyChannelInitializer02 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 ServerNettyServerHandle());
}
}
处理器
处理具体业务,其实看下来,就是这个模式,是不是很好理解。
代码语言:javascript复制package com.liukai.netty.test02.server;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.UUID;
/**
* 处理器
*
* @author liu kai
* @since 2020-01-04 22:08
*/
public class ServerNettyServerHandle extends SimpleChannelInboundHandler<String> {
/**
* 处理请求
* @param ctx 表示请求上下文信息。可用于获得channel,远程地址等
* @param msg 客户端消息
* @return void
* @author liu kai
* @since 2020-01-04 22:19
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("server: [remote ip]-> " ctx.channel().remoteAddress() ", [msg]-> " msg);
//向客户端发送消息
ctx.channel().writeAndFlush("server: " UUID.randomUUID());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}
客户端
启动类
代码语言:javascript复制/**
* 客户端
*
* @author liu kai
* @since 2020-01-04 22:23
*/
public class ClientServer {
public static void main(String[] args) {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new ClientNettyChannelInitializer());
ChannelFuture channelFuture = bootstrap.connect("localhost",8889).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
eventLoopGroup.shutdownGracefully();
e.printStackTrace();
}
}
}
初始化
代码语言:javascript复制package com.liukai.netty.test02.client;
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;
/**
* 客户端初始化
*
* @author liu kai
* @since 2020-01-04 22:23
*/
public class ClientNettyChannelInitializer 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 ClientNettyHandle());
}
}
处理器
代码语言:javascript复制package com.liukai.netty.test02.client;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.time.LocalDateTime;
/**
* 处理器
*
* @author liu kai
* @since 2020-01-04 22:27
*/
public class ClientNettyHandle extends SimpleChannelInboundHandler<String> {
/**
* 接收服务器返回消息
* @param ctx 上下文请求对象
* @param msg 表示服务端发来的消息
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("client: [remote ip]->" ctx.channel().remoteAddress() ", [msg]->" msg);
//向服务端发送消息
Thread.sleep(1000);
ctx.writeAndFlush("client:" LocalDateTime.now());
}
/**
* 如果没有这个方法,Client并不会主动发消息给Server
* 那么Server的channelRead0无法触发,导致Client的channelRead0也无法触发
* 这个channelActive可以让Client连接后,发送一条消息
* 但是问题在于,有一这个方法后,服务端
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush("客户端:发送数据");
}
}
效果
可以启动看一下这个代码的效果:
1.启动服务端Server
Netty 服务端启动完毕 server: [remote ip]-> /127.0.0.1:65320, [msg]-> 客户端:发送数据 server: [remote ip]-> /127.0.0.1:65320, [msg]-> client:2022-05-21T21:49:42.653 server: [remote ip]-> /127.0.0.1:65320, [msg]-> client:2022-05-21T21:49:43.657 server: [remote ip]-> /127.0.0.1:65320, [msg]-> client:2022-05-21T21:49:44.661 server: [remote ip]-> /127.0.0.1:65320, [msg]-> client:2022-05-21T21:49:45.664 server: [remote ip]-> /127.0.0.1:65320, [msg]-> client:2022-05-21T21:49:46.668 server: [remote ip]-> /127.0.0.1:65320, [msg]-> client:2022-05-21T21:49:47.674 server: [remote ip]-> /127.0.0.1:65320, [msg]-> client:2022-05-21T21:49:48.678
2.启动客户端
client: [remote ip]->localhost/127.0.0.1:8889, [msg]->server: bd34689c-237a-4499-be9f-be7b55d1f7e2 client: [remote ip]->localhost/127.0.0.1:8889, [msg]->server: afe29b02-65f8-44c5-8b1b-c5286acd0a72 client: [remote ip]->localhost/127.0.0.1:8889, [msg]->server: 806f346f-9ae8-4dce-ad20-7a308d8b0c46 client: [remote ip]->localhost/127.0.0.1:8889, [msg]->server: 670fb4af-15cd-4471-9990-bc86ff07932c client: [remote ip]->localhost/127.0.0.1:8889, [msg]->server: b103ed1a-127b-42df-9c86-58f9e5dd2d4a client: [remote ip]->localhost/127.0.0.1:8889, [msg]->server: 3948c322-1cb7-4ba0-bade-7f7161fc2712 client: [remote ip]->localhost/127.0.0.1:8889, [msg]->server: e3daf473-6dd6-4e6c-8bca-27f87e43854f client: [remote ip]->localhost/127.0.0.1:8889, [msg]->server: 64bf61e2-4126-4238-b53c-4bf90e1c21d8
总结
netyy 的刚开始学习时,只需要了解到它的这个套路,后面的开发其实大同小异,无非是对协议和序列化相关的东西进行处理。实现自己的业务需求。