使用Java搭建一个简单的Netty通信例子

看过dubbo源码的同学应该都清楚,使用dubbo协议的底层通信是使用的netty进行交互,而最近看了dubbo的Netty部分后,自己写了个简单的Netty通信例子。

本文源地址:实现Netty进行通信


准备

工程截图

模块详解

  • rpc-common

rpc-common作为各个模块都需使用的模块,工程中出现的是一些通信时请求的参数以及返回的参数,还有一些序列化的工具。

  • rpc-client

rpc-client中目前只是单单的一个NettyClient启动类。

  • rpc-server

rpc-client中目前也只是单单的一个NettyServer服务启动类。

需要的依赖

目前所有的依赖项都出现在 rpc-common 下的 pom.xml中。

<dependencies>
<!-- Netty -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.10.Final</version>
</dependency> <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency> <!-- Protostuff -->
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-core</artifactId>
<version>1.0.9</version>
</dependency> <dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-runtime</artifactId>
<version>1.0.9</version>
</dependency> <!-- Objenesis -->
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
<version>2.1</version>
</dependency> <!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.38</version>
</dependency>
</dependencies>

实现

首先我们在common中先定义本次的Request和Response的基类对象。

public class Request {

    private String requestId;

    private Object parameter;

    public String getRequestId() {
return requestId;
} public void setRequestId(String requestId) {
this.requestId = requestId;
} public Object getParameter() {
return parameter;
} public void setParameter(Object parameter) {
this.parameter = parameter;
}
} public class Response { private String requestId; private Object result; public String getRequestId() {
return requestId;
} public void setRequestId(String requestId) {
this.requestId = requestId;
} public Object getResult() {
return result;
} public void setResult(Object result) {
this.result = result;
}
}

使用fastJson进行本次序列化

Netty对象的序列化转换很好懂, ByteToMessageDecoderMessageToByteEncoder 分别只要继承它们,重写方法后,获取到Object和Byte,各自转换就OK。

不过如果是有要用到生产上的同学,建议不要使用 fastJson,因为它的漏洞补丁真的是太多了,可以使用google的 protostuff

public class RpcDecoder extends ByteToMessageDecoder {

    // 目标对象类型进行解码
private Class<?> target; public RpcDecoder(Class target) {
this.target = target;
} @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.readableBytes() < 4) { // 不够长度丢弃
return;
}
in.markReaderIndex(); // 标记一下当前的readIndex的位置
int dataLength = in.readInt(); // 读取传送过来的消息的长度。ByteBuf 的readInt()方法会让他的readIndex增加4 if (in.readableBytes() < dataLength) { // 读到的消息体长度如果小于我们传送过来的消息长度,则resetReaderIndex. 这个配合markReaderIndex使用的。把readIndex重置到mark的地方
in.resetReaderIndex();
return;
}
byte[] data = new byte[dataLength];
in.readBytes(data); Object obj = JSON.parseObject(data, target); // 将byte数据转化为我们需要的对象
out.add(obj);
}
} public class RpcEncoder extends MessageToByteEncoder { //目标对象类型进行编码
private Class<?> target; public RpcEncoder(Class target) {
this.target = target;
} @Override
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
if (target.isInstance(msg)) {
byte[] data = JSON.toJSONBytes(msg); // 使用fastJson将对象转换为byte
out.writeInt(data.length); // 先将消息长度写入,也就是消息头
out.writeBytes(data); // 消息体中包含我们要发送的数据
}
} }

NetyServer

public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Request request = (Request) msg; System.out.println("Client Data:" + JSON.toJSONString(request)); Response response = new Response();
response.setRequestId(request.getRequestId());
response.setResult("Hello Client !"); // client接收到信息后主动关闭掉连接
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
} public class NettyServer { private static final Logger logger = LoggerFactory.getLogger(NettyServer.class); private String ip;
private int port; public NettyServer(String ip, int port) {
this.ip = ip;
this.port = port;
} public void server() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup(); try { final ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.option(ChannelOption.SO_SNDBUF, 32 * 1024)
.option(ChannelOption.SO_RCVBUF, 32 * 1024)
.option(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new RpcDecoder(Request.class))
.addLast(new RpcEncoder(Response.class))
.addLast(new NettyServerHandler());
}
}); serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true); // 开启长连接 ChannelFuture future = serverBootstrap.bind(ip, port).sync(); // if (future.isSuccess()) {
//
// new Register().register("/yanzhenyidai/com.yanzhenyidai.server", ip + ":" + port);
// } future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
} public static void main(String[] args) throws Exception {
new NettyServer("127.0.0.1", 20000).server();
}
}

