使用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. MySQL之唯一索引、外键的变种、SQL语句数据行操作补充

    0.唯一索引 unique对num进行唯一限制,表示num是独一无二的,uql是唯一索引名称 上面为联合索引:num和xx不能完全一样  1.外键的变种 a. 用户表和部门表 用户: 1 alex 1 ...

  2. Python数据分析:大众点评数据进行选址

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:砂糖侠 如果你处于想学Python或者正在学习Python,Pyth ...

  3. javascript-网页尺寸

    scrollWidth:对象的实际内容的宽度,不包边线宽度,会随对象中内容超过可视区后而变大. clientWidth:对象内容的可视区的宽度,不包滚动条等边线,会随对象显示大小的变化而改变. off ...

  4. phpcms 后台分页

    php 代码public function init() { $where='';//条件 $page = $_GET['page'] ? intval($_GET['page']) : '1'; $ ...

  5. C# 9 新特性:代码生成器、编译时反射

    前言 今天 .NET 官方博客宣布 C# 9 Source Generators 第一个预览版发布,这是一个用户已经喊了快 5 年特性,今天终于发布了. 简介 Source Generators 顾名 ...

  6. 怎么break java8 stream的foreach

    目录 简介 使用Spliterator 自定义forEach方法 总结 怎么break java8 stream的foreach 简介 我们通常需要在java stream中遍历处理里面的数据,其中f ...

  7. Solidity的Bytecode和Opcode简介

    Solidity的Bytecode和Opcode简介 随着我们更深入地编写智能合约,我们将遇到诸如" PUSH1"," SSTORE"," CALLV ...

  8. linux 之学习路线

    原文地址:https://www.oschina.net/question/587367_156024 推荐的发行版如下: UBUNTU 适合纯菜鸟,追求稳定的官方支持,对系统稳定性要求较弱,喜欢最新 ...

  9. BeanDefinition源码解析

    我们知道BeanDefintion定义了Bean在IoC容器内的基本数据结构.在学习IoC之前先了解BeanDefition对我们理解IoC容器是有帮助的. 首先BeanDefinition是一个接口 ...

  10. 在CentOS 7中安装配置JDK8

    为什么80%的码农都做不了架构师?>>>   ###说明 参考博客:http://blog.csdn.net/czmchen/article/details/41047187 系统环 ...