通讯协议 & 基于分布式思想下的RPC方案

网络模型

OSI模型(开放式系统互联),它是由国际标准化组织(ISO)提出的。

通讯协议 & 基于分布式思想下的RPC方案_第1张图片

TCP/IP系列协议

TCP/IP(Transmission Control Protocol/Internet Protocol)不只是TCP/IP两个协议,而是有很多个协议组成,并且是在不同的层,是互联网的基础通信架构。

通讯协议 & 基于分布式思想下的RPC方案_第2张图片

一个http请求浏览:应用层HTTP -> 传输层TCP -> 网络层IP(数据包)、 ICMP(确保源地址和目的地址之间是网络通)、IGMP(本地路由器和英特网的路由器联通) ->链路层

直接使用网络层协议的应用:ping命令,ICMP协议。

TCP的3次握手协议

第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。

TCP的4次挥手协议

(1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。

TCP的通讯原理

“阻塞模式”:如果接收端,当然接收端缓冲区为空的时候,调用Socket的read方法的线程会阻塞,阻塞到有数据进入接收缓冲区;另外对于写数据到Socket中的线程来说,如果待发送的数据长度大于发送缓冲区空余长度,则会阻塞在write方法上,等待发送缓冲区的报文被发送到网络上,所以呢这个就是TCP的阻塞。

滑动窗口协议
发送方和接收方都会维护一个数据帧的序列,这个序列被称作窗口。发送方的窗口大小由接收方确认,目的是控制发送速度,以免接收方的缓存不够大导致溢出,同时控制流量也可以避免网络拥塞

HTTP协议
URI是请求的资源,URL是你请求的资源的地址也就是地址路径。

UDP协议
那为什么UDP不可靠,我们还使用它了,在这种在线视频中,丢失数据只会作为干扰出现,并且这种干扰是可以容忍的,就比如看视频的时候出现了画面与声音不同步的现象,大家还是会忍受的,同时UDP传输比较高效

实战
TCP的实战,因为TCP是要建立连接的,所以需要Socket和ServerSocket之间建立连接。
TCP Server

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 socket = serverSocket.accept();
            //根据标准输入构造一个BufferedReader对象
            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 ");
            //写一些html的体
            writer.write("http请求

这是一个HTTP请求!

"
); //刷新输出流,使得数据立马发送 writer.flush(); //关闭 reader.close(); writer.close(); socket.close(); } } }

TCP Client

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创建一个PrintWriter进行写数据
        PrintWriter printWriter = new PrintWriter(socket.getOutputStream());
        //发送数据
        printWriter.println(msg);
        //刷新一下,使得服务立马可以收到请求信息
        printWriter.flush();
        printWriter.println(msg);
        printWriter.println(msg);
        printWriter.println(msg);
        printWriter.println(msg);

        //关闭资源
        printWriter.close();
        socket.close();
    }
}

UDP呢,首先UDP是没有任何两台主机之间连接的概念,它只管发给谁就可以了,TCP可以使用流数据,而UDP不行,UDP在处理的时候以一个包的形式进行发送,要么就发送到了,要么就丢失。

UDP Recive

package enjoy.protocol.udp;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
 * UDP接收端
 */
public class ReciveDemo {
    public static void main(String[] args)throws  Exception {
        //创建一个DatagramSocket实例,并且把实例绑定到本机的地址,端口10005
        DatagramSocket datagramSocket = new DatagramSocket(10005);
        byte bytes[] = new byte[1024];
        //以一个空数组来创建 DatagramPacket,这个对象作用是接收DatagramSocket中的数据
        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();

    }
}

UDP Send

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);
            if("88".equals(line)){//当输入88时结束发送
                break;
            }
        }
        //关闭
        datagramSocket.close();
    }
}

通讯协议 & 基于分布式思想下的RPC方案_第3张图片

分布式:

RPC可以提高系统稳定性,比如说,我们的订单服务程序更新出BUG,导致内存溢出,是这台服务器宕机了,但是它只会影响的整个系统的订单业务部分,对于用户注册登录等业务没有影响,同样对于系统的日志记录也没有影响。

