【Netty】——“请求-响应“同步通信机制

项目需求使用Netty搭建Tcp服务器,实现与Http Web服务器一样的同步“请求-响应”的单工同步通信方式。虽然Netty提供了异步IO和同步IO的统一实现,但是我们的需求关键并不是IO的同步异步关系,而是实现请求-响应这种典型的一问一答交互方式。要实现这个需求,需要解决两个问题:

1.请求和响应的正确匹配

客户端发送消息后,服务端返回响应结果,那么此结果怎么和客户端的请求正确匹配呢,即一个消息请求如何对应一个消息响应呢?
解决思路:客户端程序中为每一条请求消息设置一个全局唯一id,服务端返回的响应结果中包含该id,这样客户端就可以通过id来正确匹配请求响应了。

2.请求线程和响应线程的通信。

Netty异步IO通信模型,请求消息发送线程和channel中channelRead()(接收响应)方法接收线程并不是同一线程,要实现请求线程在发出消息后,同步等待服务端的响应,就需要解决,Netty客户端在接受到响应之后,怎么通知请求线程结果。
解决思路:多线程Guarded Suspension设计模式(当现在并不适合马上执行某个操作时,就要求想要执行该操作的线程等待),实现等待—通知机制,即客户端线程在发送请求后,进入等待,服务器返回响应后,根据消息的唯一id来唤醒客户端的请求线程,并把结果返回给请求线程。

代码示例

项目中使用Netty+Protobuf进行Tcp通信。大体思路:客户端发送请求后将<请求ID,Future>的键值对保存到一个缓存中,这时候用Future等待结果,挂住请求线程;当Netty客户端收到服务端的响应后,响应线程根据请求ID从缓存中取出Future,然后设置响应结果到Future中。这个时候利用CountDownLatch的通知机制,通知请求线程。请求线程从Future中拿到响应结果,然后做业务处理。

HprotoFuture

异步转同步的核心,即发送请求线程与响应接收线程的同步协调者

public class HprotoFuture<T extends Message> {

    /**
     * message  id
     */
    private long uuid;
    /**
     * Response 协议
     */
    private Response response;
    /**
     * 响应体实例
     */
    private T entity;
    /**
     * 响应体类型
     */
    private Class<T> responseType;
    /**
     * 线程同步,
     */
    private CountDownLatch latch = new CountDownLatch(1);

    /**
     * 异常监听,处理服务端响应的异常信息
     */
    private ExceptionHandler exceptionHandler;

    public HprotoFuture(long uuid,Class<T> responseType, ExceptionHandler handler) {
        this.uuid = uuid;
        this.exceptionHandler = handler;
        this.responseType = responseType;
    }

    /**
     * 注入Response
     * 如果服务端返回2000  Ok,解码响应实体Entity,唤醒get线程
     * 否则处理异常,唤醒get线程,返回entity=null
     * 业务响应异常交由此future处理,channel handler只处理底层协议相关逻辑
     *
     * @param response
     */
    public void onResponse(Response response) {
        this.response = response;
        if (response != null) {
            try {
                Any any = response.getData();
                if (response.getStatus() != StatusCode.OK.code()) {
                    //异常响应
                    exceptionHandler.caught(new ServerResponseException(response.getMessage()));
                } else {
                    //正常响应
                    entity = any.unpack(responseType);
                }
            } catch (InvalidProtocolBufferException e) {
                exceptionHandler.caught(e);
                log.error("Type of the Any reflect exception");
            } finally {
                //唤醒get()线程,释放FutureContext资源
                latch.countDown();
                FutureContext.release(uuid);
            }
        }
    }

