利用Java搭建個簡單的Netty通信實例教程
前言
看過dubbo源碼的同學應該都清楚,使用dubbo協(xié)議的底層通信是使用的netty進行交互,而最近看了dubbo的Netty部分后,自己寫了個簡單的Netty通信例子。
準備
工程截圖

模塊詳解
- rpc-common
rpc-common作為各個模塊都需使用的模塊,工程中出現(xiàn)的是一些通信時請求的參數(shù)以及返回的參數(shù),還有一些序列化的工具。
- rpc-client
rpc-client中目前只是單單的一個NettyClient啟動類。
- rpc-server
rpc-client中目前也只是單單的一個NettyServer服務啟動類。
需要的依賴
目前所有的依賴項都出現(xiàn)在 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>
實現(xiàn)
首先我們在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對象的序列化轉(zhuǎn)換很好懂, ByteToMessageDecoder 和 MessageToByteEncoder 分別只要繼承它們,重寫方法后,獲取到Object和Byte,各自轉(zhuǎn)換就OK。
不過如果是有要用到生產(chǎn)上的同學,建議不要使用 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數(shù)據(jù)轉(zhuǎn)化為我們需要的對象
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將對象轉(zhuǎn)換為byte
out.writeInt(data.length); // 先將消息長度寫入,也就是消息頭
out.writeBytes(data); // 消息體中包含我們要發(fā)送的數(shù)據(jù)
}
}
}
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中出現(xiàn)的 ChannelFutureListener.CLOSE ,作為Server端主動關閉與Client端的通信,如果沒有主動Close,那么NettyClient將會一直處于阻塞狀態(tài),得不到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 {
// 創(chuàng)建并初始化 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 請求數(shù)據(jù)并關閉連接
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)));
}
}
測試
如果以上所有內(nèi)容都準備就緒,那么就可以進行調(diào)試了。
啟動順序,先啟動NettyServer,再啟動NettyClient。
總結
記得剛出來工作時,有工作很多年的同事問我了不了解Netty,當時工作太短,直說聽過Putty,現(xiàn)在回想起來真的挺丟人的,哈哈。😋
Netty作為通信框架,如果你了解TCP,而且項目中有類似傳輸信息的需求,又不想集成HTTP或者Socket,那么Netty真的挺實用的。
參考資料:
本項目Github地址:Netty-RPC
到此這篇關于利用Java搭建個簡單的Netty通信的文章就介紹到這了,更多相關Java搭建Netty通信內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot集成IJPay實現(xiàn)微信v3支付的示例代碼
本文主要介紹了SpringBoot集成IJPay實現(xiàn)微信v3支付的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-07-07
關于MyBatis中Mapper?XML熱加載優(yōu)化
大家好,本篇文章主要講的是關于MyBatis中Mapper?XML熱加載優(yōu)化,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下2022-01-01
Spring實戰(zhàn)之使用@Resource配置依賴操作示例
這篇文章主要介紹了Spring實戰(zhàn)之使用@Resource配置依賴操作,結合實例形式分析了Spring使用@Resource配置依賴具體步驟、實現(xiàn)及測試案例,需要的朋友可以參考下2019-12-12
Mybatis中關于自定義mapper.xml時,參數(shù)傳遞的方式及寫法
這篇文章主要介紹了Mybatis中關于自定義mapper.xml時,參數(shù)傳遞的方式及寫法,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12
減小Maven項目生成的JAR包體積實現(xiàn)提升運維效率
在Maven構建Java項目過程中,減小JAR包體積可通過排除不必要的依賴和使依賴jar包獨立于應用jar包來實現(xiàn),在pom.xml文件中使用<exclusions>標簽排除不需要的依賴,有助于顯著降低JAR包大小,此外,將依賴打包到應用外,可減少應用包的體積2024-10-10
Java實戰(zhàn)之小米交易商城系統(tǒng)的實現(xiàn)
這篇文章將利用Java實現(xiàn)小米交易商城系統(tǒng),文中采用的技術有:JSP?、Spring、SpringMVC、MyBatis等,感興趣的小伙伴可以跟隨小編一起學習一下2022-04-04

