RPC框架-2
一、新增功能点
1、增加注册中心,维护各个服务器提供的服务以及服务的活跃状态,具备为服务器提供注册服务功能,为客户端提供查询服务的功能以及检测服务是否活跃功能
2、服务器可以在运行过程中注册新服务
3、客户端通过注册中心查找需要调用的服务在哪台服务器上,再远程过程调用服务器
二、客户端
1、增加查找服务功能:
连接注册中心通过其查找功能获取服务所在的ip地址和端口号,若服务不存在,或者目的服务器已停(状态为false),返回提示信息
2、在每个实现类连接服务器前,先通过查找服务功能获取服务所在的ip地址和端口号
client.java
客户端增加静态的findServer方法供实现类调用,参数为远程调用的方法名,返回值为ip和端口号或者提示信息
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
public class Client {
private final static String RS_IP ="localhost";
private final static int RS_PORT = 12345;
public static void main(String[] args) {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new HelloWorldImp().sayHello("AJava");
String res = new CallImp().call("张伟");
System.out.println(res);
System.out.println(new FindJobImp().findJob("chenyin"));
}
}
public static String findServer(String service ) throws IOException {
try(Socket s = new Socket(RS_IP ,RS_PORT);
ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
ObjectInputStream ois = new ObjectInputStream(s.getInputStream());
){
oos.writeUTF("findServer");
oos.writeUTF(service);
oos.flush();
String res = ois.readUTF();
System.out.println(res);
return res ;
}
}
}
HelloWordImpl.java
客户端实现类代码,连接服务器前调用findServer方法并解析结果
package client;
import client.lib.HelloWorld;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
// 客户端实现
// 客户端 自己不会有具体的实现逻辑 需要服务端上的方法来执行
public class HelloWorldImp implements HelloWorld {
String ip ;
int port ;
@Override
public String sayHello(String name) {
//首先获取服务所在服务器的ip地址和监听端口号
try {
String res = Client.findServer("sayHello");
if(res.equals("该服务不存在或服务器未启动")){
return res;
}else{
String [] ip_port = res.split(" ");
ip = ip_port[0];
port = Integer.parseInt(ip_port[1]);
}
} catch (IOException ioException) {
ioException.printStackTrace();
}
String response = null;
if(ip != null && port != 0){
try (Socket socket = new Socket("localhost", 1234);
ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream input = new ObjectInputStream(socket.getInputStream())) {
output.writeUTF("RPCRequest");
output.writeUTF("sayHello");
output.writeUTF(name);
output.flush();
response = input.readUTF();
System.out.println(response);
} catch (IOException e) {
e.printStackTrace();
}
}
return response;
}
}
二、服务器
1、增加注册服务功能:连接注册中心,发送方法名、ip地址和端口号给注册中心。注册服务功能需要页面,在页面中输入方法名和实现类全名进行注册。且注册同时需要更新methods.txt文件和map。避免手动维护文件或者重新扫描文件。
2、处理注册中心请求:由于注册中心需要启动线程每隔一定时间发送请求连接服务器以检测服务是否活跃,因此服务器还需要处理注册中心的连接请求,connect指令为处理注册中心发送的连接请求,RPCRequest为处理客户端发送的远程调用请求
Server.java
package com.rpc.server;
import com.rpc.server.lib.RpcInterface;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
public class Server extends JFrame {
private static final String RS_IP = "localhost";
private static final int RS_PORT = 12345 ;
private String filePath = "RPCServerV2/config/methods.txt";
private String IP ="127.0.0.1";
private static final int PORT = 1234;
HashMap<String, RpcInterface> methodMap = new HashMap<>();
public void setIP(String IP) {
this.IP = IP;
}
public Server(){
try {
init();
setTitle("服务器注册服务");
setSize(600,400);
JLabel methodName = new JLabel("方法名:");
JTextField methodT = new JTextField(50);
JLabel className = new JLabel("类全名:");
JTextField classT = new JTextField(50);
JButton registBtn = new JButton("注册服务");
add(methodName);
add(methodT);
add(className);
add(classT);
add(registBtn);
registBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String methodName = methodT.getText();
System.out.println("methodName:"+methodName);
String className = classT.getText();
System.out.println("className:"+className);
register(methodName , className);
}
});
setLayout(new FlowLayout());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
} catch (IOException ioException) {
ioException.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
public void init() throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
// 读取方法表 配置文档
File file = new File(filePath);
FileReader fileReader = new FileReader(file);
BufferedReader br = new BufferedReader(fileReader);
String listStr = null;
while ((listStr = br.readLine()) != null) {
System.out.println(listStr);
// 解析单行数据 方法名 类名
String[] methodMsg = listStr.split(" ");
if(methodMsg.length < 2){
continue;
}
String methodName = methodMsg[0];
String className = methodMsg[1];
if(!methodMap.containsKey(methodName)){
// 反射获取类对象
Class<?> aClass = Class.forName(className);
// 加载类实例
Object object = aClass.newInstance();// 实例化
// 存储方法名-类实例
methodMap.put(methodName, (RpcInterface) object);
}
}
}
//如果新增接口和实现类,需要向注册中心注册服务
public void register(String methodName , String className) {
System.out.println("进入register方法");
//发送请求向注册中心注册 向注册中心发送方法名
Socket s = null;
try {
s = new Socket(RS_IP, RS_PORT);
ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
oos.writeUTF("register");
oos.writeUTF(methodName);
System.out.println("methodName="+methodName);
oos.writeUTF(IP);
System.out.println("本机ip地址为:"+IP);
oos.writeUTF(String.valueOf(PORT));
System.out.println("PORT="+String.valueOf(PORT));
oos.flush();
ObjectInputStream ois = new ObjectInputStream(s.getInputStream());
String result = (String)ois.readUTF();
System.out.println(result);
JOptionPane.showMessageDialog(this,result);
} catch (IOException ioException) {
ioException.printStackTrace();
}
//将kv写入methods.txt
if (!methodMap.containsKey(methodName)) {
String line = methodName + " " + className;
File file = new File(filePath);
BufferedWriter bw = null;
try {
//将新增服务写入methods.txt文件
bw = new BufferedWriter(new FileWriter(file, true));
bw.newLine();
bw.write(line);
bw.close();
//将新增服务增加到methodMap中
// 反射获取类对象
Class<?> aClass = Class.forName(className);
// 加载类实例
Object object = aClass.newInstance();// 实例化
// 存储方法名-类实例
methodMap.put(methodName, (RpcInterface) object);
} catch(IOException | ClassNotFoundException ioException){
ioException.printStackTrace();
} catch(IllegalAccessException e){
e.printStackTrace();
} catch(InstantiationException e){
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
Server server = new Server();
//启动服务
ServerSocket serverSocket = new ServerSocket(PORT);
System.out.println("Server started");
while (true) {
try (Socket socket = serverSocket.accept();
ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream input = new ObjectInputStream(socket.getInputStream())) {
String code = input.readUTF();
System.out.println("服务器接收到的指令为:"+code);
if(code.equals("connect")){
output.writeUTF("连接成功");
output.flush();
}else if("RPCRequest".equals(code)){
// 读第一句话 方法名
String methodName = input.readUTF();
if (server.methodMap.containsKey(methodName)) {
// 根据方法名 获取实例
RpcInterface rpcInterface = server.methodMap.get(methodName);
// 反射类
Class rpcClass = rpcInterface.getClass();
// 加载方法
Method[] methods = rpcClass.getMethods();
// 读第二句话 参数
String name = input.readUTF();
// 调用方法 获取返回结果
Object obj = methods[0].invoke(rpcInterface, name);
// 发送给调用者结果
output.writeUTF(obj.toString());
output.flush();
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
三、注册中心
配置文件中维护方法名和方法所在服务器的ip地址和端口号及活跃状态
注册中心初始化扫描文件,将信息存入map中
1、注册服务功能:若服务未注册过,更新map并将数据持久化到文件中
2、查找服务功能:在map中找到匹配方法名且活跃状态为true的服务器的ip地址和端口号发送给客户端,
3、更新状态功能:遍历map,连接服务器,若服务器无法连接,更新服务状态为false
注意:
可能存在注册中心更新完服务状态之后服务器马上停掉的情况,此时客户端根据获取的ip和端口无法与服务器建立连接
解决:
在注册中心获取服务状态为true之后,与服务器进行连接,若连接不上,更新状态为false并发送响应信息给客户端
RegisterCenter.java
import java.io.*;
import java.net.ConnectException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collection;
import java.util.HashMap;
import java.util.Set;
public class RegisterCenter {
//默认方法名唯一
//map属性存储方法名和对应的服务器 注册操作需要持久化到文件
private static HashMap<String,ServerMsg> map = new HashMap<>();
private static final int PORT = 12345;
//文件存储路径
private static final String PATH = "RegisterCenter\\config\\services.txt";
static{
//将本地配置文件中信息读入map
File file = new File(PATH);
String sub = PATH.substring(0,PATH.lastIndexOf("\\"));
File directory = new File(sub);
if(!directory.exists()){
directory.mkdirs();
}
if(!file.exists()){
try {
file.createNewFile();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
try {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file),"UTF-8"));
String str = null;
while((str = br.readLine()) != null){
System.out.println(str);
String[] kvs = str.split("=");
String[] msgVals = kvs[1].split("\\+");
ServerMsg msg = new ServerMsg(msgVals[0],Integer.parseInt(msgVals[1]));
map.put(kvs[0],msg);
}
br.close();
System.out.println("注册中心初始化之后的map:"+map);
} catch (FileNotFoundException | UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
//注册服务 服务器发送请求注册服务
public static void handleRequest(){
try {
ServerSocket ss = new ServerSocket(PORT);
while(true){
Socket s = ss.accept();
System.out.println(s);
//注册的方法名 服务器的ip和端口
new Thread(new Runnable() {
@Override
public void run() {
try {
HandleRequest(s);
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}).start();
}
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
public static void HandleRequest(Socket s ) throws IOException {
// System.out.println(s+"连接啦!");
ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
ObjectInputStream ois = new ObjectInputStream(s.getInputStream());
//获取指令
String code = ois.readUTF();
if(code.equals("register")){
String methodName = (String) ois.readUTF();
System.out.println("注册中心接收到的方法名为:"+methodName);
String ip = ois.readUTF();
System.out.println("注册中心接收到的服务器ip地址为:"+ip);
int port = Integer.parseInt(ois.readUTF());
System.out.println("注册服务器的监听接口为:"+port);
ServerMsg msg = new ServerMsg(ip,port);
if(!map.containsKey(methodName)){
map.put(methodName,msg);
System.out.println("注册后的map为:"+map);
oos.writeUTF("注册成功!");
File file = new File(PATH);
System.out.println("path"+PATH);
String sub = PATH.substring(0,PATH.lastIndexOf("\\"));
File directory = new File(sub);
if(!directory.exists()){
directory.mkdirs();
}
if(!file.exists()){
file.createNewFile();
}
String str = methodName+"="+msg.getIp()+"+"+msg.getPort()+"+"+msg.isState();
System.out.println(str);
FileOutputStream fos = new FileOutputStream(file,true);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos,"UTF-8"));
bw.write(str);
bw.newLine();
bw.close();
}else{
oos.writeUTF("服务已存在,请勿重复注册");
}
oos.flush();
}else if(code.equals("findServer")){
String methodName = (String)ois.readUTF();
if(map.containsKey(methodName) && map.get(methodName).isState()){
//为排除在注册中心连接服务器后服务器马上挂掉的情况,因此在state为true的情况下,注册中心再发送请求连接服务器,若能连接,则返回ip和端口号,若服务器在500ms内挂掉,则返回提示信息给客户端
String ip = map.get(methodName).getIp();
int port = map.get(methodName).getPort();
try(Socket socket = new Socket(ip,port);
ObjectOutputStream os = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream is = new ObjectInputStream(socket.getInputStream())){
os.writeUTF("connect");
os.flush();
String res = is.readUTF();
if("连接成功".equals(res)){
oos.writeUTF(ip+" "+port);
}
}catch(ConnectException e ){
map.get(methodName).setState(false);
oos.writeUTF("该服务不存在或服务器未启动");
}
}else{
oos.writeUTF("该服务不存在或服务器未启动");
}
oos.flush();
}
}
//更新服务state
public static void updateState(){
new Thread(new Runnable() {
@Override
public void run() {
while(true){
Collection<ServerMsg> msgs = map.values();
for (ServerMsg msg : msgs){
String ip = msg.getIp();
int port = msg.getPort();
Socket s = null;
try {
s = new Socket(ip,port);
ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
ObjectInputStream ois = new ObjectInputStream(s.getInputStream());
oos.writeUTF("connect");
oos.flush();
String res = ois.readUTF();
if("连接成功".equals(res)){
msg.setState(true);
continue;
}
}catch(ConnectException e ){
//服务挂掉了 修改状态为false
msg.setState(false);
System.out.println(ip+":"+port+"服务器挂掉了!");
} catch (IOException ioException) {
ioException.printStackTrace();
}finally{
if(s != null){
try {
s.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
public static void main(String[] args) {
updateState();
handleRequest();
}
}
ServerMsg.java
维护服务所在服务器的ip地址,端口号及活跃状态
public class ServerMsg {
private String ip;
private int port;
private boolean state ; //状态 是否挂掉
public ServerMsg(String ip , int port ){
this.ip = ip ;
this.port = port ;
this.state = true ;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public boolean isState() {
return state;
}
public void setState(boolean state) {
this.state = state;
}
@Override
public String toString() {
return "ServerMsg{" +
"ip='" + ip + '\'' +
", port=" + port +
", state=" + state +
'}';
}
}