徒手撸一个简单的RPC框架(2)——项目改造

徒手撸一个简单的RPC框架(2)——项目改造

在上一篇的徒手撸一个简单的RPC框架中再最后的服务器和客户端连接的时候只是简单的写了Socket连接,觉得有些不妥。正好最近学习了Netty,在平时工作中没机会运用,于是自己就给自己出需求将之前的项目改造一下。

Netty是什么?

在学习Netty之前呢我们首先得了解IO和NIO

IO模型

IO编程模型简单来说就是上一篇我写的服务端与客户端的连接,客户端与服务端建立连接通信后,必须等待服务端返回信息才能进行下一步的动作,这期间线程一直是等待状态。IO模型在客户端较少的情况下是没问题的,但是一旦有大量客户端与服务端进行连接,那么就会出问题。我们简单的分析一下原因。

  1. 首先我之前写的代码其实是有问题的,为什么呢?因为每次连接通信一次我就将其连接关闭了。Socket连接时TCP,双方每次建立连接时都会耗费时间和资源,不能每通信一次就关闭连接。就好比你和别人打电话你说一句对方说一句,然后挂电话,然后再打过去。肯定是不能这么做的
  2. 如果是想要保持通信,那么程序中就得将其监听的代码放入while循环中用专门的线程来维护。但是线程是操作系统中非常宝贵的资源,每个操作系统能创建的线程也是有限的。
  3. CPU频繁的在线程之间切换是非常损耗性能的。
  4. 我们可以看到之前编写的代码中客户端与服务端交流的媒介是字节流,效率不高。

IO模型有这么多的问题,于是在JDK1.4中提出了NIO的概念,就是为了解决以上的问题

NIO模型

我们一一对应上面的问题来看NIO用什么技术来解决的

第一个问题

第一个问题是代码的问题,我们就不讨论了

第二个问题

线程有限的问题:NIO中提出了Selector概念,IO中是每个连接都会由一个线程阻塞来维护,NIO中用Selector来管理这些连接,如果有消息的传入或传出,那么就建立相应的线程了处理。这样服务器只需要阻塞一个Selector线程,就可以管理多个连接了。

具体的Selector文章可以看我之前的NIO中选择器Selector,里面有介绍详细的Selector用法。

这里举个例子应该就明白的,好比你去钓鱼,IO就是一人一个鱼竿,等着鱼上来,中间哪也不能去,而NIO就是一个人能守着好几个鱼竿。

这就是NIO模型解决操作系统中线程有限的问题。

第三个问题

CPU在线程之间频繁切换,由于NIO中只管理了一个Selector线程,那么这个问题也就相应的解决了

第四个问题

NIO中提出了ChannelBuffer的概念,就好比在向往的生活第一季中摘玉米中,是用竹筐一次一次背快呢?还是接一辆车子来回运送快?当然是车子来回运送快了,而这里的Buffer就好比车子。具体的ChannelBuffer的解释可以看我之前的文章Java中IO和NIOJAVA中NIO再深入

Netty

那么为什么就和Netty扯上关系了呢?其实我觉得NIO之于Netty的关系就好比Servlet之于Tomcat的关系,Netty只是对于NIO进行了进一步的封装,让使用者更加简便的编程。

改造

这次改造分为服务端和客户端的改造

服务端

接下来我们就利用Netty将我们的服务器端与客户端连接通信部分进行改造一下,首先我们先加上对于Netty的依赖

compile 'io.netty:netty-all:4.1.6.Final'

然后编写服务端的代码,服务端的代码非常简单

ServerBootstrap serverBootstrap = new ServerBootstrap();
NioEventLoopGroup boos = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
serverBootstrap
        .group(boos, worker)
        .channel(NioServerSocketChannel.class)
        .childHandler(new ChannelInitializer<NioSocketChannel>() {
            protected void initChannel(NioSocketChannel ch) {
                ch.pipeline().addLast(new StringDecoder());
                ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
                    @Override
                    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
                        //获得实现类处理过后的返回值
                        String invokeMethodMes = CommonDeal.getInvokeMethodMes(msg);
                        ByteBuf encoded = ctx.alloc().buffer(4 * invokeMethodMes.length());
                        encoded.writeBytes(invokeMethodMes.getBytes());
                        ctx.writeAndFlush(encoded);
                    }
                });
            }
        }).bind(20006);

这和我们平常写的Socket连接有些区别,可以看到我们建了两个NioEventLoopGroup一个boss一个worker,为什么会有两个呢?

image

从这个图里面我们可以看到,boss是专门用来对外连接的,worker则是像NIO中Selector用来处理各种读写的请求。

客户端

其实难点就是在客户端,因为Netty是异步事件驱动的框架,什么是异步呢?

image

客户端与服务端的任何I/O操作都将立即返回,等待服务端处理完成以后会调用指定的回调函数进行处理。在这个过程中客户端一直没有阻塞。所以我们在客户端与服务端请求处理时,如果获得异步处理的结果呢?Netty提供有一种获取异步回调结果的,但是那是添加监听器。而我们的RPC调用在最后返回结果的时候必须得阻塞等待结果的返回,所以我们需要自己写一个简单的获取异步回调结果的程序。想法如下。

  1. 想要获得服务端返回的消息时,阻塞等待。
  2. Netty客户端读取到客户端消息时,唤醒等待的线程

那么我们就围绕这两步来进行编码。

客户端想要获取服务端消息时如何等待呢?这里我们就可以用wait()

public Response getMessage(){
    synchronized (object){

        while (!success){
            try {
                object.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        return response;
    }
}

那么读到消息以后如何唤醒呢?

public void setMessage(Response response){
    synchronized (object){
        this.response = response;
        this.success = true;
        object.notify();
    }
}

这样就解决了我们上面提出的两个问题了。接下来编写客户端的代码

 private final Map<Long,MessageFuture> futureMap = new ConcurrentHashMap<>();
    private CountDownLatch countDownLatch = new CountDownLatch(1);

    public void connect(String requestJson,Long threadId){
        Bootstrap bootstrap = new Bootstrap();
        NioEventLoopGroup group = new NioEventLoopGroup();
        bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<Channel>() {
            @Override
            protected void initChannel(Channel ch) {
                ch.pipeline().
                        addLast(new StringDecoder()).
                        addLast(new SimpleChannelInboundHandler<String>() {
                            @Override
                            protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
                                Gson gson = new Gson();
                                Response response = gson.fromJson(msg, Response.class);
                                MessageFuture messageFuture = futureMap.get(threadId);
                                messageFuture.setMessage(response);
                            }

                            @Override
                            public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                futureMap.put(threadId,new MessageFuture());
                                countDownLatch.countDown();
                                ByteBuf encoded = ctx.alloc().buffer(4 * requestJson.length());
                                encoded.writeBytes(requestJson.getBytes());
                                ctx.writeAndFlush(encoded);
                            }
                        });
            }
        }).connect("127.0.0.1", 20006);
    }

    public Response getResponse(Long threadId){
        MessageFuture messageFuture = null;
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        messageFuture = futureMap.get(threadId);
        return messageFuture.getMessage();
    }

这里面我们用到了CountDownLatch类,即等待发送完消息以后通知我能获取数据了。这里面的代码和服务端的差不多,其中有区别的地方就是在发送数据的时候将线程ID和MessageFuture放入Map中,在得到服务端发送的数据时取出并放入得到的Response。

总结

到目前为止我们就完成了我们的项目改造,只是简单的应用了一下Netty的客户端和服务端的通信,因为在学习的过程中如果没有运用的话,那么感觉记忆没有那么牢靠,所以就有了此次的项目改造的计划。虽然完成了简单的通信,但是我知道还有些地方需要优化,例如用synchronized在以后学习了AQS以后希望也能够学以致用将这里给改一下。

完整的项目地址

徒手撸一个简单的RPC框架

徒手撸一个简单的IOC

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • 作者:李林锋 原文:http://www.infoq.com/cn/articles/netty-high-per...
    杨鑫科阅读 3,928评论 0 64
  • 1、Netty基础入门 Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应...
    我是嘻哈大哥阅读 4,666评论 0 31
  • 最近,又把《欢乐颂》重温了一遍,五个女孩的职场与情感故事,给我留下了深刻的印象。同样身在大城市奋斗的我,和小蚯蚓一...
    南山阿姨阅读 181评论 3 1
  • 一、很久不化妆了 素颜霜 对,我要讲的就是素颜霜,真是个神器。一抹直接就遮瑕了,白的还特自然。没有选大牌的素颜霜,...
    三盏花阅读 261评论 2 1
  • 每天都有很多稀奇古怪的想法,曾经感慨三毛的凄惨,陆小曼的才情,席慕容的缠绵,张爱玲的悲惨,慢慢的发现越是有才华的女...
    景泰蓝_烧蓝阅读 185评论 0 2