我的编程空间,编程开发者的网络收藏夹
学习永远不晚

使用Netty实现类似Dubbo的远程接口调用的实现方法

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

使用Netty实现类似Dubbo的远程接口调用的实现方法

一、Netty简介

Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。

本文通过完整示例代码,详细介绍netty实现类似dubbo的远程网络通讯,如有错误欢迎指正!

实现步骤:

  • 创建接口和实现类
  • 创建客户端代码
  • 通过动态代理模式,封装netty远程接口调用
  • 通过异步线程等待/通知,实现异步转同步
  • 创建服务端代码
  • 自定义编码解码器
  • 编写测试客户端发送请求代码

二、完整代码实现

工程依赖引入

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.75.Final</version>
</dependency>

1、创建接口和实现类

定义简单接口和实现类。通过注解定义接口和服务实现,在后续代码中解析注解。

@ServiceEntry定义接口serviceId

@MyService定义服务实现类

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:这里是服务端,已收到客户端消息:" + msg.hashCode();
    }
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ServiceEntry {
    
    String serviceId();

    
    String name() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyService {
    String value() default "";
}

2、客户端代码实现及动态代理和异步转同步

1)创建客户端Handler,MyClientHandler,继承ChannelInboundHandlerAdapter,并实现Callable接口

  • 客户端发送请求时,会调用call方法,在这里将异步转同步
  • 将请求context放入map,并等待线程,在收到服务端返回时,异步通知线程执行,返回结果数据
  • 收到服务端返回时,设置返回结果数据,并通知线程执行
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("客户端和服务端链接成功");
        this.ctx = ctx;
    }
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("客户端收到服务端回复: " + msg);
        ResponseData data = (ResponseData) msg;
        String id = data.getId();
        // 收到服务端返回时,设置返回结果数据,并通知线程执行
        SyncSendContext context = syncSendContextMap.get(id);
        context.setResp(data);
        synchronized (context) {
            context.notify();
        }
    }
    @Override
    public String call() throws Exception {
        System.out.println("客户端向服务端发送消息: " + param[0].toString());
        String id = UUID.randomUUID().toString();
        RequestData data = new RequestData();
        data.setId(id);
        //强制设置参数1
        data.setData(param[0].toString());
        data.setServiceId(serviceId);
        SyncSendContext context = new SyncSendContext();
        context.setRequest(data);
        // 将请求context放入map,并等待线程,在收到服务端返回时,异步通知线程执行,返回结果数据
        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)创建客户端代码,MyClient

  • 通过动态代理,包装远程服务请求
  • 初始化服务端链接,通过双检锁确保clientHandler是单例实现
  • 发送请求时,通过线程池异步发送clientHandler
public class MyClient {
    private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    private MyClientHandler clientHandler;
    // 通过动态代理,包装远程服务请求
    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();
                });
    }
    // 初始化服务端链接,通过双检锁确保clientHandler是单例实现
    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、服务端代码实现

1)创建服务工程类ServiceFacatory,解析注解保存服务接口和实现类,调用的时候从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 = "**
    public static Set<MethodInfo> findClassAnnotationMethods(String scanPackages, Class<? extends Annotation> annotation) {
        //获取所有的类
        Set<String> clazzSet = findPackageClass(scanPackages);
        Set<MethodInfo> methods = new HashSet<>();
        //遍历类,查询相应的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);
        // 存储接口中定义的方法
        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 {
                // 从接口中读取对应的方法
                String key = getMethodKey(method);
                Method methodInf = mapMethodInf.get(key);
                annotation = methodInf.getAnnotation(anno);
                if (annotation != null) {
                    methodSet.add(new MethodInfo(annotation,method));
                }
            }
        }
        return methodSet;
    }
    
    private static Set<String> findPackageClass(String scanPackages) {
        if (StringUtils.isBlank(scanPackages)) {
            return Collections.EMPTY_SET;
        }
        //验证及排重包路径,避免父子路径多次扫描
        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;
    }
    
    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 + ".")) {
                    //如果待加入的路径是已经加入的pack的子集,不加入
                    needAdd = false;
                } else if (pack.startsWith(pInArr + ".")) {
                    //如果待加入的路径是已经加入的pack的父集,删除已加入的pack
                    packageIte.remove();
                }
            }
            if (needAdd) {
                packages.add(pInArr);
            }
        }
        return packages;
    }
    
    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("根据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)创建服务端Handler类,接收客户端请求,并调用服务实现类执行接口

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();
            // 调用服务实现类
            Object res = ServiceFacatory.invoke(serviceId, args);
            ResponseData resp = new ResponseData();
            resp.setData(res);
            resp.setId(req.getId());
            ctx.writeAndFlush(resp);
        }
        System.out.println("----------响应结束----------" + req.getData());
    }
}