    /**
     * 阻塞获取response
     *
     * @return
     */
    public T get() {
        if (entity == null) {
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return entity;
    }

    /**
     * 阻塞获取response
     * 超时等待,避免永久休眠
     * @param timeout
     * @param unit
     * @return
     * @throws TimeoutException
     */
    public T get(long timeout, TimeUnit unit) throws TimeoutException {
        if (entity == null) {
            try {
                if (!latch.await(timeout, unit)) {                  
                    throw new TimeoutException("get time out");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally{
                //释放FutureContext资源
                FutureContext.release(uuid);
            }
        }
        return entity;
    }

    public long getUuid() {
        return uuid;
    }
}

FutureContext

缓存与管理Future与Message的关系

public class FutureContext {

    /**
     * 一个请求对应一个HprotoFuture
     */
    private static final ConcurrentHashMap<Long, HprotoFuture<?>> CONTAINER = new ConcurrentHashMap<>();

    /**
     * 记录Future
     * @param future
     */
    public static void apply(HprotoFuture<?> future) {
        if (future == null) {
            return;
        }
        CONTAINER.put(future.getUuid(), future);
    }

    /**
     * 获取future
     * @param uuid
     * @return
     */
    public static HprotoFuture get(Long uuid) {
        if (uuid == null) {
            return null;
        }
        return CONTAINER.get(uuid);
    }

    /**
     * 释放future
     * @param uuid
     */
    public static void release(Long uuid) {
        if (uuid == null) {
            return;
        }
        CONTAINER.remove(uuid);
    }

}

Client客户端

省略部分代码

public class HprotoClient {

    /**
     * Netty客户端属性
     */
   .....       

    /**
     * 成员方法
     * 
     */
    ......

    /**
     * 同步发送
     * 每一条 request message,生成一个唯一HprotoFuture管理其response
     * HprotoFuture 阻塞同步返回response
     *
     * @param message 信息 Google ProtoBuf  Message子类
     * @param responseType 响应体类型
     * @param <T>
     * @return 响应超时返回null
     */
    public <T extends Message> T send(ProtocolPattern message, Class<T> responseType) {
        HprotoFuture<T> future = new HprotoFuture<>(message.getUuid(),responseType,exceptionHandler);
        FutureContext.apply(future);
        try {
            netClient.send(message);
            return future.get(readTimeout, readUnit);
        } catch (TimeoutException e) {
            exceptionHandler.caught(e);
        } catch (IOException e2) {
            exceptionHandler.caught(e2);
        }
        return null;
    }

}

响应Handler

public class ClientResponseHandler extends ChannelInboundHandlerAdapter {

    /**
     * 异常监听类,处理客服端异常
     */
    private ExceptionHandler exceptionHandler;

    public ClientResponseHandler(ExceptionHandler exceptionHandler) {
        this.exceptionHandler = exceptionHandler;
    }


    /**
     * 读取响应
     * 将Response放入对应Future,FutureContext上下文释放Future
     * 保障一条message 消费一个response
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)  {
        try {
            ProtocolPattern protocol = (ProtocolPattern) msg;
            Response response = (Response) protocol.getBody();
            Long uuid=protocol.getUuid();
            HprotoFuture future=FutureContext.get(uuid);
            if (future != null) {
                future.onResponse(response);
            }
        }finally {
            ReferenceCountUtil.release(msg);
        }
    }

    /**
     * 异常监听处理异常
     * 关闭通道
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        if (exceptionHandler != null) {
            exceptionHandler.caught(cause);
        }
        ctx.close();
    }

 

 

 

  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论
### 回答1: Netty-WebSocket-Spring-Boot-Starter是一个用于将Websocket集成到Spring Boot应用程序中的库。它使用Netty作为底层框架,提供了一种快速和可靠的方式来处理异步通信。 这个库提供了一种简单的方法来创建Websocket端点,只需要使用注释和POJO类即可。在这些端点上可以添加动态的事件处理程序,以处理连接、断开连接和消息事件等。 此外,Netty-WebSocket-Spring-Boot-Starter还包括了一些安全性的特性,如基于令牌的授权和XSS保护,可以帮助您保持您的Websocket应用程序安全。 总的来说,Netty-WebSocket-Spring-Boot-Starter提供了一种快速和易于使用的方式来构建Websocket应用程序,使得它成为应用程序开发人员的有用工具。 ### 回答2: netty-websocket-spring-boot-starter 是一个开源的 Java Web 开发工具包,主要基于 Netty 框架实现了 WebSocket 协议的支持,同时集成了 Spring Boot 框架,使得开发者可以更加方便地搭建 WebSocket 服务器。 该工具包提供了 WebSocketServer 配置类,通过在 Spring Boot 的启动配置类中调用 WebSocketServer 配置类,即可启动 WebSocket 服务器。同时,该工具包还提供了多种配置参数,如端口号、URI 路径、SSL 配置、认证配置等等,可以根据业务需求进行自定义配置。 此外,该工具包还提供了一些可扩展的接口和抽象类,如 WebSocketHandler、ChannelHandlerAdapter 等,可以通过继承和实现这些接口和抽象类来实现业务逻辑的处理和拓展。 总的来说,netty-websocket-spring-boot-starter 提供了一个高效、简单、易用的 WebSocket 服务器开发框架,可以减少开发者的开发成本和工作量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wonder ZH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值