RPC(Remote Procedure Call)
远程方法调用,这只是个统称,重点在于方法调用
RPC只是个概念,不是什么框架,协议,只是说远程调用的一种方式,是多种概念中的一种
从单机到分布式—》需要使用分布式通信—》最基本的传输:二进制数据传输TCP/IP
User:实体类,存放用户信息
IUserService:业务接口
UserServiceImpl:业务实现类
Client:客户
Server:服务
客户与服务关系:服务间的方法调用
xxxOutputStream与xxxInputStream的注意点
先放入的先取,不然会乱
客户端
Socket s = new Socket(“127.0.0.1”, 8888);
ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
oos.writeUTF(xxx); <—
oos.writeObject(yyy); <—服务端
InputStream in = s.getInputStream();
ObjectInputStream ois = new ObjectInputStream(in);
xxx = ois.readUTF(); <—
yyy = (Class[])ois.readObject(); <—
公共的
/**
* 业务接口
*/
public interface IUserService {
public User findUserById(Integer id);
}
/**
* 实体类
*/
@Setter@Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
}
第一个版本
/**
* 客户
*/
public class Client {
public static void main(String[] args) throws IOException {
// 在这里主机是指远程机器名称或者IP地址,端口是指远程应用的端口号
Socket s = new Socket("127.0.0.1", 8888);
System.out.println("时间:" + System.currentTimeMillis() +" 连接服务端");
// 字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 将Java基本数据类型写到底层输出流
DataOutputStream dos = new DataOutputStream(baos);
// 往缓冲区写入数据
dos.writeInt(123);
// 把字节缓冲区字节数组写出去
s.getOutputStream().write(baos.toByteArray());
s.getOutputStream().flush();
System.out.println("时间:" + System.currentTimeMillis() +" 数据已发送到服务端");
// 获取返回的字节
DataInputStream dis = new DataInputStream(s.getInputStream());
int id = dis.readInt();
String name = dis.readUTF();
System.out.println("时间:" + System.currentTimeMillis() +" 从服务端返回结果");
User user = new User(id, name);
System.out.println("时间:" + System.currentTimeMillis() +" " + user);
dos.close();
s.close();
}
}
/**
* 服务
*/
public class Server {
private static boolean running = true;
/**
* 服务器建立通信,监听连接
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
// 服务器建立通信ServerSocket
ServerSocket ss = new ServerSocket(8888);
while (running){
// 监听访问服务器的客户端,返回Socket对象(客户端)
Socket s = ss.accept();
System.out.println("时间:" + System.currentTimeMillis() +" 客户端访问");
process(s);
s.close();
}
}
/**
* 获取客户端发送过来的二进制id,转成java数据类型,
* 根据id查询用户信息,把用户信息转成二进制数据,往客户端发送
* @param s
* @throws IOException
*/
private static void process(Socket s) throws IOException {
// 获取客户端的字节输入流
InputStream in = s.getInputStream();
// 获取客户端的字节输出流
OutputStream out = s.getOutputStream();
// 从底层输入流中读取基本 Java 数据类型
DataInputStream dis = new DataInputStream(in);
// 将 Java 基本数据类型写入底层输出流
DataOutputStream dos = new DataOutputStream(out);
// 获取Int类型的数据
int id = dis.readInt();
IUserService service = new UserServiceImpl();
// 模拟调用业务方法,根据id查询用户信息
User user = service.findUserById(id);
System.out.println("时间:" + System.currentTimeMillis() +" 查询的结果:" + user);
System.out.println("时间:" + System.currentTimeMillis() +" 将信息返回给用户端");
// 把信息写入字节输出流中,用于往客户端传输
dos.writeInt(user.getId());
dos.writeUTF(user.getName());
// 用于刷新此流,并强制将字节写入基本流
dos.flush();
}
}
/**
* 服务,相当于service层
*/
public class UserServiceImpl implements IUserService {
public User findUserById(Integer id) {
// 模拟从数据库中查询数据
return new User(id,"hao");
}
}
Client
时间:1611390201028 连接服务端
时间:1611390201057 数据已发送到服务端
时间:1611390201091 从服务端拿到结果
时间:1611390201092 User(id=123, name=hao)
Server
时间:1611390201028 客户端访问
时间:1611390201059 查询的结构:User(id=123, name=hao)
时间:1611390201090 将信息返回给用户端
第二个版本
把客户那边的代码抽取出来,放到Stub类中,用于屏蔽网络细节
其余代码不变
/**
* 客户
*/
public class Client {
public static void main(String[] args) throws IOException {
Stub stub = new Stub();
User user = stub.findUserById(123);
System.out.println("时间:" + System.currentTimeMillis() +" " + user);
}
}
/**
* 代理类,RPC最最最最最初期
*/
public class Stub {
public User findUserById(Integer id) throws IOException {
// 在这里主机是指远程机器名称或者IP地址,端口是指远程应用的端口号
Socket s = new Socket("127.0.0.1", 8888);
System.out.println("时间:" + System.currentTimeMillis() +" 连接服务端");
// 字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 将Java基本数据类型写到底层输出流
DataOutputStream dos = new DataOutputStream(baos);
// 往缓冲区写入数据
dos.writeInt(123);
// 把字节缓冲区字节数组写出去
s.getOutputStream().write(baos.toByteArray());
s.getOutputStream().flush();
System.out.println("时间:" + System.currentTimeMillis() +" 数据已发送到服务端");
// 获取返回的字节
DataInputStream dis = new DataInputStream(s.getInputStream());
int receivedId = dis.readInt();
String name = dis.readUTF();
System.out.println("时间:" + System.currentTimeMillis() +" 从服务端拿到结果");
User user = new User(receivedId, name);
dos.close();
s.close();
return user;
}
}
Client
时间:1611391197261 连接服务端
时间:1611391197292 数据已发送到服务端
时间:1611391197321 从服务端拿到结果
时间:1611391197323 User(id=123, name=hao)
Server
时间:1611391197260 客户端访问
时间:1611391197293 查询的结构:User(id=123, name=hao)
时间:1611391197320 将信息返回给用户端
第三个版本
把Stub类改造成JDK动态代理,修改客户调用代码,其余代码不变
/**
* 客户
*/
public class Client {
public static void main(String[] args) {
IUserService service = Stub.getStub();
User user = service.findUserById(123);
System.out.println("时间:" + System.currentTimeMillis() +" " + user);
}
}
/**
* JDK动态代理
*/
public class Stub {
public static IUserService getStub(){
InvocationHandler h = new InvocationHandler() {
/**
* @param proxy:代理对象是谁
* @param method:被调用了哪个方法
* @param args:传过来的参数是什么
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在这里主机是指远程机器名称或者IP地址,端口是指远程应用的端口号
Socket s = new Socket("127.0.0.1", 8888);
System.out.println("时间:" + System.currentTimeMillis() +" 连接服务端");
// 字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 将Java基本数据类型写到底层输出流
DataOutputStream dos = new DataOutputStream(baos);
// 往缓冲区写入数据
dos.writeInt(123);
// 把字节缓冲区字节数组写出去
s.getOutputStream().write(baos.toByteArray());
s.getOutputStream().flush();
System.out.println("时间:" + System.currentTimeMillis() +" 数据已发送到服务端");
// 获取返回的字节
DataInputStream dis = new DataInputStream(s.getInputStream());
int receivedId = dis.readInt();
String name = dis.readUTF();
System.out.println("时间:" + System.currentTimeMillis() +" 从服务端拿到结果");
User user = new User(receivedId, name);
dos.close();
s.close();
return user;
}
};
/**
* 参数一:加载代理类的类加载器
* 参数二:被代理类实现的接口
* 参数三:InvocationHandler
*/
Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(),new Class[]{IUserService.class},h);
return (IUserService)o;
}
}
Client
时间:1611393079568 连接服务端
时间:1611393079599 数据已发送到服务端
时间:1611393079629 从服务端拿到结果
时间:1611393079630 User(id=123, name=hao)
Server
时间:1611393079569 客户端访问
时间:1611393079600 查询的结构:User(id=123, name=hao)
时间:1611393079628 将信息返回给用户端
第四个版本
修改了动态代理中代码,使用反射获取方法信息,实现动态请求方法和传递参数
修改Stub、Server代码,其余不变
/**
* JDK动态代理
*/
public class Stub {
public static IUserService getStub(){
InvocationHandler h = new InvocationHandler() {
/**
* @param proxy:代理对象是谁
* @param method:被调用了哪个方法
* @param args:传过来的参数是什么
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在这里主机是指远程机器名称或者IP地址,端口是指远程应用的端口号
Socket s = new Socket("127.0.0.1", 8888);
System.out.println("时间:" + System.currentTimeMillis() +" 连接服务端");
// 该流可以将一个对象写出,或者读取一个对象到程序中,也就是执行了序列化和反序列化操作
ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
// 获取被调用方法的名字
String methodName = method.getName();
// 获取被调用方法的参数类型
Class<?>[] parameterTypes = method.getParameterTypes();
System.out.println("时间:" + System.currentTimeMillis() +" 发送数据");
oos.writeUTF(methodName);
oos.writeObject(parameterTypes);
oos.writeObject(args);
oos.flush();
// 获取返回的字节
DataInputStream dis = new DataInputStream(s.getInputStream());
int receivedId = dis.readInt();
String name = dis.readUTF();
System.out.println("时间:" + System.currentTimeMillis() +" 从服务端拿到结果");
User user = new User(receivedId, name);
oos.close();
s.close();
return user;
}
};
/**
* 参数一:加载代理类的类加载器
* 参数二:被代理类实现的接口
* 参数三:InvocationHandler
*/
Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(),new Class[]{IUserService.class},h);
return (IUserService)o;
}
}
/**
* 服务
*/
public class Server {
private static boolean running = true;
/**
* 服务器建立通信,监听连接
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
// 服务器建立通信ServerSocket
ServerSocket ss = new ServerSocket(8888);
while (running){
// 监听访问服务器的客户端,返回Socket对象(客户端)
Socket s = ss.accept();
System.out.println("时间:" + System.currentTimeMillis() +" 客户端访问");
process(s);
s.close();
}
}
/**
* 获取客户端发送过来的二进制id,转成java数据类型,
* 根据id查询用户信息,把用户信息转成二进制数据,往客户端发送
* @param s
* @throws IOException
*/
private static void process(Socket s) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 获取客户端的字节输入流
InputStream in = s.getInputStream();
// 获取客户端的字节输出流
OutputStream out = s.getOutputStream();
// 从底层输入流中读取 Java 对象类型
ObjectInputStream oos = new ObjectInputStream(in);
// 将 Java 基本数据类型写入底层输出流
DataOutputStream dos = new DataOutputStream(out);
// 获取被调用方法的名字
String methodName = oos.readUTF();
// 获取被调用方法的参数类型集合
Class[] parameterTypes = (Class[])oos.readObject();
// 获取被调用方法的参数集合
Object[] args = (Object[]) oos.readObject();
IUserService service = new UserServiceImpl();
// 根据名字,参数类型获取具体的方法对象
Method method = service.getClass().getMethod(methodName,parameterTypes);
// 调用方法,传入被代理类对象,具体参数
User user = (User) method.invoke(service,args);
System.out.println("时间:" + System.currentTimeMillis() +" 查询的结果:" + user);
System.out.println("时间:" + System.currentTimeMillis() +" 将信息返回给用户端");
// 把信息写入字节输出流中,用于往客户端传输
dos.writeInt(user.getId());
dos.writeUTF(user.getName());
// 用于刷新此流,并强制将字节写入基本流
dos.flush();
}
}
Client
时间:1611395269591 连接服务端
时间:1611395269625 发送数据
时间:1611395269685 从服务端拿到结果
时间:1611395269686 User(id=123, name=hao)
Server
时间:1611395269591 客户端访问
时间:1611395269656 查询的结构:User(id=123, name=hao)
时间:1611395269685 将信息返回给用户端
第五个版本
Stub从服务中,从ObjectInputStream
中获取返回值
Server服务中,往ObjectOutputStream
中获放入返回值
其余代码不变
/**
* JDK动态代理
*/
public class Stub {
public static IUserService getStub(){
InvocationHandler h = new InvocationHandler() {
/**
* @param proxy:代理对象是谁
* @param method:被调用了哪个方法
* @param args:传过来的参数是什么
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在这里主机是指远程机器名称或者IP地址,端口是指远程应用的端口号
Socket s = new Socket("127.0.0.1", 8888);
System.out.println("时间:" + System.currentTimeMillis() +" 连接服务端");
// 该流可以将一个对象写出,或者读取一个对象到程序中,也就是执行了序列化和反序列化操作
ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
// 获取被调用方法的名字
String methodName = method.getName();
// 获取被调用方法的参数类型
Class<?>[] parameterTypes = method.getParameterTypes();
System.out.println("时间:" + System.currentTimeMillis() +" 发送数据");
oos.writeUTF(methodName);
oos.writeObject(parameterTypes);
oos.writeObject(args);
oos.flush();
// 获取返回的字节
ObjectInputStream ois = new ObjectInputStream(s.getInputStream());
User user = (User) ois.readObject();
System.out.println("时间:" + System.currentTimeMillis() +" 从服务端拿到结果");
oos.close();
s.close();
return user;
}
};
/**
* 参数一:加载代理类的类加载器
* 参数二:被代理类实现的接口
* 参数三:InvocationHandler
*/
Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(),new Class[]{IUserService.class},h);
return (IUserService)o;
}
}
/**
* 服务
*/
public class Server {
private static boolean running = true;
/**
* 服务器建立通信,监听连接
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
// 服务器建立通信ServerSocket
ServerSocket ss = new ServerSocket(8888);
while (running){
// 监听访问服务器的客户端,返回Socket对象(客户端)
Socket s = ss.accept();
System.out.println("时间:" + System.currentTimeMillis() +" 客户端访问");
process(s);
s.close();
}
}
/**
* 获取客户端发送过来的二进制id,转成java数据类型,
* 根据id查询用户信息,把用户信息转成二进制数据,往客户端发送
* @param s
* @throws IOException
*/
private static void process(Socket s) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 获取客户端的字节输入流
InputStream in = s.getInputStream();
// 获取客户端的字节输出流
OutputStream out = s.getOutputStream();
// 从底层输入流中读取 Java 对象类型
ObjectInputStream ois = new ObjectInputStream(in);
// 将 Java 基本数据类型写入底层输出流
DataOutputStream dos = new DataOutputStream(out);
// 获取被调用方法的名字
String methodName = ois.readUTF();
// 获取被调用方法的参数类型集合
Class[] parameterTypes = (Class[])ois.readObject();
// 获取被调用方法的参数集合
Object[] args = (Object[]) ois.readObject();
IUserService service = new UserServiceImpl();
// 根据名字,参数类型获取具体的方法对象
Method method = service.getClass().getMethod(methodName,parameterTypes);
// 调用方法,传入被代理类对象,具体参数
User user = (User) method.invoke(service,args);
System.out.println("时间:" + System.currentTimeMillis() +" 查询的结果:" + user);
System.out.println("时间:" + System.currentTimeMillis() +" 将信息返回给用户端");
// 把信息写入字节输出流中,用于往客户端传输
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(user);
// 用于刷新此流,并强制将字节写入基本流
dos.flush();
}
}
Client
时间:1611405600697 连接服务端
时间:1611405600727 发送数据
时间:1611405600788 从服务端拿到结果
时间:1611405600788 User(id=123, name=hao)
Server
时间:1611405600698 客户端访问
时间:1611405600751 查询的结构:User(id=123, name=hao)
时间:1611405600784 将信息返回给用户端
第六个版本
完成这就是RPC的一个小小实现了
修改为调用指定类的指定方法
- 客户传入字节码对象,Stub中根据对象获取类名称(服务名)
- 在Server中根据服务名,在服务注册表找到具体的类
- 调用类中的方法
/**
* 客户
*/
public class Client {
public static void main(String[] args) {
IUserService service = (IUserService) Stub.getStub(IUserService.class);
User user = service.findUserById(123);
System.out.println("时间:" + System.currentTimeMillis() +" " + user);
}
}
/**
* JDK动态代理
*/
public class Stub {
public static Object getStub(Class clazz){
InvocationHandler h = new InvocationHandler() {
/**
* @param proxy:代理对象是谁
* @param method:被调用了哪个方法
* @param args:传过来的参数是什么
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在这里主机是指远程机器名称或者IP地址,端口是指远程应用的端口号
Socket s = new Socket("127.0.0.1", 8888);
System.out.println("时间:" + System.currentTimeMillis() +" 连接服务端");
// 该流可以将一个对象写出,或者读取一个对象到程序中,也就是执行了序列化和反序列化操作
ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
// 获取class的名字
String clazzName = clazz.getName();
// 获取被调用方法的名字
String methodName = method.getName();
// 获取被调用方法的参数类型
Class<?>[] parameterTypes = method.getParameterTypes();
System.out.println("时间:" + System.currentTimeMillis() +" 发送数据");
oos.writeUTF(clazzName);
oos.writeUTF(methodName);
oos.writeObject(parameterTypes);
oos.writeObject(args);
oos.flush();
// 获取返回的字节
ObjectInputStream ois = new ObjectInputStream(s.getInputStream());
User user = (User) ois.readObject();
System.out.println("时间:" + System.currentTimeMillis() +" 从服务端拿到结果");
oos.close();
s.close();
return user;
}
};
/**
* 参数一:加载代理类的类加载器
* 参数二:被代理类实现的接口
* 参数三:InvocationHandler
*/
Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(),new Class[]{IUserService.class},h);
return (IUserService)o;
}
}
/**
* 服务
*/
public class Server {
private static boolean running = true;
/**
* 服务器建立通信,监听连接
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 服务器建立通信ServerSocket
ServerSocket ss = new ServerSocket(8888);
while (running){
// 监听访问服务器的客户端,返回Socket对象(客户端)
Socket s = ss.accept();
System.out.println("时间:" + System.currentTimeMillis() +" 客户端访问");
process(s);
s.close();
}
}
/**
* 获取客户端发送过来的二进制id,转成java数据类型,
* 根据id查询用户信息,把用户信息转成二进制数据,往客户端发送
* @param s
* @throws IOException
*/
private static void process(Socket s) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
// 获取客户端的字节输入流
InputStream in = s.getInputStream();
// 获取客户端的字节输出流
OutputStream out = s.getOutputStream();
// 从底层输入流中读取 Java 对象类型
ObjectInputStream ois = new ObjectInputStream(in);
// 将 Java 基本数据类型写入底层输出流
DataOutputStream dos = new DataOutputStream(out);
// 获取clazzName
String clazzName = ois.readUTF();
// 获取被调用方法的名字
String methodName = ois.readUTF();
// 获取被调用方法的参数类型集合
Class[] parameterTypes = (Class[])ois.readObject();
// 获取被调用方法的参数集合
Object[] args = (Object[]) ois.readObject();
Class clazz = null;
// 根据服务名字(clazzName),从服务注册表找到具体的类
// 模拟找到
clazz = UserServiceImpl.class;
// 根据名字,参数类型获取具体的方法对象
Method method = clazz.getMethod(methodName,parameterTypes);
// 调用方法,传入代理类对象,具体参数
Object o = (Object) method.invoke(clazz.newInstance(),args);
System.out.println("时间:" + System.currentTimeMillis() +" 查询的结果:" + o);
System.out.println("时间:" + System.currentTimeMillis() +" 将信息返回给用户端");
// 把信息写入字节输出流中,用于往客户端传输
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(o);
// 用于刷新此流,并强制将字节写入基本流
dos.flush();
}
}
Client
时间:1611408464339 连接服务端
时间:1611408464381 发送数据
时间:1611408464447 从服务端拿到结果
时间:1611408464448 User(id=123, name=hao)
Server
时间:1611408464340 客户端访问
时间:1611408464409 查询的结果:User(id=123, name=hao)
时间:1611408464440 将信息返回给用户端
第七个版本
序列化、网络协议