深入理解通讯协议-手写RMI和RPC

深入理解通讯协议

七层网络模型

 

应用层:专门为应用层服务的,比如说我们的浏览器

表示层:主要数据格式的转换,数据的加密,比如说服务器的编码格式,需要表示层做一个格式的转换;

会话层: 主要是建立回话的,确保回话的稳定和安全;

传输层:是OSI最重要的关键一环;确保端对端的可靠性传输;

网络层:主要是IP和路由的选择

链路成:主要负责链路的管理,比如说我们的交换机,就是在链路层的

物理层:就是物理传输的介质,比如说网线

高一层是建立在低一层的基础之上的;

TCP/IP系列协议

 

TCP/IP 是互联网架构的一个基础;

TCP传输以后到网络层的IP 主要是目的地和原地址的一个交互

ping 命令可以测试两台主机是不是通的

TCP的三次握手协议

 

TCP的三次握手的漏洞

 

三种解决方案

1,监控达到一个阈值都没有连接次数,就把所有的连接都释放;

2,有第三次连接之后我才确认连接

3,防火墙,确认你的连接都是有效的之后我才会让你请求后端的服务;

TCP的四次挥手

客户端和服务端是可以相互发消息和接收消息;

 

抓包工具 wireshark

世界上最流行的网络协议分析器

TCP的通讯原理

 

tcp 是使用主机的ip地址和端口作为TCP连接的端点;

当我的TCP接收端里面没有内容的时候回发生阻塞。当我的发送端缓冲区写满了之后也会阻塞;

TCP的可靠性传输

解决这个问题就会引入一个概念 ,就是滑动窗口

 

滑动窗口,是一个接收方,和发送放都维护的一个序列;这个顺序被称为窗口;

只有在窗口里面的数字才会被真正的发送;

 

在线演示滑动窗口:

​
https://media.pearsoncmg.com/aw/ecs_kurose_compnetwork_7/cw/content/interactiveanimations/selective-repeat-protocol/index.html
​

滑动窗口: 确保数据不会丢失,如果丢失会从发;

限制发送的速度;

如果多次发送失败,就会断开连接;

有效的控制一个阻塞;

HTTP协议

 

http 协议是一个超文本的协议;

uri 是定义资源的唯一标识,

url 是网络中唯一的路径;

HTTP报文的组成

 

请求行: 比如说他是get还是post 请求

3xx状态 重定向,我需要附加的操作才能完成;

深入理解通讯协议2

UDP协议

 

UDP 面向的是一个无连接的状态;

所以他是相对不可靠的;

优点: 传输快

缺点:可能会数据丢失

UDP 的组成

源端口: 源端口和端口号,不需要时可为0

目的端口号: 目的端口号必须填;

长度:UDP用户数据的长度,最小值是8

校验和: 用来校验UDP的数据是不是可靠;如果这个传输校验和不对,我就直接丢弃

为什么使用UDP,因为效率高;

tcp实战

package enjoy.protocol.tcp;
​
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
​
/**
 * TCP 服务器端
 */
public class TcpServer {
    public static void main(String[] args) throws Exception {
        // 第一部分 创建一个ServerSocket监听一个端口 : 8888
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("TCP服务器已经启动,端口是 8888");
        while (true){
        //无线循环等待客户端的请求,如果有就分配一个请求
            Socket socket = serverSocket.accept();
            // 根据标准输入构造一个bufferReader
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String buffer=null;
            //循环读取输入的每一行;
            while((buffer=reader.readLine())!=null&& !buffer.equals("")){
                //输出每一行
                System.out.println(buffer);
               //输出完之后 服务器给客户端有些东西;
            }
            // 通过Socket 对象得到输出流,构造BufferedWrite
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            //模拟http的请求头信息
            writer.write("HTTP/1.1 200 OK \r\n Content-Type:text/html \r\n charset=UTF-8\r\n\r\n ");
            // 写http的请求body 信息
            writer.write("<html><head><title>http请求</title></head><body><h1>这是一个HTTP请求</h1></body></html>");
            // 刷新这个输出流使得数据立马就发送
            writer.flush();
            // 关闭
            reader.close();
            writer.close();
            socket.close();
        }
    }
​
}
​

​
package enjoy.protocol.tcp;
​
import java.io.PrintWriter;
import java.net.Socket;
​
/**
 * TCP 客户端
 */
public class TcpClient {
    public static void main(String[] args) throws Exception{
        String msg="hello 13";
        //穿件一个Socket ,跟本机的8888 端口进行连接
        Socket socket = new Socket("127.0.0.1", 8888);
        // 使用socket 创建一个printWrite 进行写数据
        PrintWriter printWriter = new PrintWriter(socket.getOutputStream());
        // 发送数据
        printWriter.println(msg);
        //刷新一下,使得服务器立马收到请求信息
        printWriter.flush();
        printWriter.close();
        socket.close();
    }
}
​

 