4)创建服务端启动类MyServer、ServerApp,启动端口监听;加入编解码器和服务端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)创建请求数据编解码器RequestMessageCodec,实现String和请求参数对象RequestData之间互相转换

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 被调用 " + 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 被调用 " + msg);
        RequestData po = JSONObject.parseObject(msg, RequestData.class);
        out.add(po);
    }
}

2)创建服务响应数据编解码器ResponseMessageCodec,实现String和响应数据对象ResponseData之间互相转换

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 被调用 " + 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 被调用 " + msg);
        ResponseData po = JSONObject.parseObject(msg, ResponseData.class);
        out.add(po);
    }
}

3)创建请求、响应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、编写测试客户端发送请求代码

1)创建客户端请求类ClientTest,模拟发送数据

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("你好 服务端 ~~ " + i);
            System.out.println("service 得到服务端返回消息: " + res);
            System.out.println("-----------------------------" + i +" 结束");
        }
    }
}

2)运行ServerApp,启动服务端,运行ClientTest模拟客户端发送数据

客户端日志:

客户端和服务端链接成功
客户端向服务端发送消息: 你好 服务端 ~~ 0
RequestMessageCodec.encode 被调用 RequestData(id=d081f840-4367-42d3-909c-32f6a1654c60, serviceId=001, data=你好 服务端 ~~ 0)
ResponseMessageCodec.decode 被调用 {"data":"re:这里是服务端,已收到客户端消息:1845339960","id":"d081f840-4367-42d3-909c-32f6a1654c60"}
客户端收到服务端回复: ResponseData(id=d081f840-4367-42d3-909c-32f6a1654c60, data=re:这里是服务端,已收到客户端消息:1845339960)
service 得到服务端返回消息: re:这里是服务端,已收到客户端消息:1845339960
-----------------------------0 结束
客户端向服务端发送消息: 你好 服务端 ~~ 1
RequestMessageCodec.encode 被调用 RequestData(id=d49105b0-2624-43c2-bb19-c826987133f1, serviceId=001, data=你好 服务端 ~~ 1)
ResponseMessageCodec.decode 被调用 {"data":"re:这里是服务端,已收到客户端消息:1845339961","id":"d49105b0-2624-43c2-bb19-c826987133f1"}
客户端收到服务端回复: ResponseData(id=d49105b0-2624-43c2-bb19-c826987133f1, data=re:这里是服务端,已收到客户端消息:1845339961)
service 得到服务端返回消息: re:这里是服务端,已收到客户端消息:1845339961
-----------------------------1 结束
客户端向服务端发送消息: 你好 服务端 ~~ 2
RequestMessageCodec.encode 被调用 RequestData(id=13f82f4a-0a2f-41cc-8420-38ab20fab2d2, serviceId=001, data=你好 服务端 ~~ 2)
ResponseMessageCodec.decode 被调用 {"data":"re:这里是服务端,已收到客户端消息:1845339962","id":"13f82f4a-0a2f-41cc-8420-38ab20fab2d2"}
客户端收到服务端回复: ResponseData(id=13f82f4a-0a2f-41cc-8420-38ab20fab2d2, data=re:这里是服务端,已收到客户端消息:1845339962)
service 得到服务端返回消息: re:这里是服务端,已收到客户端消息:1845339962
-----------------------------2 结束
客户端向服务端发送消息: 你好 服务端 ~~ 3
RequestMessageCodec.encode 被调用 RequestData(id=f4576cbd-8ee5-438c-ae6d-810b836c177a, serviceId=001, data=你好 服务端 ~~ 3)
ResponseMessageCodec.decode 被调用 {"data":"re:这里是服务端,已收到客户端消息:1845339963","id":"f4576cbd-8ee5-438c-ae6d-810b836c177a"}
客户端收到服务端回复: ResponseData(id=f4576cbd-8ee5-438c-ae6d-810b836c177a, data=re:这里是服务端,已收到客户端消息:1845339963)
service 得到服务端返回消息: re:这里是服务端,已收到客户端消息:1845339963
-----------------------------3 结束
客户端向服务端发送消息: 你好 服务端 ~~ 4
RequestMessageCodec.encode 被调用 RequestData(id=68e67b0f-0c35-4ead-915e-e1890a0c0b53, serviceId=001, data=你好 服务端 ~~ 4)
ResponseMessageCodec.decode 被调用 {"data":"re:这里是服务端,已收到客户端消息:1845339964","id":"68e67b0f-0c35-4ead-915e-e1890a0c0b53"}
客户端收到服务端回复: ResponseData(id=68e67b0f-0c35-4ead-915e-e1890a0c0b53, data=re:这里是服务端,已收到客户端消息:1845339964)
service 得到服务端返回消息: re:这里是服务端,已收到客户端消息:1845339964
-----------------------------4 结束