RPC:

注册中心:服务端会把它的服务注册到注册中心中,包括服务名称、服务调用的ip地址、端口、协议、还有调用路径等等。

RMI:

RMI接口和实现类不灵活,RMI必须继承和实现Remote接口之类的。

rmi.1 继承 Remote 接口

import java.rmi.Remote;
import java.rmi.RemoteException;
/**
 * RMI 订单接口,继承Remote类
 */
public interface IOrder extends Remote{
    //付款的方法
    public String  pay(String id) throws RemoteException;
}

rmi.2 继承UnicastRemoteObject

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

/**
 * 接口实现类,继承UnicastRemoteObject
 */
public class OrderImpl extends UnicastRemoteObject implements  IOrder {
    protected OrderImpl() throws RemoteException{
        super();
    }
    @Override
    public  String pay(String id ) throws  RemoteException{
        //默认返回成功
        return "支付成功!商品订单号:"+id;
    }
}

rmi.3 服务提供方 1. LocateRegistry.createRegistry 2. Naming.bind

import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
/**
 * RMI--服务端
 */
public class Server {
    public static void main(String[] args)throws  Exception {
        //接口实例化
        IOrder iOrder = new OrderImpl();
        //本地的服务注册到6666端口中
        LocateRegistry.createRegistry(6666);
        //把刚才的实例绑定到本地端口上的一个路径上
        Naming.bind("rmi://localhost:6666/order",iOrder);
        System.out.println("服务器已经启动了!");
    }
}

rmi.4 服务消费方 Naming.lookup

import java.rmi.Naming;

/**
 * RMI-客户端
 */
public class Client {
    public static void main(String[] args)throws  Exception {
        //通过RMI发现服务并且转成一个对象
        IOrder iOrder = (IOrder)Naming.lookup("rmi://localhost:6666/order");
        //远程调用下
        System.out.println(iOrder.pay("168888"));
    }
}

自己实现一个RPC 框架

需要掌握Socket通讯、动态代理和反射、Java反序列化

反射

demo 类

public class Tech {
    //洗脚服务
    public boolean XJ(String name) {
        System.out.println("13号技师为("+name+")服务");
        return true;
    }
    @Override
    public String toString() {
        return "这是一名技师";
    }
}

反射使用 1. Class.forName 2. TechClazz.newInstance() 3. TechClazz.getMethods() 4. method.invoke

/**
 *类说明:演示反射的使用
 */
public class RefleDemo {