实现UDP

​
​
package enjoy.protocol.udp;
​
import java.net.DatagramPacket;
import java.net.DatagramSocket;
​
public class ReciveDeom {
​
    public static void main(String[] args) throws Exception {
​
        // 创建一个DatagramDocket 实例,并且绑定到本机地址,端口10005
        DatagramSocket datagramSocket = new DatagramSocket(10005);
        byte[] bytes = new byte[1024];
        // 以一个空的数组来床间距 DatagramPacket ,这个对象作用是接收DatagramDocket  中的数据
        DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);
        // 因为我不知道你什么时候发送数据过来,所以我在这里无限循环等待
        while (true){
            // 接收到数据包
            datagramSocket.receive(datagramPacket);
            // 获取接收的数据
            byte[] data =datagramPacket.getData();
            //把数组转成字符
            String str= new String(data,0,datagramPacket.getLength());
            // 如果数据包中的数据是88的信息, 则跳出且关闭
            if("88".equals(str)){
                break;
            }
            System.out.println("接收到的数据为"+str);
        }
        // 关闭
        datagramSocket.close();
​
    }
​
​
}

​
package enjoy.protocol.udp;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
 * UDP 发送端
 */
public class SendDemo {
    public static void main(String[] args) throws Exception{
        // 创建一个DatagramSocket 实例
        DatagramSocket datagramSocket = new DatagramSocket();
        // 使用键盘输入构建一个BufferedReader
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        String line=null;
        while ((line=bufferedReader.readLine())!=null){
            //转成byte
           byte [] bytes = line.getBytes();
           //创建一个用于发送DatagramPacket对象
            DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("127.0.0.1"), 10005);
            // 发送数据包
            datagramSocket.send(datagramPacket);
            // 当输入88时结束发送
            if("88".equals(line)){
                break;
            }
        }
        // 关闭
        datagramSocket.close();
    }
}

总结

 

基于分布式思想下的RPC解决方案

手写RPC 方案

 

第一步, 定义服务的接口,并且注册服务的接口;

第二步,进行查询服务在哪个地方;去注册中心去查;

第三步, 作为客户端调用需要使用一个动态代理;

第四步,对动态代理做一个增强;

第五步,进行通讯

第六步,反序列化得出他的对象,方法,参数;

第七步,通过反射的机制完成调用

实现技术方案

Socket通讯,动态代理与反射,Java序列化

动态代理

 

代理模式三要素

1,接口,

2,接口真实对象,一定是继承接口的实现,他是真正的实现

3,还有一个代理

真实对象和代理对象必须同事继承同一个接口;

代理的对象他必须包含真实的对象在里面;

InvocationHandler 增强代理

Proxy 就是个调度器

ROC实现细节

 

序列化是把对象转化为01串

反序列化,就是把在网络上传输的01串转化为java的bean对象;

反射就是: 在运行的时候,你给我一个名称,你给我一个方法名,给我一个方法的参数,我就可以把这个类完整的构造出来,并且去调用他对应的方法;

实现代码demo

客户端

public interface TechInterface {
    String XJ(String name);
}
​

package enjoyedu.rpc;
​
import jdk.internal.org.objectweb.asm.tree.TryCatchBlockNode;
​
import javax.lang.model.element.VariableElement;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;
​
/**
 * 客户端的动态代理
 */
public class RpcClientFrame {
​
    public static <T> T getRemotProxyObj(final Class<?> serviceInterface) throws Exception{
​
        InetSocketAddress serviceAddr=new InetSocketAddress("127.0.0.1",8888);
        return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(),new Class<?>[]{serviceInterface},new DynProxy(serviceInterface,serviceAddr));
    }