关键名词:

  • EventLoopGroup

    • workerGroup
    • bossGroup

    Server端的EventLoopGroup分为两个,一般workerGroup作为处理请求,bossGroup作为接收请求。

  • ChannelOption

    • SO_BACKLOG
    • SO_SNDBUF
    • SO_RCVBUF
    • SO_KEEPALIVE

    以上四个常量作为TCP连接中的属性。

  • ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);

    NettyServerHandler中出现的 ChannelFutureListener.CLOSE ,作为Server端主动关闭与Client端的通信,如果没有主动Close,那么NettyClient将会一直处于阻塞状态,得不到NettyServer的返回信息。

NettyClient

public class NettyClient extends SimpleChannelInboundHandler<Response> {

    private final String ip;
private final int port;
private Response response; public NettyClient(String ip, int port) {
this.ip = ip;
this.port = port;
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
} @Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, Response response) throws Exception {
this.response = response;
} public Response client(Request request) throws Exception {
EventLoopGroup group = new NioEventLoopGroup(); try { // 创建并初始化 Netty 客户端 Bootstrap 对象
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline(); pipeline.addLast(new RpcDecoder(Response.class));
pipeline.addLast(new RpcEncoder(Request.class));
pipeline.addLast(NettyClient.this);
}
});
bootstrap.option(ChannelOption.TCP_NODELAY, true); // String[] discover = new Discover().discover("/yanzhenyidai/com.yanzhenyidai.server").split(":"); // 连接 RPC 服务器
ChannelFuture future = bootstrap.connect(ip, port).sync(); // 写入 RPC 请求数据并关闭连接
Channel channel = future.channel(); channel.writeAndFlush(request).sync();
channel.closeFuture().sync(); return response;
} finally {
group.shutdownGracefully();
}
} public static void main(String[] args) throws Exception {
Request request = new Request();
request.setRequestId(UUID.randomUUID().toString());
request.setParameter("Hello Server !");
System.out.println(JSON.toJSONString(new NettyClient("127.0.0.1", 30000).client(request)));
}
}

测试

如果以上所有内容都准备就绪,那么就可以进行调试了。

启动顺序,先启动NettyServer,再启动NettyClient。


总结

记得刚出来工作时,有工作很多年的同事问我了不了解Netty,当时工作太短,直说听过Putty,现在回想起来真的挺丢人的,哈哈。

