3. 手写RPC

1 RPC功能简介

  1. 远程过程调用,可以让程序员像调用本地方法一样调用远程方法
  2. 本地只有接口Car、Fly,服务端有其实现类MyCar和MyFly,客户端通过动态代理生成该接口的代理类,这个代理类底层需要将相关信息通过IO传递到远程服务端,最终服务端调用MyCar和MyFly的方法,并将结果通过IO返回给客户端

2 协议作用

  1. 所谓使用哪种协议,就是指将数据以该协议要求的格式,发送出去,将要发送的内容包装成指定协议要求的格式,就叫网络封包
  2. 使用RPC协议传递数据,就好像小火车(协议Myheader)拉货(数据MyContent)
  3. 我们既可以用自定义的RPC协议,也可以用Http协议作为载体传输数据
  4. Dubbo中的传输协议,可以是自定义RPC协议,也可以是Http协议
  5. Provider可能是通过Tomcat对外暴露服务,也可能是通过自定义的RPC框架对外暴露服务
  6. 当前主机能支持的并发量,主要取决于IO上的损耗,尤其在读取时间,所以尽量使用更小的数据包,而小的数据包取决于协议上的减轻以及更好的压缩算法,压缩算法消耗cpu,但cpu处理速度比IO快,通常不会达到瓶颈

3 有状态协议和无状态协议

  1. 自定义有状态协议:在协议头中添加requestID属性表示状态,因为协议有状态,所以客户端能分辨出返回的response是哪次request产生的
    1. 因此可以多个客户端复用一个链接(Socket)
    2. 客户端向服务器发送请求后,其他客户端就可以使用该链接继续向服务器发送请求,而不必等服务端返回数据
  2. 无状态协议:Http协议无状态,所以客户端无法分辨出返回的response是哪次request产生的
    1. 因此我们通常浏览器发送请求后,都会阻塞一直等到Tomcat服务器返回,这个阻塞是浏览器实现的,当浏览器开启多个窗口访问一个服务器时,实际上需要建立多个链接,否则新的请求会被老的请求阻塞
    2. 虽然我们使用Netty编写客户端时,可以编写为不阻塞到服务端返回,而是发送完毕后将该链接给其他客户端使用,但这样做,会导致我们无法将response和对应的request结果关联起来,即客户端A调用远程方法可能会得到客户端B调用远程方法的结果
    3. 虽然Http协议本身无状态,但可以同时对客户端和服务端进行特殊处理,例如在其内加上requestID,这样多个客户端就可以复用一个链接了
    4. 但实际上,通常服务端已经提供好了服务,我们没法去修改,比如服务端是一个Tomcat,且回复的内容中,并没有requestID,我们又没法修改服务端代码,所以对于微服务这种,我们向服务器发送请求后,如果服务器没有及时响应,客户端应该一直等待,直至服务器响应或者超时

4 粘包和拆包

  1. 客户端写入和服务端读取,是两台机器发生的操作,我们并不能控制客户端每发送一次数据包,服务端就接收一次数据包,因此服务端读取到的数据包,很可能是客户端半次发送的或多次发送的
  2. 粘包:服务端一次读取到了客户端发送的多个数据包
  3. 拆包:服务端一次只读取到了客户端发送的一个数据包的一部分

5 IO Threads

  1. 客户端java程序写入数据时,其实是先将数据从用户空间复制到内核空间的Send-Q,之后内核将这部分数据通过网卡传输到服务端的网卡中,随后服务端网卡产生中断,将网卡中数据复制到内核空间的Recv-Q,如果数据到达服务端网卡速度过快,操作系统会关闭中断,直接干预数据DMA拷贝,即轮循将网卡中数据放入内核,服务端java程序读取数据程序时,其实是将Recv-Q中数据拷贝到用户空间
  2. 如果服务端读取数据速度较慢,导致服务端的Recv-Q被填满,那么发送方会阻塞,一直等到接收方接收完毕,这一般被称为网卡被打满了
  3. 因此对于程序员设计的程序,应该使用IO Threads,即启动多个线程处理IO,并将IO线程和业务逻辑线程分开,防止业务逻辑较慢导致IO线程被阻塞,从而保证更快地搬运数据到用户空间

6 手写RPC代码