	public static void main(String[] args) throws ClassNotFoundException, 
	InstantiationException, IllegalAccessException {
	
        //通过全限定名拿到类的class对象
		Class TechClazz = Class.forName("cn.enjoyedu.refle.Tech");

        //通过class对象拿到类的实例
        Tech shapeInst = (Tech)TechClazz.newInstance();

        //通过class对象拿到方法列表
		Method[] methods = TechClazz.getMethods();
		for(Method method:methods) {
			System.out.println(method.getName());
			if(method.getName().equals("XJ")){//洗脚服务
				try {
				    //执行指定方法
					method.invoke(TechClazz.newInstance(),"king");
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

动态代理:

动态代理,两个概念,一个是代理,一个是动态。

代理的本质就是代理模式,代理模式一定要有这三个要素:接口,提供服务的真实对象,代理对象。

三个要素之外还有重要的两个动作,

首先是真实对象以及代理的对象都必须同时继承一个指定的接口。
第二个,这个代理对象必须包含这个真实的对象。
第一个小伙伴,Proxy, 它就是一个调度器,这个是专门调度人.
第二个小伙伴就是Invocationhandler,这个是一个增强器,专门做增强的.
Invocationhandler的源码,这个代码就只有一个方法,我们把这个方法实现了就可以了。

为什么在RPC中用动态代理增强?-----增强了网络远程调用功能。

interface

public interface Girl {
	void date();
	void watchMovie();
}

implements

public class WangMeiLi implements Girl {
	@Override
	public void date() {
		System.out.println("王美丽说:跟你约会好开心啊");
		this.watchMovie();
	}
	@Override
	public void watchMovie() {
		System.out.println("王美丽说:这个电影我不喜欢看");
	}
}

Proxy.newProxyInstance & InvocationHandler invoke

public class WangMeiLiProxy implements InvocationHandler {
	private Girl gilr;
	public WangMeiLiProxy(Girl gilr) {
		super();
		this.gilr = gilr;
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		//事前
		doSomeThingBefore();
		Object ret = method.invoke(gilr, args);
		//事后
		doSomeThingEnd();
		return ret;
	}
	private void doSomeThingBefore(){
		System.out.println("王美丽的父母说:我得先调查下这个男孩子的背景!");
	}
	private void doSomeThingEnd(){
		System.out.println("王美丽的父母说:他有没有对你动手动脚啊?");
	}
	//代理:调度
	public Object getProxyInstance(){
		return Proxy.newProxyInstance(gilr.getClass().getClassLoader(), gilr.getClass().getInterfaces(), this);
	}
}

调用

public class King {
	public static void main(String[] args) {
		//隔壁有个女孩,叫王美丽
		Girl girl = new WangMeiLi();
		//他有个庞大的家庭,想要跟她约会必须征得她家里人的同意
		WangMeiLiProxy   family = new WangMeiLiProxy(girl);
		//有一次我去约王美丽,碰到了她的妈妈,我征得了她妈妈的同意
		Girl mother = (Girl) family.getProxyInstance();
		//通过她的妈妈这个代理才能与王美丽约会
		mother.date();
	}
}
Bio通讯

Bio 服务端 new Thread(new ServerTask(serverSocket.accept())).start()

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 *类说明:Bio通信的服务端
 */
public class Server {

    public static void main(String[] args) throws IOException {
        //服务端启动必备
        ServerSocket serverSocket = new ServerSocket();
        //表示服务端在哪个端口上监听
        serverSocket.bind(new InetSocketAddress(10001));
        System.out.println("Start Server ....");
        try{
            while(true){
                new Thread(new ServerTask(serverSocket.accept())).start();
            }
        }finally {
            serverSocket.close();
        }
    }

    //每个和客户端的通信都会打包成一个任务,交个一个线程来执行
    private static class ServerTask implements Runnable{

        private Socket socket = null;
        public ServerTask(Socket socket){
            this.socket = socket;
        }

        @Override
        public void run() {
            //实例化与客户端通信的输入输出流
            try(ObjectInputStream inputStream =
                    new ObjectInputStream(socket.getInputStream());
                ObjectOutputStream outputStream =
                    new ObjectOutputStream(socket.getOutputStream())){

                //接收客户端的输出,也就是服务器的输入
                String userName = inputStream.readUTF();
                System.out.println("Accept client message:"+userName);

                //服务器的输出,也就是客户端的输入
                outputStream.writeUTF("Hello,"+userName);
                outputStream.flush();
            }catch(Exception e){
                e.printStackTrace();
            }finally {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Bio 客户端 socket.connect(inetSocketAddress)


import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;

/**
 *类说明:Bio通信的客户端
 */
public class Client {

    public static void main(String[] args) throws IOException {
        //客户端启动必备
        Socket socket = null;
        //实例化与服务端通信的输入输出流
        ObjectOutputStream output = null;
        ObjectInputStream input = null;
        //服务器的通信地址
        InetSocketAddress addr = new InetSocketAddress("127.0.0.1",10001);

        try{
            socket = new Socket();
            socket.connect(addr);//连接服务器

            output = new ObjectOutputStream(socket.getOutputStream());
            input = new ObjectInputStream(socket.getInputStream());

            /*向服务器输出请求*/
            output.writeUTF("Mark");
            output.flush();

            //接收服务器的输出
            System.out.println(input.readUTF());
        }finally{
            if (socket!=null) socket.close();
            if (output!=null) output.close();
            if (input!=null) input.close();
        }
    }
}

ObjectOutputStream和ObjectInputStream结合Socket通讯就是我们实现的网络增强部分的组成.

Java实现:

1.服务端定义接口和服务实现类并且注册服务
2.客户端查询出服务
3.客户端使用动态代理调用服务(动态代理)
4.客户端代理把调用对象、方法、参数序列化成数据
5.客户端与服务端通过Socket通讯传输数据
6.服务端反序列化数据成对象、方法、参数。
7.服务端代理拿到这些对象和参数后通过反射的机制调用服务的实例。

手撸服务提供方
通讯协议 & 基于分布式思想下的RPC方案_第4张图片
服务注册中心


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,由线程池执行,并且同时将socket送入(server.accept()=socket)
                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
        Socket clent = null;

        public ServiceTask(Socket client) {
            this.clent = client;
        }
        //远程请求达到服务端,我们需要执行请求结果,并且把请求结果反馈至客户端,使用Socket通讯
        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<?>[] paramTypes = ( Class<?>[])inputStream.readObject();
                //拿到参数值
                Object[] arguments = (Object[])inputStream.readObject();
                //要到注册中心根据 接口名,获取实现类
                Class serviceClass =serviceRegistry.get(serviceName);
                //使用反射的机制进行调用
                Method method = serviceClass.getMethod(methodName,paramTypes);
                //反射调用方法,把结果拿到
                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();
            }

        }

    }
}

rpc的服务端,提供服务

/**
 *类说明:rpc的服务端,提供服务
 */
public class Server {
    public static void main(String[] args) throws  Exception{
        new Thread(new Runnable() {
            public void run() {
                try {
                    //起一个服务中心
                    RegisterCenter serviceServer = new RegisterCenter(8888);
                    //注册技师对象至注册中心
                    serviceServer.register(TechInterface.class, TechImpl.class);
                    //运行我们的服务
                    serviceServer.start();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

手撸服务消费方
通讯协议 & 基于分布式思想下的RPC方案_第5张图片

rpc框架的客户端代理部分

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;

/**
 *类说明:rpc框架的客户端代理部分
 */
public class RpcClientFrame {

    /*远程服务的代理对象,参数为客户端要调用的的服务*/
    public static <T> T getRemoteProxyObj(final Class<?> serviceInterface)
            throws Exception {
        // 默认端口8888
        InetSocketAddress serviceAddr = new InetSocketAddress("127.0.0.1",8888);
        // 1.将本地的接口调用转换成JDK的动态代理,在动态代理中实现接口的远程调用
        //进行实际的服务调用(动态代理)
        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;
        }

        /*动态代理类,增强:实现了对远程服务的访问*/
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            /* 网络增强部分*/
            Socket socket = null;
            //因为传递的大部分是 方法、参数,所以我们使用Object流对象
            ObjectInputStream objectInputStream = null;
            ObjectOutputStream objectOutputStream = null;
            try {
                //新建一个Socket
                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());
                //发送 参数的值,使用UTF-8避免乱码
                objectOutputStream.writeObject(args);
                //刷新缓冲区,使得数据立马发送
                objectOutputStream.flush();
                //立马拿到远程执行的结果
                objectInputStream = new ObjectInputStream(socket.getInputStream());
                //我们要把调用的细节打印出来
                System.out.println("远程调用成功!" + serviceInterface.getName());
                //最后要网络的请求返回给返回
                return objectInputStream.readObject();
            } finally {

                //最后记得关闭
                socket.close();
                objectOutputStream.close();
                objectInputStream.close();

            }
        }
    }
}

rpc的客户端,调用远端服务

/**
 *类说明:rpc的客户端,调用远端服务
 */
public class Client {
    public static void main(String[] args) throws Exception {
        //动态代理获取我们的对象
        TechInterface techInterface = RpcClientFrame.getRemoteProxyObj(TechInterface.class);
        //进远程调用我们的对象
        System.out.println(techInterface.XJ("king"));

    }
}

你可能感兴趣的:(java,分布式,RPC)