服务端日志:

RequestMessageCodec.decode 被调用 {"data":"你好 服务端 ~~ 0","id":"f876eccf-a034-467a-8b5a-4c6dba80cee2","serviceId":"001"}
收到客户端消息:RequestData(id=f876eccf-a034-467a-8b5a-4c6dba80cee2, serviceId=001, data=你好 服务端 ~~ 0)
ResponseMessageCodec.encode 被调用 ResponseData(id=f876eccf-a034-467a-8b5a-4c6dba80cee2, data=re:这里是服务端,已收到客户端消息:1845339960)
----------响应结束----------你好 服务端 ~~ 0
RequestMessageCodec.decode 被调用 {"data":"你好 服务端 ~~ 1","id":"bcceaa9b-09be-4dcc-9135-ac14caa365d1","serviceId":"001"}
收到客户端消息:RequestData(id=bcceaa9b-09be-4dcc-9135-ac14caa365d1, serviceId=001, data=你好 服务端 ~~ 1)
ResponseMessageCodec.encode 被调用 ResponseData(id=bcceaa9b-09be-4dcc-9135-ac14caa365d1, data=re:这里是服务端,已收到客户端消息:1845339961)
----------响应结束----------你好 服务端 ~~ 1
RequestMessageCodec.decode 被调用 {"data":"你好 服务端 ~~ 2","id":"ab0181b1-b3fe-42b7-ae17-d2a533c56098","serviceId":"001"}
收到客户端消息:RequestData(id=ab0181b1-b3fe-42b7-ae17-d2a533c56098, serviceId=001, data=你好 服务端 ~~ 2)
ResponseMessageCodec.encode 被调用 ResponseData(id=ab0181b1-b3fe-42b7-ae17-d2a533c56098, data=re:这里是服务端,已收到客户端消息:1845339962)
----------响应结束----------你好 服务端 ~~ 2
RequestMessageCodec.decode 被调用 {"data":"你好 服务端 ~~ 3","id":"6a4e6061-9ebe-4250-b939-2e5f314096fc","serviceId":"001"}
收到客户端消息:RequestData(id=6a4e6061-9ebe-4250-b939-2e5f314096fc, serviceId=001, data=你好 服务端 ~~ 3)
ResponseMessageCodec.encode 被调用 ResponseData(id=6a4e6061-9ebe-4250-b939-2e5f314096fc, data=re:这里是服务端,已收到客户端消息:1845339963)
----------响应结束----------你好 服务端 ~~ 3
RequestMessageCodec.decode 被调用 {"data":"你好 服务端 ~~ 4","id":"69c726e6-a3f1-487a-8455-ada02b4e97ed","serviceId":"001"}
收到客户端消息:RequestData(id=69c726e6-a3f1-487a-8455-ada02b4e97ed, serviceId=001, data=你好 服务端 ~~ 4)
ResponseMessageCodec.encode 被调用 ResponseData(id=69c726e6-a3f1-487a-8455-ada02b4e97ed, data=re:这里是服务端,已收到客户端消息:1845339964)
----------响应结束----------你好 服务端 ~~ 4

