在分布式系统中,不同节点之间需要进行通信来实现一致性,例如:在投票选举阶段,候选者需要为所有其他节点发送拉票请求,拉票请求中包含着自己的网络地址和任期号,也就是说,我们需要发送一个拉票请求的对象,网络地址和任期号为成员变量。那么,对象是如何在网络中传输的呢?
  首先,为了保证数据的传输稳定,节点间通信应使用可靠的 TCP/IP 协议连结。TCP协议是基于字节流的通信协议,当我们传输数据时,就需要把数据转换成byte的形式进行传输,例如,我们要传输一个int类型的数据,我们需要把int类型的数据拆成4个byte数据,通过输出流传输,接收端再把四个byte数据合并成一个int数据,实现传输。我们要传输的Student对象,本质上也就是传输四个成员变量,我们只需要找到这四个成员变量转换成byte数据的方法就可以了。

下面我们通过定义原始的字节流协议来实现一下对象的传输:

Student类

首先定义我们要传输的Student类,这里定义了四种类型的成员变量,分别是int、long、String、Image,然后定义构造函数。为了方便测试,我们还要重写toString方法。

package RPC.v1.myTest03;

import java.awt.*;

public class Student {
    int id; 
    String name; 
    long liveing; 
    Image image;  

    public Student(int id, String name, long liveing, Image image) {
        this.id = id;
        this.name = name;
        this.liveing = liveing;
        this.image = image;
    }

    public Student() {
    }
    
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", liveing=" + liveing +
                ", image=" + image +
                '}';
    }
}

客户端

int,long,String数据都有自己的转变为byte数据的方法:int可以拆成4个字节,long可以拆成8个字节,String可以调用getByte()方法,但是Image应该怎么传输呢?其实,Image也是一个类,图片类的本质上是一个二维数组,所以我们实际上需要传输的数据是一个二维数组。
代码中用到了DataOutputStream类,它是OutputStream的子类,具有对int,long等数据传输的封装方法。

package RPC.v1.myTest03;

import com.sun.xml.internal.ws.resources.UtilMessages;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.Socket;

import static java.lang.Thread.sleep;

public class Client {
    public static void main(String[] args) {
        try {
            Socket client = new Socket("127.0.0.1",9999);
            System.out.println("已连接到服务端端口9999...");
            OutputStream ous = client.getOutputStream();
            InputStream ins = client.getInputStream();
            BufferedImage img = ImageIO.read(new File("src/RPC/v1/PNG/test.png"));

            Student stu = new Student(1,"李天路",2100000000,img);

            System.out.println(stu);

            while(true){
                long start = System.currentTimeMillis();
                writeStudent(ous,stu);
                System.out.println("成功发送对象stu: " + stu);
                stu.id ++;
                long end =System.currentTimeMillis();
                System.out.println("花费时间为:"+ (end-start));
                sleep(100000);
            }

        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
    /**
    * 传输Student类
    */
    public static void writeStudent(OutputStream ous, Student stu){
        DataOutputStream dous = new DataOutputStream(ous);
        try {
            dous.writeByte(1); //代表接下来的数据是Student类型
            //int
            dous.writeInt(stu.id);
            //long
            dous.writeLong(stu.liveing);
            //String
            byte[] nameBytes = stu.name.getBytes();
            dous.writeByte(nameBytes.length);
            dous.write(nameBytes);
            //Image
            writeImage(dous, stu.image);
            dous.flush();


        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    /**
    * Image本质上是一个二维数组,所以我们把二维数组传过去,对面再把二维数组转化成Image类就可以了
    * 实际操作: 图片转二维数组,传送二维数组
    */
    public static  void writeImage(DataOutputStream dous, Image image){
        BufferedImage buffimg = (BufferedImage)image;
        int w = buffimg.getWidth();
        int h = buffimg.getHeight();
        try {
            dous.writeInt(w);
            dous.writeInt(h);
            for (int i = 0; i < w; i++) {
                for (int j = 0; j < h; j++) {
                    dous.writeInt(buffimg.getRGB(i,j));
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

服务端

对应客户端的输入逐个读取数据并放在合适的位置

package RPC.v1.myTest03;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) {

        try {
            ServerSocket serverSocket = new ServerSocket(9999);
            System.out.println("端口9999正在监听中。。。");
            Socket client = serverSocket.accept();
            System.out.println("客户端端口"+client.getPort()+"已经连接");
            OutputStream ous = client.getOutputStream();
            InputStream ins = client.getInputStream();
            while(true){
                Student stu = readStudent(ins);
                System.out.println("收到来自"+client.getPort()+"的stu对象:" + stu );

            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
    * 对应客户端的输入逐个读取数据并放在合适的位置
    */
    public static Student readStudent(InputStream ins){
        DataInputStream dins = new DataInputStream(ins);
        try {
            byte type = dins.readByte();
            if(type == 1){
                Student stu = new Student();
                stu.id = dins.readInt();
                stu.liveing = dins.readLong();
                Byte nameLength = dins.readByte();
                byte[] name = new byte[nameLength];
                dins.read(name);
                stu.name = new String(name);
                stu.image = readImage(dins);
                return stu;
            }


        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }
     /**
      * 为了判断传输图片是否成功,使用ImageIO.write()方法,将图片输出
      * 这里方法需要提前在目标文件夹下,新建一个test2.png文件,才能将图片数据写入。
      */
    public static Image readImage(DataInputStream dins ){
        try {
            int w = dins.readInt();
            int h = dins.readInt();

            BufferedImage image = new BufferedImage(w,h,BufferedImage.TYPE_INT_RGB);

            for (int i = 0; i < w; i++) {
                for (int j = 0; j < h; j++) {
                    int value = dins.readInt();
                    image.setRGB(i,j,value);
                }
            }

            boolean flag = ImageIO.write(image,"png",new File("src/RPC/v1/PNG/test2.png"));
            System.out.println("Image转化为png图片结果为:" + flag + " 请查看!");

            return image;

        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;

    }
}


实验结果:

客户端输出:

服务端输出:

在图片输出位置查看图片是否传输成功: