上一篇我们通过socket对两个系统进行了通信,现在我们通过netty来对上一段demo的socket部分进行更换。
netty封装java socket noi,更好用。
可以通过这个栗子简单的学习一下netty的基本使用。
netty示例,亲测可用
我们的需求是将 comsumer和provider 之间建立起netty通讯。
在pom里添加netty依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.42.Final</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-common</artifactId>
<version>4.1.31.Final</version>
</dependency>
项目框架如下:
新建 NettyClient类
@Slf4j
public class NettyClient {
public void startAdd(CalculateRpcRequest calculateRpcRequest) {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap()
.group(group)
//该参数的作用就是禁止使用Nagle算法,使用于小数据即时传输
.option(ChannelOption.TCP_NODELAY, true)
.channel(NioSocketChannel.class)
.handler(new NettyClientInitializer());
try {
ChannelFuture future = bootstrap.connect("127.0.0.1", 8090).sync();
log.info("客户端成功....");
//发送 数据体
future.channel().writeAndFlush(calculateRpcRequest);
// 等待连接被关闭
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
}
新建NettyClientHandler
@Slf4j
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
@Autowired
ResultFactory resultFactory;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("客户端Active .....");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info("客户端收到消息: {}", msg.toString());
if (msg instanceof AddRequest) {
AddRequest addRequest = (AddRequest) msg;
ResultFactory.put(addRequest.getTimeId().toString(), addRequest.getResult());
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
新建NettyClientInitializer
public class NettyClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast("decoder",
new ObjectDecoder(ClassResolvers.cacheDisabled(this.getClass().getClassLoader())));
socketChannel.pipeline().addLast("encoder", new ObjectEncoder());
socketChannel.pipeline().addLast(new NettyClientHandler());
}
}
在 CalculatorRemoteImpl中添加方法
//启动netty客户端
public void getMethodAdd(int a, int b) {
NettyClient nettyClient = new NettyClient();
CalculateRpcRequest calculateRpcRequest = generateRequest(a, b);
nettyClient.startAdd(calculateRpcRequest);
}
将add方法里的内容注解,调用上面的方法
@Override
public int add(int a, int b) {
this.getMethodAdd(a, b);
return 0;
``}`
接下来是nettyserver部分
新建 NettyServer,这是服务端启动时的代码
@Slf4j
public class NettyServer {
@Autowired
Calculator calculator;
public void start(InetSocketAddress socketAddress) {
//new 一个主线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
//new 一个工作线程组
EventLoopGroup workGroup = new NioEventLoopGroup(200);
ServerBootstrap bootstrap = new ServerBootstrap()
.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ServerChannelInitializer())
.localAddress(socketAddress)
//设置队列大小
.option(ChannelOption.SO_BACKLOG, 1024)
// 两小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文
.childOption(ChannelOption.SO_KEEPALIVE, true);
//绑定端口,开始接收进来的连接
try {
ChannelFuture future = bootstrap.bind(socketAddress).sync();
log.info("服务器启动开始监听端口: {}", socketAddress.getPort());
// 等待连接被关闭
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//关闭主线程组
bossGroup.shutdownGracefully();
//关闭工作线程组
workGroup.shutdownGracefully();
}
}
}
新建NettyServerHandler
@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 客户端连接会触发
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("Channel active......");
}
/**
* 客户端发消息会触发
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("服务端收到消息:" + msg.toString());
AddRequest addRequest = this.getMethodAdd(msg);
ctx.write(addRequest);
ctx.flush();
}
/**
* 发生异常触发
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
//用netty接收的数据来实现add
public AddRequest getMethodAdd(Object object) {
// 调用服务
int result = 0;
if (object instanceof CalculateRpcRequest) {
CalculateRpcRequest calculateRpcRequest = (CalculateRpcRequest) object;
if ("add".equals(calculateRpcRequest.getMethod())) {
// 返回结果
result = new CalculatorImpl().add(calculateRpcRequest.getA(), calculateRpcRequest.getB());
AddRequest addRequest = new AddRequest();
addRequest.setResult(result);
addRequest.setTimeId(calculateRpcRequest.getTimeId());
return addRequest;
} else {
throw new UnsupportedOperationException();
}
}
return null;
}
}
新建ServerChannelInitializer
public class ServerChannelInitializer extends ChannelInitializer {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//添加编解码
// socketChannel.pipeline().addLast(“decoder”, new StringDecoder(CharsetUtil.UTF_8));
// socketChannel.pipeline().addLast(“encoder”, new StringEncoder(CharsetUtil.UTF_8));
socketChannel.pipeline().addLast(“decoder”,
new ObjectDecoder(ClassResolvers.cacheDisabled(this.getClass().getClassLoader())));
socketChannel.pipeline().addLast(“encoder”, new ObjectEncoder());
socketChannel.pipeline().addLast(new NettyServerHandler());
}
}
在ProviderApp更改如下代码
public static void main(String[] args) throws IOException {
// new ProviderApp().run();
new ProviderApp().runNetty();
}
//通过netty通信
private void runNetty() throws IOException {
//启动服务端
NettyServer nettyServer = new NettyServer();
//监听9090端口
nettyServer.start(new InetSocketAddress("127.0.0.1", 8090));
}
以上,启动时先运行ProviderApp,在运行ComsumerApp
可以看到结果:
传输成功
其中一些值得注意的地方:
进入ServerChannelInitializer方法内部,这里定义了如何处理netty通信的数据.
我们使用的是类处理数据的方式。
ComsumerApp 启动后,将calculateRpcRequest类发送给ProviderApp
provider会接收到该数据,在NettyServerHandler的channelRead()中处理并返回
最后comsumer的NettyClientHandler中会处理返回的数据
这里其实有一个小问题,细心的朋友其实应该能发现。
channelRead() 方法没有返回值,这就是说我们从服务端接收的结果没有办法直接使用。
netty是异步框架,客户端发出数据后,可以做其他的事情,而不必一直等待服务端的返回数据。
所以我们要把每个发起的请求都要设定id,服务端处理好后,将结果和id一并返回。
我们通过id,就可以知道返回的结果是哪一个请求的。
up处理的方式是将其存入一个客户端的map中,
客户端要使用时,可以直接调用。
https://github.com/bobly2/rpc-0.2