使用Netty實(shí)現(xiàn)類似Dubbo的遠(yuǎn)程接口調(diào)用的實(shí)現(xiàn)方法
一、Netty簡(jiǎn)介
Netty 是一個(gè)基于NIO的客戶、服務(wù)器端的編程框架,使用Netty 可以確保你快速和簡(jiǎn)單的開發(fā)出一個(gè)網(wǎng)絡(luò)應(yīng)用,例如實(shí)現(xiàn)了某種協(xié)議的客戶、服務(wù)端應(yīng)用。Netty相當(dāng)于簡(jiǎn)化和流線化了網(wǎng)絡(luò)應(yīng)用的編程開發(fā)過(guò)程,例如:基于TCP和UDP的socket服務(wù)開發(fā)。
本文通過(guò)完整示例代碼,詳細(xì)介紹netty實(shí)現(xiàn)類似dubbo的遠(yuǎn)程網(wǎng)絡(luò)通訊,如有錯(cuò)誤歡迎指正!
實(shí)現(xiàn)步驟:
- 創(chuàng)建接口和實(shí)現(xiàn)類
- 創(chuàng)建客戶端代碼
- 通過(guò)動(dòng)態(tài)代理模式,封裝netty遠(yuǎn)程接口調(diào)用
- 通過(guò)異步線程等待/通知,實(shí)現(xiàn)異步轉(zhuǎn)同步
- 創(chuàng)建服務(wù)端代碼
- 自定義編碼解碼器
- 編寫測(cè)試客戶端發(fā)送請(qǐng)求代碼
二、完整代碼實(shí)現(xiàn)
工程依賴引入
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.75.Final</version>
</dependency>
1、創(chuàng)建接口和實(shí)現(xiàn)類
定義簡(jiǎn)單接口和實(shí)現(xiàn)類。通過(guò)注解定義接口和服務(wù)實(shí)現(xiàn),在后續(xù)代碼中解析注解。
@ServiceEntry定義接口serviceId
@MyService定義服務(wù)實(shí)現(xiàn)類
public interface IHelloService {
@ServiceEntry(serviceId = "001", name = "hello")
String hello(String msg);
}
@MyService
public class HelloServiceImpl implements IHelloService {
@Override
public String hello(String msg) {
return "re:這里是服務(wù)端,已收到客戶端消息:" + msg.hashCode();
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ServiceEntry {
/**
* 服務(wù)Id
*/
String serviceId();
/**
* 服務(wù)名稱
*/
String name() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyService {
String value() default "";
}
2、客戶端代碼實(shí)現(xiàn)及動(dòng)態(tài)代理和異步轉(zhuǎn)同步
1)創(chuàng)建客戶端Handler,MyClientHandler,繼承ChannelInboundHandlerAdapter,并實(shí)現(xiàn)Callable接口
- 客戶端發(fā)送請(qǐng)求時(shí),會(huì)調(diào)用call方法,在這里將異步轉(zhuǎn)同步
- 將請(qǐng)求context放入map,并等待線程,在收到服務(wù)端返回時(shí),異步通知線程執(zhí)行,返回結(jié)果數(shù)據(jù)
- 收到服務(wù)端返回時(shí),設(shè)置返回結(jié)果數(shù)據(jù),并通知線程執(zhí)行
public class MyClientHandler extends ChannelInboundHandlerAdapter implements Callable<String> {
private ChannelHandlerContext ctx;
private ConcurrentHashMap<String, SyncSendContext> syncSendContextMap = new ConcurrentHashMap<>();
private Object[] param;
private String serviceId;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客戶端和服務(wù)端鏈接成功");
this.ctx = ctx;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("客戶端收到服務(wù)端回復(fù): " + msg);
ResponseData data = (ResponseData) msg;
String id = data.getId();
// 收到服務(wù)端返回時(shí),設(shè)置返回結(jié)果數(shù)據(jù),并通知線程執(zhí)行
SyncSendContext context = syncSendContextMap.get(id);
context.setResp(data);
synchronized (context) {
context.notify();
}
}
@Override
public String call() throws Exception {
System.out.println("客戶端向服務(wù)端發(fā)送消息: " + param[0].toString());
String id = UUID.randomUUID().toString();
RequestData data = new RequestData();
data.setId(id);
//強(qiáng)制設(shè)置參數(shù)1
data.setData(param[0].toString());
data.setServiceId(serviceId);
SyncSendContext context = new SyncSendContext();
context.setRequest(data);
// 將請(qǐng)求context放入map,并等待線程,在收到服務(wù)端返回時(shí),異步通知線程執(zhí)行,返回結(jié)果數(shù)據(jù)
syncSendContextMap.put(id, context);
synchronized (context) {
ctx.writeAndFlush(data);
context.wait();
return (String) context.getResp().getData();
}
}
public void setParam(Object[] param) {
this.param = param;
}
public void setServiceId(String serviceId) {
this.serviceId = serviceId;
}
}
2)創(chuàng)建客戶端代碼,MyClient
- 通過(guò)動(dòng)態(tài)代理,包裝遠(yuǎn)程服務(wù)請(qǐng)求
- 初始化服務(wù)端鏈接,通過(guò)雙檢鎖確保clientHandler是單例實(shí)現(xiàn)
- 發(fā)送請(qǐng)求時(shí),通過(guò)線程池異步發(fā)送clientHandler
public class MyClient {
private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
private MyClientHandler clientHandler;
// 通過(guò)動(dòng)態(tài)代理,包裝遠(yuǎn)程服務(wù)請(qǐng)求
public <T> T getServie(final Class<T> service) {
return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{service},
(proxy, method, args) -> {
if (clientHandler == null) {
init("127.0.0.1", 7000);
}
ServiceEntry annotation = method.getAnnotation(ServiceEntry.class);
if (annotation == null) {
return null;
}
clientHandler.setParam(args);
clientHandler.setServiceId(annotation.serviceId());
return executor.submit(clientHandler).get();
});
}
// 初始化服務(wù)端鏈接,通過(guò)雙檢鎖確保clientHandler是單例實(shí)現(xiàn)
private synchronized void init(String hostname, int port) {
if (clientHandler != null) {
return;
}
clientHandler = new MyClientHandler();
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap().group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new ResponseMessageCodec());
pipeline.addLast(new RequestMessageCodec());
pipeline.addLast(clientHandler);
}
});
bootstrap.connect(hostname, port).sync();
} catch (Exception e) {
e.printStackTrace();
}
}
}3、服務(wù)端代碼實(shí)現(xiàn)
1)創(chuàng)建服務(wù)工程類ServiceFacatory,解析注解保存服務(wù)接口和實(shí)現(xiàn)類,調(diào)用的時(shí)候從Map直接獲取
public class ServiceFacatory {
private static final Map<String, Method> methodMap = new HashMap<>();
private static final Map<String, Object> serviceMap = new HashMap<>();
public static void init() throws Exception {
// 要掃描的包
String packages = "com.hj.netty.dubbo.api";
Set<MethodInfo> methods = PackageUtils.findClassAnnotationMethods(packages, ServiceEntry.class);
for (MethodInfo info : methods) {
ServiceEntry serviceEntry = (ServiceEntry) info.getAnnotation();
methodMap.put(serviceEntry.serviceId(), info.getMethod());
String serviceName = info.getMethod().getDeclaringClass().getName();
if (!serviceMap.containsKey(serviceName)) {
Object instance = info.getMethod().getDeclaringClass().newInstance();
serviceMap.put(serviceName, instance);
}
}
}
public static Object invoke(String serviceId, Object args) throws Exception {
Method method = methodMap.get(serviceId);
String serviceName = method.getDeclaringClass().getName();
Object instance = serviceMap.get(serviceName);
Object result = method.invoke(instance, args);
return result;
}
}
@Data
@AllArgsConstructor
public class MethodInfo {
private Annotation annotation;
private Method method;
}2)包解析工具類,解析指定目錄下的所有service類
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.SystemPropertyUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.*;
public class PackageUtils {
private final static Logger log = LoggerFactory.getLogger(PackageUtils.class);
//掃描 scanPackages 下的文件的匹配符
protected static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
/**
* 結(jié)合spring的類掃描方式
* 根據(jù)需要掃描的包路徑及相應(yīng)的注解,獲取最終測(cè)method集合
* 僅返回public方法,如果方法是非public類型的,不會(huì)被返回
* 可以掃描工程下的class文件及jar中的class文件
*
* @param scanPackages
* @param annotation
* @return
*/
public static Set<MethodInfo> findClassAnnotationMethods(String scanPackages, Class<? extends Annotation> annotation) {
//獲取所有的類
Set<String> clazzSet = findPackageClass(scanPackages);
Set<MethodInfo> methods = new HashSet<>();
//遍歷類,查詢相應(yīng)的annotation方法
for (String clazz : clazzSet) {
try {
Set<MethodInfo> ms = findAnnotationMethods(clazz, annotation);
methods.addAll(ms);
} catch (ClassNotFoundException ignore) {
}
}
return methods;
}
public static Set<MethodInfo> findAnnotationMethods(String fullClassName, Class<? extends Annotation> anno) throws ClassNotFoundException {
Set<MethodInfo> methodSet = new HashSet<>();
Class<?> clz = Class.forName(fullClassName);
// 存儲(chǔ)接口中定義的方法
Map<String, Method> mapMethodInf = new HashMap<>();
for (int i = 0; i < clz.getInterfaces().length; i++) {
Class<?> inf = clz.getInterfaces()[i];
Method[] methods = inf.getDeclaredMethods();
for (Method method : methods) {
String key = getMethodKey(method);
mapMethodInf.put(key, method);
}
}
Method[] methods = clz.getDeclaredMethods();
for (Method method : methods) {
if (method.getModifiers() != Modifier.PUBLIC) {
continue;
}
Annotation annotation = method.getAnnotation(anno);
if (annotation != null) {
methodSet.add(new MethodInfo(annotation,method));
} else {
// 從接口中讀取對(duì)應(yīng)的方法
String key = getMethodKey(method);
Method methodInf = mapMethodInf.get(key);
annotation = methodInf.getAnnotation(anno);
if (annotation != null) {
methodSet.add(new MethodInfo(annotation,method));
}
}
}
return methodSet;
}
/**
* 根據(jù)掃描包的,查詢下面的所有類
*
* @param scanPackages 掃描的package路徑
* @return
*/
private static Set<String> findPackageClass(String scanPackages) {
if (StringUtils.isBlank(scanPackages)) {
return Collections.EMPTY_SET;
}
//驗(yàn)證及排重包路徑,避免父子路徑多次掃描
Set<String> packages = checkPackage(scanPackages);
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
Set<String> clazzSet = new HashSet<String>();
for (String basePackage : packages) {
if (StringUtils.isBlank(basePackage)) {
continue;
}
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
org.springframework.util.ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage)) + "/" + DEFAULT_RESOURCE_PATTERN;
try {
Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
for (Resource resource : resources) {
//檢查resource,這里的resource都是class
String clazz = loadClassName(metadataReaderFactory, resource);
clazzSet.add(clazz);
}
} catch (Exception e) {
log.error("獲取包下面的類信息失敗,package:" + basePackage, e);
}
}
return clazzSet;
}
/**
* 排重、檢測(cè)package父子關(guān)系,避免多次掃描
*
* @param scanPackages
* @return 返回檢查后有效的路徑集合
*/
private static Set<String> checkPackage(String scanPackages) {
if (StringUtils.isBlank(scanPackages)) {
return Collections.EMPTY_SET;
}
Set<String> packages = new HashSet<>();
//排重路徑
Collections.addAll(packages, scanPackages.split(","));
String[] strings = packages.toArray(new String[packages.size()]);
for (String pInArr : strings) {
if (StringUtils.isBlank(pInArr) || pInArr.equals(".") || pInArr.startsWith(".")) {
continue;
}
if (pInArr.endsWith(".")) {
pInArr = pInArr.substring(0, pInArr.length() - 1);
}
Iterator<String> packageIte = packages.iterator();
boolean needAdd = true;
while (packageIte.hasNext()) {
String pack = packageIte.next();
if (pInArr.startsWith(pack + ".")) {
//如果待加入的路徑是已經(jīng)加入的pack的子集,不加入
needAdd = false;
} else if (pack.startsWith(pInArr + ".")) {
//如果待加入的路徑是已經(jīng)加入的pack的父集,刪除已加入的pack
packageIte.remove();
}
}
if (needAdd) {
packages.add(pInArr);
}
}
return packages;
}
/**
* 加載資源,根據(jù)resource獲取className
*
* @param metadataReaderFactory spring中用來(lái)讀取resource為class的工具
* @param resource 這里的資源就是一個(gè)Class
*/
private static String loadClassName(MetadataReaderFactory metadataReaderFactory, Resource resource) {
try {
if (resource.isReadable()) {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
if (metadataReader != null) {
return metadataReader.getClassMetadata().getClassName();
}
}
} catch (Exception e) {
log.error("根據(jù)resource獲取類名稱失敗", e);
}
return null;
}
private static String getMethodKey(Method method) {
StringBuilder key = new StringBuilder(method.getName());
for (Parameter parameter : method.getParameters()) {
key.append(parameter.getType().getName())
.append(parameter.getName());
}
return key.toString();
}
}3)創(chuàng)建服務(wù)端Handler類,接收客戶端請(qǐng)求,并調(diào)用服務(wù)實(shí)現(xiàn)類執(zhí)行接口
public class MyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客戶端接入");
super.channelActive(ctx);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("收到客戶端消息:" + msg);
RequestData req = (RequestData) msg;
if (req != null) {
String args = req.getData();
String serviceId = req.getServiceId();
// 調(diào)用服務(wù)實(shí)現(xiàn)類
Object res = ServiceFacatory.invoke(serviceId, args);
ResponseData resp = new ResponseData();
resp.setData(res);
resp.setId(req.getId());
ctx.writeAndFlush(resp);
}
System.out.println("----------響應(yīng)結(jié)束----------" + req.getData());
}
}4)創(chuàng)建服務(wù)端啟動(dòng)類MyServer、ServerApp,啟動(dòng)端口監(jiān)聽;加入編解碼器和服務(wù)端MyServerHandler
public class MyServer {
public static void start(String hostname, int port) throws Exception {
ServiceFacatory.init();
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap().group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.TCP_NODELAY, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new RequestMessageCodec());
pipeline.addLast(new ResponseMessageCodec());
pipeline.addLast(new MyServerHandler());
}
});
ChannelFuture future = bootstrap.bind(hostname, port).sync();
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
public class ServerApp {
public static void main(String[] args) throws Exception {
MyServer.start("127.0.0.1", 7000);
}
}4、自定義編碼解碼器
1)創(chuàng)建請(qǐng)求數(shù)據(jù)編解碼器RequestMessageCodec,實(shí)現(xiàn)String和請(qǐng)求參數(shù)對(duì)象RequestData之間互相轉(zhuǎn)換
public class RequestMessageCodec extends MessageToMessageCodec<String, RequestData> {
@Override
protected void encode(ChannelHandlerContext ctx, RequestData msg, List<Object> out) throws Exception {
System.out.println("RequestMessageCodec.encode 被調(diào)用 " + msg);
String json = JSONObject.toJSONString(msg);
out.add(json);
}
@Override
protected void decode(ChannelHandlerContext ctx, String msg, List<Object> out) throws Exception {
System.out.println("RequestMessageCodec.decode 被調(diào)用 " + msg);
RequestData po = JSONObject.parseObject(msg, RequestData.class);
out.add(po);
}
}
2)創(chuàng)建服務(wù)響應(yīng)數(shù)據(jù)編解碼器ResponseMessageCodec,實(shí)現(xiàn)String和響應(yīng)數(shù)據(jù)對(duì)象ResponseData之間互相轉(zhuǎn)換
public class ResponseMessageCodec extends MessageToMessageCodec<String, ResponseData> {
@Override
protected void encode(ChannelHandlerContext ctx, ResponseData msg, List<Object> out) throws Exception {
System.out.println("ResponseMessageCodec.encode 被調(diào)用 " + msg);
String json = JSONObject.toJSONString(msg);
out.add(json);
}
@Override
protected void decode(ChannelHandlerContext ctx, String msg, List<Object> out) throws Exception {
System.out.println("ResponseMessageCodec.decode 被調(diào)用 " + msg);
ResponseData po = JSONObject.parseObject(msg, ResponseData.class);
out.add(po);
}
}
3)創(chuàng)建請(qǐng)求、響應(yīng)VO
@Data
public class RequestData {
private String id;
private String serviceId;
private String data;
}
@Data
public class ResponseData {
private String id;
private Object data;
}
@Data
public class SyncSendContext {
private ResponseData resp;
private RequestData request;
}
5、編寫測(cè)試客戶端發(fā)送請(qǐng)求代碼
1)創(chuàng)建客戶端請(qǐng)求類ClientTest,模擬發(fā)送數(shù)據(jù)
public class ClientTest {
public static void main(String[] args) throws Exception {
MyClient client = new MyClient();
IHelloService servie = client.getServie(IHelloService.class);
for (int i = 0; i < 5; i++) {
Thread.sleep(2 * 1000);
String res = servie.hello("你好 服務(wù)端 ~~ " + i);
System.out.println("service 得到服務(wù)端返回消息: " + res);
System.out.println("-----------------------------" + i +" 結(jié)束");
}
}
}
2)運(yùn)行ServerApp,啟動(dòng)服務(wù)端,運(yùn)行ClientTest模擬客戶端發(fā)送數(shù)據(jù)
客戶端日志:
客戶端和服務(wù)端鏈接成功
客戶端向服務(wù)端發(fā)送消息: 你好 服務(wù)端 ~~ 0
RequestMessageCodec.encode 被調(diào)用 RequestData(id=d081f840-4367-42d3-909c-32f6a1654c60, serviceId=001, data=你好 服務(wù)端 ~~ 0)
ResponseMessageCodec.decode 被調(diào)用 {"data":"re:這里是服務(wù)端,已收到客戶端消息:1845339960","id":"d081f840-4367-42d3-909c-32f6a1654c60"}
客戶端收到服務(wù)端回復(fù): ResponseData(id=d081f840-4367-42d3-909c-32f6a1654c60, data=re:這里是服務(wù)端,已收到客戶端消息:1845339960)
service 得到服務(wù)端返回消息: re:這里是服務(wù)端,已收到客戶端消息:1845339960
-----------------------------0 結(jié)束
客戶端向服務(wù)端發(fā)送消息: 你好 服務(wù)端 ~~ 1
RequestMessageCodec.encode 被調(diào)用 RequestData(id=d49105b0-2624-43c2-bb19-c826987133f1, serviceId=001, data=你好 服務(wù)端 ~~ 1)
ResponseMessageCodec.decode 被調(diào)用 {"data":"re:這里是服務(wù)端,已收到客戶端消息:1845339961","id":"d49105b0-2624-43c2-bb19-c826987133f1"}
客戶端收到服務(wù)端回復(fù): ResponseData(id=d49105b0-2624-43c2-bb19-c826987133f1, data=re:這里是服務(wù)端,已收到客戶端消息:1845339961)
service 得到服務(wù)端返回消息: re:這里是服務(wù)端,已收到客戶端消息:1845339961
-----------------------------1 結(jié)束
客戶端向服務(wù)端發(fā)送消息: 你好 服務(wù)端 ~~ 2
RequestMessageCodec.encode 被調(diào)用 RequestData(id=13f82f4a-0a2f-41cc-8420-38ab20fab2d2, serviceId=001, data=你好 服務(wù)端 ~~ 2)
ResponseMessageCodec.decode 被調(diào)用 {"data":"re:這里是服務(wù)端,已收到客戶端消息:1845339962","id":"13f82f4a-0a2f-41cc-8420-38ab20fab2d2"}
客戶端收到服務(wù)端回復(fù): ResponseData(id=13f82f4a-0a2f-41cc-8420-38ab20fab2d2, data=re:這里是服務(wù)端,已收到客戶端消息:1845339962)
service 得到服務(wù)端返回消息: re:這里是服務(wù)端,已收到客戶端消息:1845339962
-----------------------------2 結(jié)束
客戶端向服務(wù)端發(fā)送消息: 你好 服務(wù)端 ~~ 3
RequestMessageCodec.encode 被調(diào)用 RequestData(id=f4576cbd-8ee5-438c-ae6d-810b836c177a, serviceId=001, data=你好 服務(wù)端 ~~ 3)
ResponseMessageCodec.decode 被調(diào)用 {"data":"re:這里是服務(wù)端,已收到客戶端消息:1845339963","id":"f4576cbd-8ee5-438c-ae6d-810b836c177a"}
客戶端收到服務(wù)端回復(fù): ResponseData(id=f4576cbd-8ee5-438c-ae6d-810b836c177a, data=re:這里是服務(wù)端,已收到客戶端消息:1845339963)
service 得到服務(wù)端返回消息: re:這里是服務(wù)端,已收到客戶端消息:1845339963
-----------------------------3 結(jié)束
客戶端向服務(wù)端發(fā)送消息: 你好 服務(wù)端 ~~ 4
RequestMessageCodec.encode 被調(diào)用 RequestData(id=68e67b0f-0c35-4ead-915e-e1890a0c0b53, serviceId=001, data=你好 服務(wù)端 ~~ 4)
ResponseMessageCodec.decode 被調(diào)用 {"data":"re:這里是服務(wù)端,已收到客戶端消息:1845339964","id":"68e67b0f-0c35-4ead-915e-e1890a0c0b53"}
客戶端收到服務(wù)端回復(fù): ResponseData(id=68e67b0f-0c35-4ead-915e-e1890a0c0b53, data=re:這里是服務(wù)端,已收到客戶端消息:1845339964)
service 得到服務(wù)端返回消息: re:這里是服務(wù)端,已收到客戶端消息:1845339964
-----------------------------4 結(jié)束
服務(wù)端日志:
RequestMessageCodec.decode 被調(diào)用 {"data":"你好 服務(wù)端 ~~ 0","id":"f876eccf-a034-467a-8b5a-4c6dba80cee2","serviceId":"001"}
收到客戶端消息:RequestData(id=f876eccf-a034-467a-8b5a-4c6dba80cee2, serviceId=001, data=你好 服務(wù)端 ~~ 0)
ResponseMessageCodec.encode 被調(diào)用 ResponseData(id=f876eccf-a034-467a-8b5a-4c6dba80cee2, data=re:這里是服務(wù)端,已收到客戶端消息:1845339960)
----------響應(yīng)結(jié)束----------你好 服務(wù)端 ~~ 0
RequestMessageCodec.decode 被調(diào)用 {"data":"你好 服務(wù)端 ~~ 1","id":"bcceaa9b-09be-4dcc-9135-ac14caa365d1","serviceId":"001"}
收到客戶端消息:RequestData(id=bcceaa9b-09be-4dcc-9135-ac14caa365d1, serviceId=001, data=你好 服務(wù)端 ~~ 1)
ResponseMessageCodec.encode 被調(diào)用 ResponseData(id=bcceaa9b-09be-4dcc-9135-ac14caa365d1, data=re:這里是服務(wù)端,已收到客戶端消息:1845339961)
----------響應(yīng)結(jié)束----------你好 服務(wù)端 ~~ 1
RequestMessageCodec.decode 被調(diào)用 {"data":"你好 服務(wù)端 ~~ 2","id":"ab0181b1-b3fe-42b7-ae17-d2a533c56098","serviceId":"001"}
收到客戶端消息:RequestData(id=ab0181b1-b3fe-42b7-ae17-d2a533c56098, serviceId=001, data=你好 服務(wù)端 ~~ 2)
ResponseMessageCodec.encode 被調(diào)用 ResponseData(id=ab0181b1-b3fe-42b7-ae17-d2a533c56098, data=re:這里是服務(wù)端,已收到客戶端消息:1845339962)
----------響應(yīng)結(jié)束----------你好 服務(wù)端 ~~ 2
RequestMessageCodec.decode 被調(diào)用 {"data":"你好 服務(wù)端 ~~ 3","id":"6a4e6061-9ebe-4250-b939-2e5f314096fc","serviceId":"001"}
收到客戶端消息:RequestData(id=6a4e6061-9ebe-4250-b939-2e5f314096fc, serviceId=001, data=你好 服務(wù)端 ~~ 3)
ResponseMessageCodec.encode 被調(diào)用 ResponseData(id=6a4e6061-9ebe-4250-b939-2e5f314096fc, data=re:這里是服務(wù)端,已收到客戶端消息:1845339963)
----------響應(yīng)結(jié)束----------你好 服務(wù)端 ~~ 3
RequestMessageCodec.decode 被調(diào)用 {"data":"你好 服務(wù)端 ~~ 4","id":"69c726e6-a3f1-487a-8455-ada02b4e97ed","serviceId":"001"}
收到客戶端消息:RequestData(id=69c726e6-a3f1-487a-8455-ada02b4e97ed, serviceId=001, data=你好 服務(wù)端 ~~ 4)
ResponseMessageCodec.encode 被調(diào)用 ResponseData(id=69c726e6-a3f1-487a-8455-ada02b4e97ed, data=re:這里是服務(wù)端,已收到客戶端消息:1845339964)
----------響應(yīng)結(jié)束----------你好 服務(wù)端 ~~ 4
代碼地址
https://gitee.com/personal_practice/netty-demo
到此這篇關(guān)于使用Netty實(shí)現(xiàn)類似Dubbo的遠(yuǎn)程接口調(diào)用的實(shí)現(xiàn)方法的文章就介紹到這了,更多相關(guān)Netty實(shí)現(xiàn)類似Dubbo的遠(yuǎn)程調(diào)用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
RestTemplate在Spring或非Spring環(huán)境下使用精講
這篇文章主要為大家介紹了RestTemplate在Spring或非Spring環(huán)境下使用精講,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03
SpringSecurity 自定義表單登錄的實(shí)現(xiàn)
這篇文章主要介紹了SpringSecurity 自定義表單登錄的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
Java自學(xué)書籍推薦 程序員到架構(gòu)師必看的書
這篇文章主要為大家推薦了Java程序員到架構(gòu)師自學(xué)書籍,幫助大家不斷提高自己的專業(yè)水平,感興趣的小伙伴們可以參考一下2016-09-09
使用Java實(shí)現(xiàn)簡(jiǎn)單搭建內(nèi)網(wǎng)穿透
內(nèi)網(wǎng)穿透是一種網(wǎng)絡(luò)技術(shù),適用于需要遠(yuǎn)程訪問(wèn)本地部署服務(wù)的場(chǎng)景,本文主要為大家介紹了如何使用Java實(shí)現(xiàn)簡(jiǎn)單搭建內(nèi)網(wǎng)穿透,感興趣的可以了解下2024-02-02