代码地址

https://gitee.com/personal_practice/netty-demo

到此这篇关于使用Netty实现类似Dubbo的远程接口调用的实现方法的文章就介绍到这了,更多相关Netty实现类似Dubbo的远程调用内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

使用Netty实现类似Dubbo的远程接口调用的实现方法

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

使用Netty实现类似Dubbo的远程接口调用的实现方法

本文介绍了如何使用Netty框架实现类似Dubbo的远程接口调用,通过自定义编解码器、通信协议和服务注册中心等实现远程通信和服务治理。文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
2023-05-17

Python实现类似jQuery使用中的链式调用的示例

关于jQuery的链式调用 真正有意义的链式调用也就是方法链(method chaining)。方法链这个词是有的,而且使用的很广泛。其实很多人口中的“链式调用”实际上就是指方法链。但是“链式调用”这个词语还可以描述函数调用链,所以让它自身
2022-06-04

前端vue3使用axios调用后端接口的实现方法

vue本身不支持ajax接口的请求,所以在vue中经常使用axios这个接口请求工具,下面这篇文章主要给大家介绍了关于前端vue3使用axios调用后端接口的实现方法,需要的朋友可以参考下
2022-12-08

怎么使用Spring特性实现接口多类的动态调用

今天小编给大家分享一下怎么使用Spring特性实现接口多类的动态调用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。正好用到。
2023-06-29

如何使用纯CSS实现类似悬浮窗口的效果

悬浮窗口是在网页设计中经常使用的一种效果,它可以提供快速访问功能或者展示重要的信息。本文将介绍如何使用纯CSS来实现类似悬浮窗口的效果,包括具体的代码示例。首先,我们需要在HTML中创建一个容器元素,用于承载悬浮窗口的内容。可以是一个div
2023-10-21

如何使用Golang Facade实现简洁的接口调用

使用Golang的Facade模式可以实现简洁的接口调用。Facade模式是一种结构设计模式,它提供了一种简化接口的方式,使得客户端可以更方便地使用系统的功能。下面是一个使用Golang Facade实现简洁的接口调用的示例代码:首先,我们
2023-10-10

vue3+vite+axios 配置连接后端调用接口的实现方法

这篇文章主要介绍了vue3+vite+axios 配置连接后端调用接口的实现方法,在vite.config.ts文件中添加配置,本文通过实例代码给大家介绍的非常详细,需要的朋友可以参考下
2022-12-08

Springboot使用redis实现接口Api限流的方法

这篇文章主要介绍“Springboot使用redis实现接口Api限流的方法”,在日常操作中,相信很多人在Springboot使用redis实现接口Api限流的方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答
2023-06-20

使用PHP的API接口:在移动应用中实现API接口的方法

随着移动应用市场的火爆,越来越多的企业开始重视移动应用程序的开发和推广。而对于开发人员来说,API接口就是一个必不可少的技术,可以使移动应用与服务器之间进行数据交互,并在应用程序中进行数据处理、存储和显示操作。本文将介绍PHP API接口的
使用PHP的API接口:在移动应用中实现API接口的方法
2024-01-22

在 Go 中使用泛型类型结构实现接口方法

问题内容我想要一个接口,它可以将任何类型的参数传递给它的单个方法,并且我想要一个带有泛型的单个实现结构。这是我的问题的超级简化版本:package maintype MyInterface interface {Set(val any
在 Go 中使用泛型类型结构实现接口方法
2024-02-06

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录