chore: import upstream snapshot with attribution
MCP Bash Server CI / Test MCP Bash Server (dev) (push) Has been cancelled
MCP Bash Server CI / Test MCP Bash Server (release) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:11:39 +08:00
commit c8cebdfeee
4654 changed files with 626756 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You under the Apache License, Version 2.0
~ (the "License"); you may not use this file except in compliance with
~ the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat</artifactId>
<version>2.0-SNAPSHOT</version>
</parent>
<artifactId>hertzbeat-remoting</artifactId>
<name>${project.artifactId}</name>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-common-core</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.remoting;
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
import org.apache.hertzbeat.remoting.netty.NettyRemotingProcessor;
/**
* remoting client interface
*/
public interface RemotingClient extends RemotingService {
/**
* register remoting processor by type
* @param messageType type
* @param processor remoting processor
*/
void registerProcessor(ClusterMsg.MessageType messageType, NettyRemotingProcessor processor);
/**
* send message to server
* @param request request message
*/
void sendMsg(ClusterMsg.Message request);
/**
* send message to server and sync waiting receive server message
* @param request request message
* @param timeoutMillis timeout millis
* @return response message
*/
ClusterMsg.Message sendMsgSync(ClusterMsg.Message request, int timeoutMillis);
}
@@ -0,0 +1,59 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.remoting;
import io.netty.channel.Channel;
import java.util.List;
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
import org.apache.hertzbeat.remoting.netty.NettyHook;
import org.apache.hertzbeat.remoting.netty.NettyRemotingProcessor;
/**
* remoting server interface
*/
public interface RemotingServer extends RemotingService {
/**
* register remoting processor by type
* @param messageType type
* @param processor remoting processor
*/
void registerProcessor(ClusterMsg.MessageType messageType, NettyRemotingProcessor processor);
/**
* send message to client
* @param channel client channel
* @param request request message
*/
void sendMsg(Channel channel, ClusterMsg.Message request);
/**
* send message to client and receive client message
* @param channel client channel
* @param request request message
* @param timeoutMillis timeout millis
* @return response message
*/
ClusterMsg.Message sendMsgSync(Channel channel, ClusterMsg.Message request, int timeoutMillis);
/**
* register hook.
* @param nettyHookList hook list
*/
void registerHook(List<NettyHook> nettyHookList);
}
@@ -0,0 +1,42 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.remoting;
/**
* Derived from Apache Rocketmq org.apache.rocketmq.remoting.RemotingService
* remoting service interface
* @see <a href="https://github.com/apache/rocketmq/blob/develop/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingService.java">RemotingService</a>
*/
public interface RemotingService {
/**
* start server
*/
void start();
/**
* shutdown server
*/
void shutdown();
/**
* test use
* @return is server start
*/
boolean isStart();
}
@@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.remoting.event;
import io.netty.channel.Channel;
/**
* interface some method from ChannelInboundHandler
*/
public interface NettyEventListener {
/**
* do something when channel active
* @param channel netty channel
*/
default void onChannelActive(final Channel channel) throws Exception {
}
/**
* do something when channel idle
* @param channel netty channel
*/
default void onChannelIdle(final Channel channel) throws Exception {
}
}
@@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.remoting.netty;
import lombok.Data;
/**
* netty client config
*/
@Data
public class NettyClientConfig {
private String serverHost;
private int serverPort;
private int connectTimeoutMillis = 10000;
}
@@ -0,0 +1,30 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.remoting.netty;
import io.netty.channel.ChannelHandlerContext;
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
/**
* hook interface, handle something before request processor
*/
public interface NettyHook {
void doBeforeRequest(ChannelHandlerContext ctx, ClusterMsg.Message message);
}
@@ -0,0 +1,158 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.remoting.netty;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.epoll.Epoll;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
import org.apache.hertzbeat.common.util.NetworkUtil;
import org.apache.hertzbeat.remoting.RemotingService;
import org.apache.hertzbeat.remoting.event.NettyEventListener;
/**
* Derived from Apache Rocketmq org.apache.rocketmq.remoting.netty.NettyRemotingAbstract
* netty remote abstract
* @see <a href="https://github.com/apache/rocketmq/blob/develop/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstract.java">NettyRemotingAbstract</a>
*/
@Slf4j
public abstract class NettyRemotingAbstract implements RemotingService {
protected ConcurrentHashMap<ClusterMsg.MessageType, NettyRemotingProcessor> processorTable = new ConcurrentHashMap<>();
protected ConcurrentHashMap<String, ResponseFuture> responseTable = new ConcurrentHashMap<>();
protected List<NettyHook> nettyHookList = new ArrayList<>();
protected NettyEventListener nettyEventListener;
protected NettyRemotingAbstract(NettyEventListener nettyEventListener) {
this.nettyEventListener = nettyEventListener;
}
public void registerProcessor(final ClusterMsg.MessageType messageType, final NettyRemotingProcessor processor) {
this.processorTable.put(messageType, processor);
}
protected void processReceiveMsg(ChannelHandlerContext ctx, ClusterMsg.Message message) {
if (ClusterMsg.Direction.REQUEST.equals(message.getDirection())) {
this.processRequestMsg(ctx, message);
} else {
this.processResponseMsg(ctx, message);
}
}
protected void processRequestMsg(ChannelHandlerContext ctx, ClusterMsg.Message request) {
this.doBeforeRequest(ctx, request);
NettyRemotingProcessor processor = this.processorTable.get(request.getType());
if (processor == null) {
log.info("request type {} not supported", request.getType());
return;
}
ClusterMsg.Message response = processor.handle(ctx, request);
if (response != null) {
ctx.writeAndFlush(response);
}
}
private void doBeforeRequest(ChannelHandlerContext ctx, ClusterMsg.Message request) {
if (CollectionUtils.isEmpty(this.nettyHookList)) {
return;
}
for (NettyHook nettyHook : this.nettyHookList) {
nettyHook.doBeforeRequest(ctx, request);
}
}
protected void processResponseMsg(ChannelHandlerContext ctx, ClusterMsg.Message response) {
// for sync response
if (this.responseTable.containsKey(response.getIdentity())) {
ResponseFuture responseFuture = this.responseTable.get(response.getIdentity());
responseFuture.putResponse(response);
} else {
// async response
NettyRemotingProcessor processor = this.processorTable.get(response.getType());
if (processor != null) {
ClusterMsg.Message repMessage = processor.handle(ctx, response);
if (repMessage != null) {
ctx.writeAndFlush(repMessage);
}
}
}
}
protected void sendMsgImpl(final Channel channel, final ClusterMsg.Message request) {
channel.writeAndFlush(request).addListener(future -> {
if (!future.isSuccess()) {
log.warn("send request message failed. address: {}, ", channel.remoteAddress(), future.cause());
}
});
}
protected ClusterMsg.Message sendMsgSyncImpl(final Channel channel, final ClusterMsg.Message request, final int timeoutMillis) {
final String identity = request.getIdentity();
try {
ResponseFuture responseFuture = new ResponseFuture();
this.responseTable.put(identity, responseFuture);
channel.writeAndFlush(request).addListener(future -> {
if (!future.isSuccess()) {
responseTable.remove(identity);
log.warn("send request message failed. request: {}, address: {}, ", request, channel.remoteAddress(), future.cause());
}
});
ClusterMsg.Message response = responseFuture.waitResponse(timeoutMillis);
if (response == null) {
log.warn("get response message failed, message is null");
}
return response;
} catch (InterruptedException e) {
log.warn("get response message failed, ", e);
} finally {
responseTable.remove(identity);
}
return null;
}
protected void channelActive(ChannelHandlerContext ctx) throws Exception {
if (this.nettyEventListener != null && ctx.channel().isActive()) {
this.nettyEventListener.onChannelActive(ctx.channel());
}
}
protected void channelIdle(ChannelHandlerContext ctx, Object evt) throws Exception {
IdleStateEvent event = (IdleStateEvent) evt;
if (this.nettyEventListener != null && event.state() == IdleState.ALL_IDLE) {
ctx.channel().closeFuture();
this.nettyEventListener.onChannelIdle(ctx.channel());
}
}
protected boolean useEpoll() {
return NetworkUtil.isLinuxPlatform()
&& Epoll.isAvailable();
}
}
@@ -0,0 +1,184 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.remoting.netty;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.compression.ZlibCodecFactory;
import io.netty.handler.codec.compression.ZlibWrapper;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import java.util.concurrent.ThreadFactory;
import org.apache.hertzbeat.common.concurrent.BackgroundTaskExecutor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
import org.apache.hertzbeat.remoting.RemotingClient;
import org.apache.hertzbeat.remoting.event.NettyEventListener;
/**
* Derived from Apache Rocketmq org.apache.rocketmq.remoting.netty.NettyRemotingClient
* netty client
* @see <a href="https://github.com/apache/rocketmq/blob/develop/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java">NettyRemotingClient</a>
*/
@Slf4j
public class NettyRemotingClient extends NettyRemotingAbstract implements RemotingClient {
private static final int DEFAULT_WORKER_THREAD_NUM = Math.min(4, Runtime.getRuntime().availableProcessors());
private final NettyClientConfig nettyClientConfig;
private final BackgroundTaskExecutor threadPool;
private final Bootstrap bootstrap = new Bootstrap();
private EventLoopGroup workerGroup;
private Channel channel;
public NettyRemotingClient(final NettyClientConfig nettyClientConfig,
final NettyEventListener nettyEventListener,
final BackgroundTaskExecutor threadPool) {
super(nettyEventListener);
this.nettyClientConfig = nettyClientConfig;
this.threadPool = threadPool;
}
@Override
public void start() {
this.threadPool.executeLongRunning(() -> {
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setUncaughtExceptionHandler((thread, throwable) -> {
log.error("NettyClientWorker has uncaughtException.");
log.error(throwable.getMessage(), throwable);
})
.setDaemon(true)
.setNameFormat("netty-client-worker-%d")
.build();
String envThreadNum = System.getProperty("hertzbeat.client.worker.thread.num");
int workerThreadNum = envThreadNum != null ? Integer.parseInt(envThreadNum) : DEFAULT_WORKER_THREAD_NUM;
this.workerGroup = new NioEventLoopGroup(workerThreadNum, threadFactory);
this.bootstrap.group(workerGroup)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, this.nettyClientConfig.getConnectTimeoutMillis())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
NettyRemotingClient.this.initChannel(channel);
}
});
this.channel = null;
boolean first = true;
while (!Thread.currentThread().isInterrupted()
&& (first || this.channel == null || !this.channel.isActive())) {
first = false;
try {
this.channel = this.bootstrap
.connect(this.nettyClientConfig.getServerHost(), this.nettyClientConfig.getServerPort())
.sync().channel();
this.channel.closeFuture().sync();
} catch (InterruptedException ignored) {
log.info("client shutdown now!");
Thread.currentThread().interrupt();
} catch (Exception e2) {
log.error("client connect to server error: {}. try after 10s.", e2.getMessage());
try {
Thread.sleep(10000);
} catch (InterruptedException ignored) {
}
}
}
workerGroup.shutdownGracefully();
});
}
private void initChannel(final SocketChannel channel) {
ChannelPipeline pipeline = channel.pipeline();
// zip
pipeline.addLast(ZlibCodecFactory.newZlibEncoder(ZlibWrapper.GZIP));
pipeline.addLast(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP));
// protocol buf encode decode
pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast(new ProtobufDecoder(ClusterMsg.Message.getDefaultInstance()));
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast(new ProtobufEncoder());
pipeline.addLast(new NettyClientHandler());
}
@Override
public void shutdown() {
try {
if (this.channel != null) {
this.channel.close();
}
this.workerGroup.shutdownGracefully();
this.threadPool.destroy();
} catch (Exception e) {
log.error("netty client shutdown exception, ", e);
}
}
@Override
public boolean isStart() {
return this.channel != null && this.channel.isActive();
}
@Override
public void sendMsg(final ClusterMsg.Message request) {
this.sendMsgImpl(this.channel, request);
}
@Override
public ClusterMsg.Message sendMsgSync(ClusterMsg.Message request, int timeoutMillis) {
return this.sendMsgSyncImpl(this.channel, request, timeoutMillis);
}
class NettyClientHandler extends SimpleChannelInboundHandler<ClusterMsg.Message> {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
NettyRemotingClient.this.channelActive(ctx);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ClusterMsg.Message msg) throws Exception {
NettyRemotingClient.this.processReceiveMsg(ctx, msg);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
NettyRemotingClient.this.channelIdle(ctx, evt);
}
}
}
@@ -0,0 +1,32 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.remoting.netty;
import io.netty.channel.ChannelHandlerContext;
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
/**
* Derived from Apache Rocketmq org.apache.rocketmq.remoting.netty.NettyRequestProcessor
* netty remoting processor
* @see <a href="https://github.com/apache/rocketmq/blob/develop/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRequestProcessor.java">NettyRequestProcessor</a>
*/
public interface NettyRemotingProcessor {
ClusterMsg.Message handle(ChannelHandlerContext ctx, ClusterMsg.Message message);
}
@@ -0,0 +1,203 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.remoting.netty;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.compression.ZlibCodecFactory;
import io.netty.handler.codec.compression.ZlibWrapper;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;
import java.util.List;
import java.util.concurrent.ThreadFactory;
import org.apache.hertzbeat.common.concurrent.BackgroundTaskExecutor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
import org.apache.hertzbeat.remoting.RemotingServer;
import org.apache.hertzbeat.remoting.event.NettyEventListener;
/**
* Derived from Apache Rocketmq org.apache.rocketmq.remoting.netty.NettyRemotingServer
* netty server
* @see <a href="https://github.com/apache/rocketmq/blob/develop/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java">NettyRemotingServer</a>
*/
@Slf4j
public class NettyRemotingServer extends NettyRemotingAbstract implements RemotingServer {
private final NettyServerConfig nettyServerConfig;
private final BackgroundTaskExecutor threadPool;
private EventLoopGroup bossGroup;
private EventLoopGroup workerGroup;
private Channel channel = null;
public NettyRemotingServer(final NettyServerConfig nettyServerConfig,
final NettyEventListener nettyEventListener,
final BackgroundTaskExecutor threadPool) {
super(nettyEventListener);
this.nettyServerConfig = nettyServerConfig;
this.threadPool = threadPool;
}
@Override
public void start() {
this.threadPool.executeLongRunning(() -> {
int port = this.nettyServerConfig.getPort();
ThreadFactory bossThreadFactory = new ThreadFactoryBuilder()
.setUncaughtExceptionHandler((thread, throwable) -> {
log.error("NettyServerBoss has uncaughtException.");
log.error(throwable.getMessage(), throwable);
})
.setDaemon(true)
.setNameFormat("netty-server-boss-%d")
.build();
ThreadFactory workerThreadFactory = new ThreadFactoryBuilder()
.setUncaughtExceptionHandler((thread, throwable) -> {
log.error("NettyServerWorker has uncaughtException.");
log.error(throwable.getMessage(), throwable);
})
.setDaemon(true)
.setNameFormat("netty-server-worker-%d")
.build();
if (this.useEpoll()) {
bossGroup = new EpollEventLoopGroup(bossThreadFactory);
workerGroup = new EpollEventLoopGroup(workerThreadFactory);
} else {
bossGroup = new NioEventLoopGroup(bossThreadFactory);
workerGroup = new NioEventLoopGroup(workerThreadFactory);
}
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(this.useEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_KEEPALIVE, false)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
NettyRemotingServer.this.initChannel(channel);
}
});
channel = b.bind(port).sync().channel();
channel.closeFuture().sync();
} catch (InterruptedException ignored) {
log.info("server shutdown now!");
} catch (Exception e) {
log.error("Netty Server start exception, {}", e.getMessage());
throw new RuntimeException(e);
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
});
}
@Override
public boolean isStart() {
return this.channel != null && this.channel.isActive();
}
private void initChannel(final SocketChannel channel) {
ChannelPipeline pipeline = channel.pipeline();
// zip
pipeline.addLast(ZlibCodecFactory.newZlibEncoder(ZlibWrapper.GZIP));
pipeline.addLast(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP));
// protocol buf encode decode
pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast(new ProtobufDecoder(ClusterMsg.Message.getDefaultInstance()));
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast(new ProtobufEncoder());
// idle state
pipeline.addLast(new IdleStateHandler(0, 0, nettyServerConfig.getIdleStateEventTriggerTime()));
pipeline.addLast(new NettyServerHandler());
}
@Override
public void shutdown() {
try {
this.bossGroup.shutdownGracefully();
this.workerGroup.shutdownGracefully();
this.threadPool.destroy();
} catch (Exception e) {
log.error("Netty Server shutdown exception, ", e);
}
}
@Override
public void sendMsg(final Channel channel, final ClusterMsg.Message request) {
this.sendMsgImpl(channel, request);
}
@Override
public ClusterMsg.Message sendMsgSync(final Channel channel, final ClusterMsg.Message request, final int timeoutMillis) {
return this.sendMsgSyncImpl(channel, request, timeoutMillis);
}
@Override
public void registerHook(List<NettyHook> nettyHookList) {
this.nettyHookList.addAll(nettyHookList);
}
/**
* netty server handler
*/
@ChannelHandler.Sharable
public class NettyServerHandler extends SimpleChannelInboundHandler<ClusterMsg.Message> {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
NettyRemotingServer.this.channelActive(ctx);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ClusterMsg.Message msg) throws Exception {
NettyRemotingServer.this.processReceiveMsg(ctx, msg);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
NettyRemotingServer.this.channelIdle(ctx, evt);
}
}
}
@@ -0,0 +1,32 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.remoting.netty;
import lombok.Data;
/**
* netty server config
*/
@Data
public class NettyServerConfig {
private Integer port;
private Integer idleStateEventTriggerTime = 100;
}
@@ -0,0 +1,43 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.remoting.netty;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
/**
* netty response future
*/
public class ResponseFuture {
private final CountDownLatch countDownLatch = new CountDownLatch(1);
private ClusterMsg.Message response;
public ClusterMsg.Message waitResponse(final long timeoutMillis) throws InterruptedException {
this.countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
return this.response;
}
public void putResponse(final ClusterMsg.Message response) {
this.response = response;
this.countDownLatch.countDown();
}
}
@@ -0,0 +1,178 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.remoting;
import com.google.protobuf.ByteString;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
import org.apache.hertzbeat.common.concurrent.BackgroundTaskExecutor;
import org.apache.hertzbeat.remoting.netty.NettyClientConfig;
import org.apache.hertzbeat.remoting.netty.NettyRemotingClient;
import org.apache.hertzbeat.remoting.netty.NettyRemotingServer;
import org.apache.hertzbeat.remoting.netty.NettyServerConfig;
import org.assertj.core.util.Lists;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* test NettyRemotingClient and NettyRemotingServer
*/
public class RemotingServiceTest {
private final BackgroundTaskExecutor threadPool = new BackgroundTaskExecutor() {
private final ExecutorService executor = Executors.newCachedThreadPool();
@Override
public void execute(Runnable runnable) {
executor.execute(runnable);
}
@Override
public void executeLongRunning(Runnable runnable) {
executor.execute(runnable);
}
@Override
public void destroy() {
executor.shutdownNow();
}
};
private RemotingServer remotingServer;
private RemotingClient remotingClient;
public RemotingServer createRemotingServer(int port) {
NettyServerConfig nettyServerConfig = new NettyServerConfig();
nettyServerConfig.setPort(port);
// todo test NettyEventListener
RemotingServer server = new NettyRemotingServer(nettyServerConfig, null, threadPool);
server.start();
return server;
}
public RemotingClient createRemotingClient(int port) {
NettyClientConfig nettyClientConfig = new NettyClientConfig();
nettyClientConfig.setServerHost("localhost");
nettyClientConfig.setServerPort(port);
RemotingClient client = new NettyRemotingClient(nettyClientConfig, null, threadPool);
client.start();
return client;
}
@BeforeEach
public void setUp() throws InterruptedException {
int port = 10000 + (int) (Math.random() * 10000);
remotingServer = createRemotingServer(port);
// await remotingServer start
int count = 5;
while (count-- > 0) {
Thread.sleep(1000);
if (remotingServer.isStart()) {
break;
}
}
if (count < 0) {
throw new RuntimeException("remoting server start error");
}
remotingClient = createRemotingClient(port);
// await remotingClient start
count = 5;
while (count-- > 0) {
Thread.sleep(1000);
if (remotingClient.isStart()) {
break;
}
}
if (count < 0) {
throw new RuntimeException("remoting client start error");
}
}
@AfterEach
public void shutdown() {
this.remotingClient.shutdown();
this.remotingServer.shutdown();
}
@Test
public void testSendMsg() {
final String msg = "hello world";
this.remotingServer.registerProcessor(ClusterMsg.MessageType.HEARTBEAT, (ctx, message) -> {
Assertions.assertEquals(msg, message.getMsg().toStringUtf8());
return null;
});
ClusterMsg.Message request = ClusterMsg.Message.newBuilder()
.setDirection(ClusterMsg.Direction.REQUEST)
.setType(ClusterMsg.MessageType.HEARTBEAT)
.setMsg(ByteString.copyFromUtf8(msg))
.build();
this.remotingClient.sendMsg(request);
}
@Test
public void testSendMsgSync() {
final String requestMsg = "request";
final String responseMsg = "response";
this.remotingServer.registerProcessor(ClusterMsg.MessageType.HEARTBEAT, (ctx, message) -> {
Assertions.assertEquals(requestMsg, message.getMsg().toStringUtf8());
return ClusterMsg.Message.newBuilder()
.setDirection(ClusterMsg.Direction.RESPONSE)
.setMsg(ByteString.copyFromUtf8(responseMsg))
.build();
});
ClusterMsg.Message request = ClusterMsg.Message.newBuilder()
.setDirection(ClusterMsg.Direction.REQUEST)
.setType(ClusterMsg.MessageType.HEARTBEAT)
.setMsg(ByteString.copyFromUtf8(requestMsg))
.build();
ClusterMsg.Message response = this.remotingClient.sendMsgSync(request, 3000);
Assertions.assertEquals(responseMsg, response.getMsg().toStringUtf8());
}
@Test
public void testNettyHook() {
this.remotingServer.registerHook(Lists.newArrayList(
(ctx, message) -> Assertions.assertEquals("hello world", message.getMsg().toStringUtf8())
));
this.remotingServer.registerProcessor(ClusterMsg.MessageType.HEARTBEAT, (ctx, message) ->
ClusterMsg.Message.newBuilder()
.setDirection(ClusterMsg.Direction.RESPONSE)
.build());
ClusterMsg.Message request = ClusterMsg.Message.newBuilder()
.setDirection(ClusterMsg.Direction.REQUEST)
.setType(ClusterMsg.MessageType.HEARTBEAT)
.setMsg(ByteString.copyFromUtf8("hello world"))
.build();
this.remotingClient.sendMsg(request);
}
}