​
    /**
     * 实现动态代理类,实现了对远程服务的访问
     *
     */
    private  static class DynProxy implements InvocationHandler{
        //接口
        private final Class<?> serviceInterface;
        //远程调用地址
        private final InetSocketAddress addr;
        public DynProxy(Class<?> serviceInterface,InetSocketAddress addr){
            this.serviceInterface=serviceInterface;
            this.addr=addr;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            /**
             * 网络增强部分
             */
            //  使用socket
            Socket socket =null;
            //因为传递的大部分是方法, 参数, 所以我们使用Object 流对象
            ObjectInputStream objectInputStream =null;
            ObjectOutputStream objectOutputStream=null;
            try{
               socket=new Socket();
               //连接到远程的地址和端口
               socket.connect(addr);
               // 往远端 发送数据,按照顺序发送数据: 类名,方法名,参数类型,参数值
                // 拿到输出的流
                objectOutputStream =new ObjectOutputStream(socket.getOutputStream());
                // 发送 调用方法的类名  使用UTF-8  避免乱码
                objectOutputStream.writeUTF(serviceInterface.getName());
                // 发送 方法名
                objectOutputStream.writeUTF(method.getName());
                // 发送 调用方法的类名  使用Object
                objectOutputStream.writeObject(method.getParameterTypes());
                // 发送 参数的值
                objectOutputStream.writeObject(args);
                objectOutputStream.flush();
                // 立马拿到远程的执行结果
                objectInputStream =new ObjectInputStream(socket.getInputStream());
                // 把调用的细节打印出来
                System.out.println("远程调用成功!"+serviceInterface.getName());
                //最后网络的请求返回给返回结果
                return objectInputStream.readObject();
​
            }catch (Exception e){
              e.printStackTrace();
            }
            finally {
                // 关闭
             socket.close();
             objectInputStream.close();
             objectOutputStream.close();
                return null;
            }
        }
    }
}
​

package enjoyedu;
​
import enjoyedu.rpc.RpcClientFrame;
import enjoyedu.service.TechInterface;
​
public class Client {
    public static void main(String[] args) throws  Exception {
        TechInterface techInterface = RpcClientFrame.getRemotProxyObj(TechInterface.class);
        System.out.println(techInterface.XJ("king"));
    }
}
​

服务server端

package enjoyedu.service;
​
public interface TeachInterface {
    // 服务员的接口
    String XJ(String name);
}
​

​
package enjoyedu.service.impl;
​
import enjoyedu.service.TeachInterface;
​
public class TechImpl implements TeachInterface {
​
    @Override
    public String XJ(String name) {
        return "您好,13号为您服务"+name;
    }
}
​

package enjoyedu;
​
import enjoyedu.register.RegisterCenter;
import enjoyedu.service.TeachInterface;
import enjoyedu.service.impl.TechImpl;
​
import java.io.IOException;
​
public class Server {
    public static void main(String[] args) throws Exception{
​
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 起一个注册中心
                    RegisterCenter serviceServer = new RegisterCenter(8888);
                    // 注册中心的服务至注册中心
                    serviceServer.register(TeachInterface.class, TechImpl.class);
                    // 运行我们的服务
                    serviceServer.start();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
​
    }
​
}
​

注册中心

package enjoyedu.register;
​
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
​
/**
 * 注册中心
 */
public class RegisterCenter {
    //线程池
    private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
   private static final HashMap<String,Class> serviceRegistry=new HashMap<String,Class>();
   private static boolean isRunning =false;
   private static int port;
    public  RegisterCenter(int port){
        this.port=port;
​
    }
    // 启动支持中心 启动
    public void start() throws IOException{
        ServerSocket server = new ServerSocket();
        server.bind(new InetSocketAddress(port));
        System.out.println("start server");
        try {
            while (true){
               //  1 监听客户端的TCP 连接,连接到TCP连接以后将其封装成task,由线程池执行,
                executor.execute(new ServiceTask(server.accept()));
            }
        }finally {
            server.close();
        }
    }
    // 注册服务: socket 通讯+ 反射
    public void register(Class serviceInterface,Class impl){
        serviceRegistry.put(serviceInterface.getName(),impl);
    }
    // 服务端获取运行
    private static class ServiceTask implements Runnable{
        Socket clent=null;
        public ServiceTask(Socket client){
            this.clent=client;
        }
        public void run(){
            // 反射
            // 同样使用object 流
            ObjectInputStream inputStream =null;
            ObjectOutputStream outputStream=null;
            try{
                //1 发送的object 对象拿到, 2,在使用反射的机制进行调用,3,最后返回结果
                inputStream=new ObjectInputStream(clent.getInputStream());
                // 拿到类名
                String serviceName = inputStream.readUTF();
                String methodName = inputStream.readUTF();
                //拿到参数类型
                Class<?>[] prarmTypes= ( Class<?>[])inputStream.readObject();
                 // 拿到参数值
                Object[]  arguments = ( Object[] )inputStream.readObject();
                // 到注册中心根据 接口名,获取实现类
                Class serviceClass =serviceRegistry.get(serviceName);
                // 使用反射的机制进行调用
                Method method=serviceClass.getMethod(methodName,prarmTypes);
                // 反射调用方法 ,类的实例化,反射参数值,把结果拿到
                Object result = method.invoke(serviceClass.newInstance(), arguments);
                // 通过执行socket 返回给客户端
                outputStream=new ObjectOutputStream(clent.getOutputStream());
                outputStream.writeObject(result);
                // 关闭
                outputStream.close();
                inputStream.close();
                clent.close();
​
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
}
​

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值