6.1 测试类
  1. MyRPCTest

    package com.bjmashibing.system.rpcdemo;
    
    import com.bjmashibing.system.rpcdemo.proxy.MyProxy;
    import com.bjmashibing.system.rpcdemo.rpc.Dispatcher;
    import com.bjmashibing.system.rpcdemo.rpc.protocol.MyContent;
    import com.bjmashibing.system.rpcdemo.rpc.transport.MyHttpRpcHandler;
    import com.bjmashibing.system.rpcdemo.rpc.transport.ServerDecode;
    import com.bjmashibing.system.rpcdemo.rpc.transport.ServerRequestHandler;
    import com.bjmashibing.system.rpcdemo.service.*;
    import com.bjmashibing.system.rpcdemo.util.SerDerUtil;
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.*;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import io.netty.handler.codec.http.*;
    import org.eclipse.jetty.server.Server;
    import org.eclipse.jetty.servlet.ServletContextHandler;
    import org.junit.Test;
    
    import java.io.ByteArrayInputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.net.InetSocketAddress;
    import java.net.URLConnection;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class MyRPCTest {
    
    
        //服务端:Netty实现
        @Test
        public void startServer() {
            MyCar car = new MyCar();
            MyFly fly = new MyFly();
            //由于传来的MyContent中,放的是接口名,直接通过反射,并不知道应该创建该接口的哪个实现类的实例,所以需要使用Dispatcher存放接口名到具体对象的映射
            Dispatcher dis = Dispatcher.getDis();
            dis.register(Car.class.getName(), car);
            dis.register(Fly.class.getName(), fly);
            //创建20个线程,对应20个selector,但一个client只能绑定到一个selector上,所以如果只有10个client,那么只能绑定到其中10个线程上,那么NioEventLoopGroup中就会剩余10个线程,不处理IO
            NioEventLoopGroup boss = new NioEventLoopGroup(20);
            NioEventLoopGroup worker = boss;
            ServerBootstrap sbs = new ServerBootstrap();
            ChannelFuture bind = sbs.group(boss, worker)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            System.out.println("server accept cliet port: " + ch.remoteAddress().getPort());
                            ChannelPipeline p = ch.pipeline();
                            //当使用自定义的RPC协议时,需要通过如下两个handler对客户端传递来的数据进行解码和业务处理
    
                            //                        p.addLast(new ServerDecode());
                            //                        p.addLast(new ServerRequestHandler(dis));
                            //HttpServerCodec、HttpObjectAggregator:Netty内置的handler实现,对读取的数据进行编解码,解决拆包、粘包等问题,通过这两个handler将数据包转为FullHttpRequest,他们之后的handler中msg就是FullHttpRequest了
                            //其实Tomcat做的就是HttpServerCodec和HttpObjectAggregator的功能,对数据包编解码后将数据包转为Request对象,然后将Request对象传到一个handler类中,这个handler类可以决定将request传给具体Servlet中执行
                            p.addLast(new HttpServerCodec());
                            p.addLast(new HttpObjectAggregator(1024 * 512));
                            //自己定义业务逻辑
                            p.addLast(new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    //经过HttpServerCodec和HttpObjectAggregator,此处的msg就是FullHttpRequest对象
                                    FullHttpRequest request = (FullHttpRequest) msg;
                                    System.out.println(request.toString());
                                    //得到客户端请求体中的字符序列,其实就是序列化后的MyContent对象
                                    ByteBuf content = request.content();
                                    byte[] data = new byte[content.readableBytes()];
                                    content.readBytes(data);
                                    ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(data));
                                    MyContent myContent = (MyContent) oin.readObject();
                                    String serviceName = myContent.getName();
                                    String method = myContent.getMethodName();
                                    //从服务端获取一个注册好的该服务的具体对象
                                    Object c = dis.get(serviceName);
                                    Class<?> clazz = c.getClass();
                                    Object res = null;
                                    try {
                                        Method m = clazz.getMethod(method, myContent.getParameterTypes());
                                        res = m.invoke(c, myContent.getArgs());
                                    } catch (NoSuchMethodException e) {
                                        e.printStackTrace();
                                    } catch (IllegalAccessException e) {
                                        e.printStackTrace();
                                    } catch (InvocationTargetException e) {
                                        e.printStackTrace();
                                    }
                                    MyContent resContent = new MyContent();
                                    resContent.setRes(res);
                                    byte[] contentByte = SerDerUtil.ser(resContent);
                                    //使用Http协议
                                    DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_0,
                                            HttpResponseStatus.OK,
                                            Unpooled.copiedBuffer(contentByte));
                                    //当客户端使用Netty方式建立链接,而不使用URLConnection时,需要设置传输出去的数据长度,不然客户端无法解析
                                    response.headers().set(HttpHeaderNames.CONTENT_LENGTH, contentByte.length);
                                    //response相当于自定义RPC协议中的header+body
                                    ctx.writeAndFlush(response);
                                }
                            });
                        }
                    }).bind(new InetSocketAddress("localhost", 9090));
            try {
                bind.sync().channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        //服务端:Jetty实现
        //使用Jetty可以创建基于Http协议的服务器,类似Tomcat,中间件如果想内置服务器功能,通常使用Jetty实现
        //使用Jetty需要在pom文件中引入jetty-server和jetty-webapp
        @Test
        public void startHttpServer() {
            MyCar car = new MyCar();
            MyFly fly = new MyFly();
            Dispatcher dis = Dispatcher.getDis();
            dis.register(Car.class.getName(), car);
            dis.register(Fly.class.getName(), fly);
            //通过Jetty创建服务
            Server server = new Server(new InetSocketAddress("localhost", 9090));
            ServletContextHandler handler = new ServletContextHandler(server, "/");
            server.setHandler(handler);
            //只要URI为"/*",就会进入MyHttpRpcHandler这个Servlet,addServlet类似web.xml的功能
            handler.addServlet(MyHttpRpcHandler.class, "/*");
            try {
                //启动服务
                server.start();
                //阻塞,防止代码直接退出
                server.join();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        //客户端:多线程进行RPC调用
        @Test
        public void get() {
            AtomicInteger num = new AtomicInteger(0);
            int size = 50;
            Thread[] threads = new Thread[size];
            for (int i = 0; i < size; i++) {
                threads[i] = new Thread(() -> {
                    Car car = MyProxy.proxyGet(Car.class);//动态代理实现   //是真的要去触发 RPC调用吗?
                    String arg = "hello" + num.incrementAndGet();
                    String res = car.ooxx(arg);
                    System.out.println("client over msg: " + res + " src arg: " + arg);
                });
            }
            for (Thread thread : threads) {
                thread.start();
            }
            try {
                System.in.read();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        //客户端:单线程进行RPC调用
        @Test
        public void testRPC() {
            Car car = MyProxy.proxyGet(Car.class);
            Persion zhangsan = car.oxox("zhangsan", 16);
            System.out.println(zhangsan);
        }
    
        //客户端:调用本地对象的方法
        @Test
        public void testRpcLocal() {
            new Thread(() -> {
                startServer();
            }).start();
            System.out.println("server started......");
            Car car = MyProxy.proxyGet(Car.class);
            Persion zhangsan = car.oxox("zhangsan", 16);
            System.out.println(zhangsan);
        }
    
    }
    
6.2 proxy层
  1. MyProxy

       package com.bjmashibing.system.rpcdemo.proxy;
       
       import com.bjmashibing.system.rpcdemo.rpc.Dispatcher;
       import com.bjmashibing.system.rpcdemo.rpc.protocol.MyContent;
       import com.bjmashibing.system.rpcdemo.rpc.transport.ClientFactory;
       
       import java.lang.reflect.InvocationHandler;
       import java.lang.reflect.InvocationTargetException;
       import java.lang.reflect.Method;
       import java.lang.reflect.Proxy;
       import java.util.concurrent.CompletableFuture;
       
       public class MyProxy {
           public static <T> T proxyGet(Class<T> interfaceInfo) {
               ClassLoader loader = interfaceInfo.getClassLoader();
               Class<?>[] interfaces = {interfaceInfo};
               Dispatcher dis = Dispatcher.getDis();
               //Proxy.newProxyInstance需要传入3个参数
               //1. loader:使用哪个类加载器对代理类进行加载
               //2. interfaces:代理类需要实现哪些接口
               //3. InvocationHandler:当调用代理对象的方法,最终就相当于调用InvocationHandler的invoke方法
               return (T) Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
                   //proxy:代理对象本身,method:这个代理对象本次调用的方法,args:本次调用方法传递的参数
                   @Override
                   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                       Object res = null;
                       //如果本地向Dispatcher注册了Car、Fly的实例,就不必调用远程对象得到结果了
                       Object o = dis.get(interfaceInfo.getName());
                       if (o == null) {
                           String name = interfaceInfo.getName();
                           String methodName = method.getName();
                           Class<?>[] parameterTypes = method.getParameterTypes();
                           //MyContent是要传输的具体数据,Myhead是协议头,MyContent为协议体,只传输MyContent是不行的,因为协议中有MyContent的长度,不然服务端无法知道数据包是否传完,以及截取多长作为MyContent
                           MyContent content = new MyContent();
                           content.setArgs(args);
                           content.setName(name);
                           content.setMethodName(methodName);
                           content.setParameterTypes(parameterTypes);
                           //此处并没有实现通过ZK完成服务的注册发现功能,即可能有多个服务端提供服务,可以任选其一完成远程调用,本例中选择固定服务,localhost 9090
                           CompletableFuture resF = ClientFactory.transport(content);
                           //CompletableFuture:jdk提供的一个类,其对象的get方法会阻塞,直到调用该对象的complete方法,向该对象中放入返回值。此时get方法解除阻塞,并返回你放入的那个返回值
                           res = resF.get();
                       } else {
                           System.out.println("当前为本地方法调用...");
                           Class<?> clazz = o.getClass();
                           try {
                               Method m = clazz.getMethod(method.getName(), method.getParameterTypes());
                               res = m.invoke(o, args);
                           } catch (NoSuchMethodException e) {
                               e.printStackTrace();
                           } catch (IllegalAccessException e) {
                               e.printStackTrace();
                           } catch (InvocationTargetException e) {
                               e.printStackTrace();
                           }
                       }
                       return res;
                   }
               });
           }
       }
    
6.3 rpc.protocol层
  1. MyContent:表示协议体

    package com.bjmashibing.system.rpcdemo.rpc.protocol;
    
    import java.io.Serializable;
    //必须可序列化,因为需要通过网络传输
    public class MyContent implements Serializable {
        String name;
        String methodName;
        Class<?>[] parameterTypes;
        Object[] args;
        //返回的数据
        Object res;
    
        public Object getRes() {
            return res;
        }
    
        public void setRes(Object res) {
            this.res = res;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getMethodName() {
            return methodName;
        }
    
        public void setMethodName(String methodName) {
            this.methodName = methodName;
        }
    
        public Class<?>[] getParameterTypes() {
            return parameterTypes;
        }
    
        public void setParameterTypes(Class<?>[] parameterTypes) {
            this.parameterTypes = parameterTypes;
        }
    
        public Object[] getArgs() {
            return args;
        }
    
        public void setArgs(Object[] args) {
            this.args = args;
        }
    }
    
    
  2. Myheader:表示协议头

    package com.bjmashibing.system.rpcdemo.rpc.protocol;
    
    import java.io.Serializable;
    import java.util.UUID;
    //必须可序列化,因为需要通过网络传输
    public class Myheader implements Serializable {
        int flag;
      	//用于标记不同请求,表示当前协议是一个有状态的协议
        long requestID;
      	//MyContent数据包的长度
        long dataLen;
        public static Myheader createHeader(byte[] msg){
            Myheader header = new Myheader();
            int size = msg.length;
          	//int占4字节,1字节8bit,1个16进制数占4bit,即0x14占1字节,所以0x14141414正好4字节
            int f = 0x14141414;
            //防止requestID重复,所以使用UUID生成requestID
            long requestID =  Math.abs(UUID.randomUUID().getLeastSignificantBits());
            header.setFlag(f);
            header.setDataLen(size);
            header.setRequestID(requestID);
            return header;
        }
    
        public int getFlag() {
            return flag;
        }
    
        public void setFlag(int flag) {
            this.flag = flag;
        }
    
        public long getRequestID() {
            return requestID;
        }
    
        public void setRequestID(long requestID) {
            this.requestID = requestID;
        }
    
        public long getDataLen() {
            return dataLen;
        }
    
        public void setDataLen(long dataLen) {
            this.dataLen = dataLen;
        }
    }
    
6.4 rpc.transport层
  1. ClientFactory

    package com.bjmashibing.system.rpcdemo.rpc.transport;
    
    
    import com.bjmashibing.system.rpcdemo.util.SerDerUtil;
    import com.bjmashibing.system.rpcdemo.rpc.ResponseMappingCallback;
    import com.bjmashibing.system.rpcdemo.rpc.protocol.MyContent;
    import com.bjmashibing.system.rpcdemo.rpc.protocol.Myheader;
    import io.netty.bootstrap.Bootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.PooledByteBufAllocator;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.*;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import io.netty.handler.codec.http.*;
    
    import java.io.*;
    import java.net.*;
    import java.util.Random;
    import java.util.concurrent.CompletableFuture;
    import java.util.concurrent.ConcurrentHashMap;
    
    public class ClientFactory {
    
        int poolSize = 5;
        NioEventLoopGroup clientWorker;
        Random rand = new Random();
    
        private ClientFactory() {
        }
    
        private static final ClientFactory factory;
    
        static {
            factory = new ClientFactory();
        }
    
        public static ClientFactory getFactory() {
            return factory;
        }
    
        public static CompletableFuture<Object> transport(MyContent content) {
    //        String type = "rpc";
            String type = "http";
            CompletableFuture<Object> res = new CompletableFuture<>();
            //调用通过自定义RPC协议暴露服务的Provider
            if (type.equals("rpc")) {
                byte[] msgBody = SerDerUtil.ser(content);
                Myheader header = Myheader.createHeader(msgBody);
                byte[] msgHeader = SerDerUtil.ser(header);
    //        System.out.println("main:::"+ msgHeader.length);
                NioSocketChannel clientChannel = factory.getClient(new InetSocketAddress("localhost", 9090));
                ByteBuf byteBuf = PooledByteBufAllocator.DEFAULT.directBuffer(msgHeader.length + msgBody.length);
                long id = header.getRequestID();
                //添加回调
                ResponseMappingCallback.addCallBack(id, res);
                byteBuf.writeBytes(msgHeader);
                byteBuf.writeBytes(msgBody);
                //注意,不能使用channelFuture.sync()来完成阻塞,因为该阻塞只能阻塞到发送成功,而不能一直阻塞到服务端返回结果,所以需要使用CompletableFuture的get方法保证阻塞,如果不保证阻塞,那可能没等服务端返回数据,客户端就执行完毕,那么返回的结果就不正确了
                ChannelFuture channelFuture = clientChannel.writeAndFlush(byteBuf);
            }
            //调用通过http协议暴露服务的Provider
            else {
                //1. 使用jdk提供的URL类建立链接,该类包含了Http的编解码、建立Socket链接等功能
                //通过Http协议建立链接,由于Http无状态,所以为每个客户端分配一个链接,而没法使用一个链接
    //            urlTS(content,res);
    
                //2. 使用Netty实现
                //每个客户端,都会new Bootstrap,然后调用Bootstrap对象的connet方法建立链接,所以是每个客户端新建一个链接
                nettyTS(content, res);
            }
            return res;
        }
    
        private static void nettyTS(MyContent content, CompletableFuture<Object> res) {
            //应该定义到外面
            NioEventLoopGroup group = new NioEventLoopGroup(1);
            Bootstrap bs = new Bootstrap();
            Bootstrap client = bs.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            //编解码
                            p.addLast(new HttpClientCodec())
                                    .addLast(new HttpObjectAggregator(1024 * 512))
                                    //处理服务端返回的数据对应的FullHttpResponse
                                    .addLast(new ChannelInboundHandlerAdapter() {
                                        @Override
                                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                            FullHttpResponse response = (FullHttpResponse) msg;
                                            System.out.println(response.toString());
                                            ByteBuf resContent = response.content();
                                            byte[] data = new byte[resContent.readableBytes()];
                                            resContent.readBytes(data);
                                            ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(data));
                                            MyContent o = (MyContent) oin.readObject();
                                            res.complete(o.getRes());
                                        }
                                    });
                        }
                    });
            try {
                ChannelFuture syncFuture = client.connect("localhost", 9090).sync();
                Channel clientChannel = syncFuture.channel();
                byte[] data = SerDerUtil.ser(content);
                DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_0,
                        HttpMethod.POST, "/",
                        Unpooled.copiedBuffer(data)
                );
                //设置长度,否则服务端解析会报错
                request.headers().set(HttpHeaderNames.CONTENT_LENGTH, data.length);
                //客户端使用Http协议向服务端发送数据
                clientChannel.writeAndFlush(request).sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        private static void urlTS(MyContent content, CompletableFuture<Object> res) {
            Object obj = null;
            try {
                URL url = new URL("http://localhost:9090/");
                HttpURLConnection hc = (HttpURLConnection) url.openConnection();
                hc.setRequestMethod("POST");
                //告诉URL底层通信时,自己的数据包中需要发送请求体
                hc.setDoOutput(true);
                hc.setDoInput(true);
                //获得IO,注意这个IO是BIO
                OutputStream out = hc.getOutputStream();
                ObjectOutputStream oout = new ObjectOutputStream(out);
                //此处不真正发送
                oout.writeObject(content);
                //真正发送,并阻塞等待返回数据
                if (hc.getResponseCode() == 200) {
                    //拿到返回的输入流
                    InputStream in = hc.getInputStream();
                    ObjectInputStream oin = new ObjectInputStream(in);
                    MyContent myContent = (MyContent) oin.readObject();
                    obj = myContent.getRes();
                }
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            //放入远程返回的结果,并解除阻塞
            res.complete(obj);
        }
        //为不同ip的Provider,准备不同的链接池,即每个Provider可以建立多个链接
        ConcurrentHashMap<InetSocketAddress, ClientPool> outboxs = new ConcurrentHashMap<>();
        public NioSocketChannel getClient(InetSocketAddress address) {
            ClientPool clientPool = outboxs.get(address);
            //多线程时需要加入,双重判断保证clientPool一定为null才会新建
            if (clientPool == null) {
                synchronized (outboxs) {
                    if (clientPool == null) {
                        outboxs.putIfAbsent(address, new ClientPool(poolSize));
                        clientPool = outboxs.get(address);
                    }
                }
            }
            //从链接池中随机选择一个链接
            int i = rand.nextInt(poolSize);
            //如果链接已经被初始化,且链接是active状态,那么可以直接返回使用
            if (clientPool.clients[i] != null && clientPool.clients[i].isActive()) {
                return clientPool.clients[i];
            } else {
                //锁住clientPool.lock[i]这个对象,直到代码执行完成,才解锁,不同线程进来,如果也想锁这个对象,会失败
                synchronized (clientPool.lock[i]) {
                    //也是双重判断防止多线程下出现问题
                    if (clientPool.clients[i] == null || !clientPool.clients[i].isActive())
                        //使用Netty创建链接
                        clientPool.clients[i] = create(address);
                }
            }
            return clientPool.clients[i];
        }
    
        private NioSocketChannel create(InetSocketAddress address) {
            //基于Netty创建链接
            clientWorker = new NioEventLoopGroup(1);
            Bootstrap bs = new Bootstrap();
            ChannelFuture connect = bs.group(clientWorker)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            //接收到数据后的处理
                          	//ServerDecode:用于将read到的内容,转换成Packmsg放入集合
                            //ClientResponses:用于依次处理集合中的Packmsg
                            p.addLast(new ServerDecode());
                            p.addLast(new ClientResponses());
                        }
                    }).connect(address);
            try {
                NioSocketChannel client = (NioSocketChannel) connect.sync().channel();
                return client;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    
  2. ClientPool

    package com.bjmashibing.system.rpcdemo.rpc.transport;
    
    import io.netty.channel.socket.nio.NioSocketChannel;
    
    public class ClientPool{
        NioSocketChannel[] clients;
        Object[] lock;
        ClientPool(int size){
            //初始化,链接还是null
            clients = new NioSocketChannel[size];
            //锁,同一时间不能有两个客户端创建对应索引的链接
            lock = new Object[size];
            for(int i = 0;i< size;i++){
                lock[i] = new Object();
            }
        }
    }
    
  3. ClientResponses

    package com.bjmashibing.system.rpcdemo.rpc.transport;
    
    import com.bjmashibing.system.rpcdemo.util.Packmsg;
    import com.bjmashibing.system.rpcdemo.rpc.ResponseMappingCallback;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    
    public class ClientResponses extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
          	//由于先使用了解码器,所以msg此时类型为Packmsg
            Packmsg responsepkg = (Packmsg) msg;
          	//执行回调,解除阻塞
            ResponseMappingCallback.runCallBack(responsepkg);
        }
    }
    
  4. MyHttpRpcHandler

    package com.bjmashibing.system.rpcdemo.rpc.transport;
    
    import com.bjmashibing.system.rpcdemo.rpc.Dispatcher;
    import com.bjmashibing.system.rpcdemo.rpc.protocol.MyContent;
    
    import javax.servlet.ServletException;
    import javax.servlet.ServletInputStream;
    import javax.servlet.ServletOutputStream;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class MyHttpRpcHandler extends HttpServlet {
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            ServletInputStream in = req.getInputStream();
            ObjectInputStream oin = new ObjectInputStream(in);
            try {
                MyContent myContent = (MyContent) oin.readObject();
                String serviceName = myContent.getName();
                String method = myContent.getMethodName();
                Object c = Dispatcher.getDis().get(serviceName);
                Class<?> clazz = c.getClass();
                Object res = null;
                try {
                    Method m = clazz.getMethod(method, myContent.getParameterTypes());
                    res = m.invoke(c, myContent.getArgs());
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
                MyContent resContent = new MyContent();
                resContent.setRes(res);
                if (res == null) {
                    System.out.println("完蛋了。。。");
                }
    //                byte[] contentByte = SerDerUtil.ser(resContent);
                ServletOutputStream out = resp.getOutputStream();
                ObjectOutputStream oout = new ObjectOutputStream(out);
                oout.writeObject(resContent);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    
    }
    
  5. ServerDecode

    package com.bjmashibing.system.rpcdemo.rpc.transport;
    
    import com.bjmashibing.system.rpcdemo.util.Packmsg;
    import com.bjmashibing.system.rpcdemo.rpc.protocol.MyContent;
    import com.bjmashibing.system.rpcdemo.rpc.protocol.Myheader;
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.handler.codec.ByteToMessageDecoder;
    
    import java.io.ByteArrayInputStream;
    import java.io.ObjectInputStream;
    import java.util.List;
    
    public class ServerDecode extends ByteToMessageDecoder {
        //ByteToMessageDecoder类中,在channelRead中,替你解决了拆包的问题,其内逻辑为:将上一次剩余的ByteBuf拼接到本次的ByteBuf上,然后调用decode代码,最后将decode中剩余的ByteBuf,留存起来,用于拼接到下一次的ByteBuf上
        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception {
            //Myheader为122位长,大于等于122表示ByteBuf中一定包含一个完整的Myheader,该长度取决于你序列化的Myhead对象,当Myhead处于不同包下时,该值可能不同,所以开始时,应该先将Myhead长度打印,来确定该值到底应为多少
            //此处需要使用while处理,因为本次ByteBuf中,可能包含多个Myhead+MyContent,且只有当ByteBuf中包含一整个Myhead+MyContent时,才应该移动ByteBuf中的读指针,否则应将ByteBuf记录下来和下一次的ByteBuf合并
            while (buf.readableBytes() >= 122) {
                byte[] bytes = new byte[122];
                //获得Myheader,这样就能通过header.getDataLen得到MyContent的长度
                //此处不移动ByteBuf的读指针
                buf.getBytes(buf.readerIndex(), bytes);
                ByteArrayInputStream in = new ByteArrayInputStream(bytes);
                ObjectInputStream oin = new ObjectInputStream(in);
                Myheader header = (Myheader) oin.readObject();
                //如果ByteBuf-Myheader长度,还超过MyContent的长度,说明ByteBuf中包含一个完整的Myheader+MyContent
                if (buf.readableBytes() - 122 >= header.getDataLen()) {
                    //此时就可以将指针移动到MyContent开始的位置
                    buf.readBytes(122);
                    byte[] data = new byte[(int) header.getDataLen()];
                    buf.readBytes(data);
                    ByteArrayInputStream din = new ByteArrayInputStream(data);
                    ObjectInputStream doin = new ObjectInputStream(din);
                    //flag为0x14141414表示在处理客户端发来的数据,为0x14141424表示正处理服务端发来的数据,对于当前代码,处理客户端或服务端发来的数据并没有区别
                    if (header.getFlag() == 0x14141414) {
                        MyContent content = (MyContent) doin.readObject();
                        //Packmsg:对Myhead+MyContent的进一步封装,方便后续放入out集合
                        out.add(new Packmsg(header, content));
                    } else if (header.getFlag() == 0x14141424) {
                        MyContent content = (MyContent) doin.readObject();
                        out.add(new Packmsg(header, content));
                    }
                } else {
                    break;
                }
            }
        }
    }
    
  6. ServerRequestHandler

    package com.bjmashibing.system.rpcdemo.rpc.transport;
    
    import com.bjmashibing.system.rpcdemo.util.Packmsg;
    import com.bjmashibing.system.rpcdemo.util.SerDerUtil;
    import com.bjmashibing.system.rpcdemo.rpc.Dispatcher;
    import com.bjmashibing.system.rpcdemo.rpc.protocol.MyContent;
    import com.bjmashibing.system.rpcdemo.rpc.protocol.Myheader;
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.PooledByteBufAllocator;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    //服务端收到数据后,调用Dispatcher中对象的指定方法,并返回给客户端
    public class ServerRequestHandler extends ChannelInboundHandlerAdapter {
        Dispatcher dis;
    
        public ServerRequestHandler(Dispatcher dis) {
            this.dis = dis;
        }
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            //先调用了ServerDecode这个handler,所以此处的msg已经变为了放入ServerDecode中的out集合中的Packmsg
            Packmsg requestPkg = (Packmsg) msg;
            //此处可以有三种方式处理业务逻辑
            //1. 直接在当前线程,当前方法中编写业务逻辑:但由于业务逻辑和IO在一个线程中处理,所以业务逻辑阻塞,会导致IO也被阻塞
            //2. 自己创建线程池,并从中选取一个线程处理业务逻辑:会导致没有充分利用NioEventLoopGroup中的线程,可能NioEventLoopGroup中还有线程空闲,但自己又建立了一些线程处理业务逻辑
            //3. 使用Netty自己的eventloop中的线程来处理业务逻辑
          		//a. 写法一:ctx.executor().execute:这相当于将处理业务逻辑的代码,放入了当前线程的任务队列,之后其实还是当前线程处理该逻辑,也就是说,如果该业务逻辑阻塞,还是会导致IO阻塞
            	//b. 写法二:ctx.executor().parent().next().execute:从NioEventLoopGroup线程组中,选取下一个线程对业务逻辑进行处理,其实就是将业务逻辑放到其他并不常使用的IO线程中执行,而不在当前IO线程中执行,假设NioEventLoopGroup中有20个线程,但Client只有1个,由于1个Client只能绑定到一个Selector,所以只能用到NioEventLoopGroup中的一个线程,此时使用写法二,就能充分地利用剩下不处理IO的那19个线程,对业务逻辑进行处理
            ctx.executor().execute(new Runnable() {
                //        ctx.executor().parent().next().execute(new Runnable() {
                @Override
                public void run() {
                    String serviceName = requestPkg.getContent().getName();
                    String method = requestPkg.getContent().getMethodName();
                    Object c = dis.get(serviceName);
                    Class<?> clazz = c.getClass();
                    Object res = null;
                    try {
                        Method m = clazz.getMethod(method, requestPkg.getContent().getParameterTypes());
                        res = m.invoke(c, requestPkg.getContent().getArgs());
                    } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
    //                String execThreadName = Thread.currentThread().getName();
                    MyContent content = new MyContent();
    //                String s = "io thread: " + ioThreadName + " exec thread: " + execThreadName + " from args:" + requestPkg.content.getArgs()[0];
                    content.setRes(res);
                    byte[] contentByte = SerDerUtil.ser(content);
                    Myheader resHeader = new Myheader();
                    //返回的requestID和接收的requestID应该一样
                    resHeader.setRequestID(requestPkg.getHeader().getRequestID());
                    //返回的头是0x14141424,不再是0x14141414
                    resHeader.setFlag(0x14141424);
                    resHeader.setDataLen(contentByte.length);
                    byte[] headerByte = SerDerUtil.ser(resHeader);
                    ByteBuf byteBuf = PooledByteBufAllocator.DEFAULT.directBuffer(headerByte.length + contentByte.length);
                    byteBuf.writeBytes(headerByte);
                    byteBuf.writeBytes(contentByte);
                    ctx.writeAndFlush(byteBuf);
                }
            });
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            System.out.println("client close");
        }
    }
    
6.5 rpc层
  1. Dispatcher

    package com.bjmashibing.system.rpcdemo.rpc;
    
    import java.util.concurrent.ConcurrentHashMap;
    
    public class Dispatcher {
    
        private static Dispatcher dis = null;
    
        static {
            dis = new Dispatcher();
        }
    
        public static Dispatcher getDis() {
            return dis;
        }
    
        private Dispatcher() {
    
        }
    
        public static ConcurrentHashMap<String, Object> invokeMap = new ConcurrentHashMap<>();
    
        public void register(String k, Object obj) {
            invokeMap.put(k, obj);
        }
    
        public Object get(String k) {
            return invokeMap.get(k);
        }
    
    }
    
  2. ResponseMappingCallback

    package com.bjmashibing.system.rpcdemo.rpc;
    
    import com.bjmashibing.system.rpcdemo.util.Packmsg;
    import java.util.concurrent.CompletableFuture;
    import java.util.concurrent.ConcurrentHashMap;
    
    public class ResponseMappingCallback {
        static ConcurrentHashMap<Long, CompletableFuture> mapping = new ConcurrentHashMap<>();
    
        public static void addCallBack(long requestID, CompletableFuture cb) {
            mapping.putIfAbsent(requestID, cb);
        }
    		//为指定的requestID对应的客户端线程提供返回值,让其不再阻塞
        public static void runCallBack(Packmsg msg) {
            CompletableFuture cf = mapping.get(msg.getHeader().getRequestID());
            cf.complete(msg.getContent().getRes());
            removeCB(msg.getHeader().getRequestID());
        }
    
        private static void removeCB(long requestID) {
            mapping.remove(requestID);
        }
    }
    
6.6 service层
  1. Car

    package com.bjmashibing.system.rpcdemo.service;
    
    public interface Car{
        public String ooxx(String msg);
        public Persion oxox(String name,Integer age);
    }
    
  2. Fly

    package com.bjmashibing.system.rpcdemo.service;
    
    public interface Fly{
        void xxoo(String msg);
    }
    
  3. MyCar

    package com.bjmashibing.system.rpcdemo.service;
    
    public class MyCar implements Car {
    
        @Override
        public String ooxx(String msg) {
            return "server res "+ msg;
        }
    
        @Override
        public Persion oxox(String name, Integer age) {
            Persion p = new Persion();
            p.setName(name);
            p.setAge(age);
            return p;
        }
    }
    
  4. MyFly

    package com.bjmashibing.system.rpcdemo.service;
    
    public class MyFly implements Fly {
        @Override
        public void xxoo(String msg) {
            System.out.println("server,get client arg:" + msg);
        }
    }
    
  5. Person

    package com.bjmashibing.system.rpcdemo.service;
    
    import java.io.Serializable;
    
    public class Persion implements Serializable {
        String name;
        Integer age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return name + " " + age;
        }
    }
    