简单的Java实现Netty进行通信的更多相关文章

  1. Java使用Netty实现简单的RPC

    造一个轮子,实现RPC调用 在写了一个Netty实现通信的简单例子后,萌发了自己实现RPC调用的想法,于是就开始进行了Netty-Rpc的工作,实现了一个简单的RPC调用工程. 如果也有兴趣动手造轮子 ...

  2. java socket实现全双工通信

    java socket实现全双工通信 单工.半双工和全双工的定义 如果在通信过程的任意时刻,信息只能由一方A传到另一方B,则称为单工. 如果在任意时刻,信息既可由A传到B,又能由B传A,但只能由一个方 ...

  3. 从一个简单的Java单例示例谈谈并发

    一个简单的单例示例 单例模式可能是大家经常接触和使用的一个设计模式,你可能会这么写 public class UnsafeLazyInitiallization { private static Un ...

  4. 从一个简单的Java单例示例谈谈并发 JMM JUC

    原文: http://www.open-open.com/lib/view/open1462871898428.html 一个简单的单例示例 单例模式可能是大家经常接触和使用的一个设计模式,你可能会这 ...

  5. Mac OS中Java Servlet与Http通信

    Java中一个Servlet其实就是一个类,用来扩展服务器的性能,服务器上驻留着可以通过“请求-响应”编程模型来访问的应用程序.Servlet可以对任何类型的请求产生响应,但通常只用来扩展Web服务器 ...

  6. java下的串口通信-RXTX

    关于java实现的串口通信,使用的是开源项目RXTX,之前sun公司也有JCL项目,不过已经很久没有更新了,RXTX项目地址:点击打开,但是两个项目的API用法是一样的,只是导入的包不一样而已.简单的 ...

  7. 最简单的 Java内存模型 讲解

    前言 在网上看了很多文章,也看了好几本书中关于JMM的介绍,我发现JMM确实是Java中比较难以理解的概念.网上很多文章中关于JMM的介绍要么是照搬了一些书上的内容,要么就干脆介绍的就是错的.本文试着 ...

  8. java基础学习02(简单的java程序)

    简单的java程序 一.完成的目标 1. 理解java程序的基本组成 2. 如何对程序代码进行注释 3. java标识符的命名规则 4. 了解java中的关键字 5. 使用java定义变量或声明变量 ...

  9. Linux环境下部署完JDK后运行一个简单的Java程序

    前言 前一篇文章详细讲解了如何在Windows环境下安装虚拟机+Linux系统,并且成功部署了JDK. 不过部署完JDK之后,我们判断部署是否成功的依据是看"java -version&qu ...

随机推荐

  1. js的中文英文排序

    本例主要实现 中文汉字按拼音排序的方法和英文按照首字母排序的方法. //要排序的数据 let data = [ {chinese: '蔡司', english: 'Chase'}, {chinese: ...

  2. ViewDragHelper的点击事件处理

    在上一篇ViewDragHelper的介绍后,已经完成了自定义控件SwipeLayout的滑动,这一篇,我们来处理它的点击事件.之前提到过,它有两个子view,最开始显示的是surfaceLayout ...

  3. Mac剪切板中的PNG保存到文件swift

    SwiftGG 教程大全 中文翻译 命令行工具开发教程 Line Programs on macOS Tutorial swift4,较详细 Swift基础中需要注意的点 NSPasteboard M ...

  4. 数值分析实验之曲线最小二乘拟合含有噪声扰动(python实现)

    一.实验目的 掌握最小二乘法拟合离散数据,多项式函数形式拟合曲线以及可以其他可以通过变量变换转化为多项式的拟合曲线目前待实现功能: 1. 最小二乘法的基本实现. 2. 用不同数据量,不同参数,不同的多 ...

  5. 原生Js贪吃蛇游戏实战开发笔记

    前言 本课程是通过JavaScript结合WebAPI DOM实现的一版网页游戏---贪吃蛇的开发全过程,采用面向以象的思想设计开发.通过这个小游戏的开发, 不仅可以掌握JS的语法的应用,还可以学会D ...

  6. Ubuntu当状态栏网络图标隐藏的解决方法汇总

    最有效之一: 直接在终端运行以下命令,以root身份: nm-applet --sm-disable 不建议修改配置文件内容

  7. Spring Boot中只能有一个WebMvcConfigurationSupport配置类

    首先将结论写文章的最前面,一个项目中只能有一个继承WebMvcConfigurationSupport的@Configuration类(使用@EnableMvc效果相同),如果存在多个这样的类,只有一 ...

  8. Linux C语言 检测文件是否存在

    头文件 unistd.h ) { // file exists } else { // file doesn't exist } You can also use R_OK, W_OK, and X_ ...

  9. gitlab环境部署

    一:配置主机名 [root@localhost ~]# hostname gitlab[root@localhost ~]# bash 二:安装依赖包 [root@gitlab ~]# yum -y ...

  10. numpy库的学习笔记

    一.ndarray 1.numpy 库处理的最基础数据类型是由同种元素构成的多维数组(ndarray),简称“数组”. 2.ndarray是一个多维数组的对象,ndarray数组一般要求所有元素类型相 ...