6.7 util层
  1. Packmsg:封装数据包

    package com.bjmashibing.system.rpcdemo.util;
    
    import com.bjmashibing.system.rpcdemo.rpc.protocol.MyContent;
    import com.bjmashibing.system.rpcdemo.rpc.protocol.Myheader;
    
    public class Packmsg {
        Myheader header;
        MyContent content;
    
        public Myheader getHeader() {
            return header;
        }
    
        public void setHeader(Myheader header) {
            this.header = header;
        }
    
        public MyContent getContent() {
            return content;
        }
    
        public void setContent(MyContent content) {
            this.content = content;
        }
    
        public Packmsg(Myheader header, MyContent content) {
            this.header = header;
            this.content = content;
        }
    }
    
  2. SerDerUtil:将对象序列化后转为字节数组

    package com.bjmashibing.system.rpcdemo.util;
    
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutputStream;
    
    public class SerDerUtil {
      	//只创建一个ByteArrayOutputStream对象,可以重复利用,也因此ser方法必须加同步进行处理
        static ByteArrayOutputStream out = new ByteArrayOutputStream();
    
        public synchronized static byte[] ser(Object msg) {
            out.reset();
            ObjectOutputStream oout = null;
            byte[] msgBody = null;
            try {
                oout = new ObjectOutputStream(out);
                oout.writeObject(msg);
                msgBody = out.toByteArray();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return msgBody;
        }
    }
    

7 EventLoopGroup

  1. EventLoopGroup相当于Selector+线程池
  2. 一个EventLoop对应一个线程,该线程中的处理分为3步
    1. select
    2. processSelectedKeys:此处使用当前线程,依次执行pipeline中的handler,但handler中,可以直接在当前线程处理,也可以将要处理的内容放入当前线程或其他线程的任务队列中,等处理任务队列那步再进行处理
    3. 处理任务队列
  3. 如果我们建立的NioEventLoopGroup有20个线程,但只有10个client,由于1个client最多注册到一个selector,因此最多会使用10个线程处理IO,此时我们可以将handler中要处理的内容,放入其他线程的任务队列中,由那些不处理IO的线程处理业务逻辑,这样就能充分的利用系统资源

8 ServerBootstrap

  1. 当使用同一个ServerBootstrap对象可以bind多个ip端口,那么两个端口用的是一套handler逻辑,只不过丰富了暴露的端口号而已
  2. 当使用不同ServerBootstrap对象时bind多个ip端口,那么两个端口号用的是各自的handler逻辑。且两个ServerBootstrap对象可以使用不同的boss和worker,只不过比较浪费资源而已

9 TODO和FIXME注释

  1. 当在IDE中使用//TODO或//FIXME开头进行注释时,IDE中会高亮显示此注释,且在右边栏中标记为蓝色小条,这样就方便后续你找到这个注释所在的位置
  2. 当前先不进行实现,后续再对其进行实现的内容,使用//TODO注释
  3. 代码错误,但准备之后再进行修复的内容,使用//FIXME注释

10 mmap

  1. 网卡是字符设备,硬盘是块设备,只有块设备可以做mmap映射,而字符设备不行,即Socket无法做mmap映射,所以从内核搬运到用户空间这步无法省略

11 selectedKeys中调用remove

  1. select:从内核将有事件的fd集合放入jvm直接空间(内核),每次会清空重放
  2. selectedKeys:从内核放入jvm堆中,并不是每次返回一个新集合,而是将新的select得到的内容放入,历史的不清除
  3. 所以每次需要对处理完的SelectionKey,从selectedKeys集合中删除
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值