详解 服务发现 的基本实现

YouzgLogo

发展历程

对于网络通信,总共有如下三个历程

单服务器 ———— 垂直应用架构

和本人《C/SFramework》专栏中所使用的方式一样:
只存在一个服务器,这就需要服务器客户端之间保持“长连接”,
那么,就客户端需要通过配置文件来获取服务器的ipport
如下图所示:
长连接 展示
而这样的形势是完全不合理的!
因为若是客流量较大,会非常容易导致服务器崩溃
而若是我们限制了客流量,则会导致用户体验较差


因此,基于上述不满,网络通信,发展为如下形式:

多服务器 ———— 分布式服务架构

开设多个服务器,共同来处理客户端的请求
这种形式下,客户端还是需要一个配置文件,来存储每一个服务器ipport
如下图所示:
多服务器 展示
正如本人在上图中所讲那样:
这时若是新增一个服务器
那么,所有客户端都需要修改配置文件,这样做是十分困难的!
而且,由于配置文件 是 写死的,因此可能会存在大量客户端同时访问其中一个服务器,而存在其它服务器 空闲的情况!


那么,为了解决上述问题,网络通信衍变为如下形式:

注册中心 + 多服务器 ———— 流动计算架构

再次添加一个用于负载均衡(依照当前每一个APP服务器的“健康状态”来分配ip和port)的服务器 —— 分流服务器
如下图所示:
负载均衡 展示
至此,网络通信不再需要客户端存储访问APP服务器配置文件,只需要存储分流服务器相关信息
这样,我们既能简化客户端的操作,也能够实现负载均衡

而最后一种网络通信形式,就被称之为“服务发现

服务发现

何为“服务发现”?

所有服务器(无论是某一种APP的多个服务器,还是不同APP的多个服务器)在启动时,
都需要在“注册中心”进行注册
客户端需要从“注册中心”获取它的请求所属APP的服务器(组)地址信息

客户端角度看,注册中心起到以下两个作用

1、是否存在某个APP的服务器;
2、获取某个APP服务器(组)的地址信息

所以,客户端角度,这是一种“服务发现”的机制

服务发现的 妙用

1、服务器热插拔
(即:在系统运行状态下,单个服务器发生异常情况,并不会影响全局)
2、服务器在线升级;
(基于 服务器热插拔 的性质)
3、容错机制;
(基于 服务器热插拔 的性质)
4、负载均衡


那么,在本篇博文中,本人将来基本实现服务发现

基本思路

在上文的讲解中,我们也能发现:
服务发现的核心 就是 注册中心
那么,我们来思考下注册中心要完成的功能

注册中心需求分析

  1. 注册中心本身是一个服务器
  2. 注册中心对服务提供者提供的一些功能;
  3. 注册中心对服务消费者提供的另一些功能

那么,我们能够从上面的讲解中,了解到:
注册中心的真正核心在于能够对APP服务器池进行轮询负载均衡


那么,依照上述的思想,现在我们就先基本实现下 负载均衡轮询

轮询 与 负载均衡 的基本实现

首先是 一个用于获取 保存在 注册中心 的 APP服务器网络节点信息接口规范:

网络节点 —— INetNode接口:

package edu.youzg.balance.core;

/**
 * 用于取出 “服务器池” 中的 服务器的ip和port
 */
public interface INetNode {
	String getIp();
	int getPort();
}

接下来是 基本实现了INetNode接口的 实现类

网络节点实现类 —— DefaultNetNode类:

(注:该类仅是基本实现,并没有任何逻辑,是以便后续的操作而产生的)

package edu.youzg.balance.core;

/**
 * 默认的 网络节点,存储每一个服务器的ip和port
 */
public class DefaultNetNode implements INetNode {
	private String ip;
	private int port;

	public DefaultNetNode() {
	}
	
	public DefaultNetNode(String ip, int port) {
		this.ip = ip;
		this.port = port;
	}

	public void setIp(String ip) {
		this.ip = ip;
	}

	public void setPort(int port) {
		this.port = port;
	}

	@Override
	public String getIp() {
		return ip;
	}

	@Override
	public int getPort() {
		return port;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((ip == null) ? 0 : ip.hashCode());
		result = prime * result + port;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}

		DefaultNetNode other = (DefaultNetNode) obj;
		if (ip == null) {
			if (other.ip != null) {
				return false;
			}
		} else if (!ip.equals(other.ip)) {
			return false;
		}
		if (port != other.port) {
			return false;
		}
		return true;
	}

	@Override
	public String toString() {
		return "[" + ip + ":" + port + "]";
	}

}

可以看到:该类仅是一个用于保存 “注册过的”服务器信息的类


接下来是 完善DefaultNetNode类,并附加“轮询功能”的网络节点类 —— PollingNetNode类

用于 轮询 的 网络节点 —— PollingNetNode类:

package edu.youzg.balance.core;

/**
 * 能够形成 顺序链(“双向链表”,以便我们取出 前驱和后继节点) 的 “网络节点”
 */
public class PollingNetNode extends DefaultNetNode {
	private PollingNetNode next;
	private PollingNetNode pre;
	
	public PollingNetNode() {
		this.next = this;
		this.pre = this;
	}

	public PollingNetNode(String ip, int port) {
		super(ip, port);
		this.next = this;
		this.pre = this;
	}

	public void setNext(PollingNetNode next) {
		this.next = next;
	}

	public void setPre(PollingNetNode pre) {
		this.pre = pre;
	}

	public PollingNetNode getNext() {
		return this.next;
	}
	
	public PollingNetNode getPre() {
		return this.pre;
	}
	
}

接下来是 一个 规范操作网络节点的接口:

负载均衡器 —— INetNodeBalance类:

package edu.youzg.balance.core;

/**
 * 规范 “增、删、查” 网络节点 的接口(负载均衡器)
 */
public interface INetNodeBalance {
	void addNode(INetNode node);
	INetNode removeNode(INetNode node);
	INetNode getNode();
}

然后是 基本实现该接口,并给出一个保存每一个注册过的服务器抽象类

未实现 获取节点方式 的 负载均衡器 —— AbstractNetNodeBalance抽象类:

package edu.youzg.balance.core;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 提供 “网络节点池子”,以及这个池子的相关操作方法
 */
public abstract class AbstractNetNodeBalance implements INetNodeBalance {
	protected final Map<Integer, INetNode> nodePool;

	/**
	 * 初始化 “网络节点池子”
	 */
	public AbstractNetNodeBalance() {
		this.nodePool = new ConcurrentHashMap<>();
	}
	
	@Override
	public void addNode(INetNode node) {
		if (node == null) {
			return;
		}
		
		int nodeHashCode = node.hashCode();
		if (nodePool.containsKey(nodeHashCode)) {
			return;
		}
		
		nodePool.put(nodeHashCode, node);
	}
	
	public boolean isNodePoolEmpty() {
		return nodePool.isEmpty();
	}
	
	@Override
	public INetNode removeNode(INetNode node) {
		if (node == null) {
			return null;
		}
		return nodePool.remove(node.hashCode());
	}
	
}

那么,现在我们就来完成下实现“轮询功能”的双向链表类:

轮询实现 —— PollingBalance类:

package edu.youzg.balance.core;

/**
 * 实现 能够 “顺序轮询” 的 网络节点的 “双向链表”
 */
public class PollingBalance extends AbstractNetNodeBalance {
	private PollingNetNode pollingNode;
	private PollingNetNode currentNode;

	/**
	 * 初始化 AbstractNetNodeBalance的map
	 */
	public PollingBalance() {
		super();
	}

	/**
	 * “链表”操作 —— 将所传网络节点 加入到 双向链表 的表头之前(即:最后一个节点后)<br/>
	 * 并更新 AbstractNetNodeBalance 中的map
	 * @param node 要进行加入的 网络节点
	 */
	@Override
	public synchronized void addNode(INetNode node) {
		PollingNetNode newNode = new PollingNetNode(node.getIp(), node.getPort());
		
		if (pollingNode == null) {
			pollingNode = newNode;
			currentNode = pollingNode;
			super.addNode(newNode);
			return;
		}
		newNode.setPre(pollingNode.getPre());
		newNode.setNext(pollingNode);
		pollingNode.setPre(newNode);
		newNode.getPre().setNext(newNode);
		
		super.addNode(newNode);
	}

	/**
	 * “链表”操作 —— 将所传网络节点 从 双向链表 中删除掉<br/>
	 * 并更新 AbstractNetNodeBalance 中的map
	 * @param node 要删除的 网络节点
	 * @return 成功删除 的 节点信息
	 */
	@Override
	public synchronized INetNode removeNode(INetNode node) {
		PollingNetNode target = new PollingNetNode(node.getIp(), node.getPort());
		target = (PollingNetNode) super.removeNode(target);
		
		if (target == null) {
			return null;
		}
		
		if (isNodePoolEmpty()) {
			pollingNode = null;
			currentNode = null;
			return pollingNode;
		}
		
		if (currentNode == target) {
			currentNode = target.getNext();
		}
		
		if (pollingNode == target) {
			pollingNode = target.getNext();
		}
		
		target.getPre().setNext(target.getNext());
		target.getNext().setPre(target.getPre());
		target.setPre(target);
		target.setNext(target);
		
		return target;
	}

	/**
	 * 按照“加入时间顺序” 轮询 双向链表
	 * @return
	 */
	@Override
	public synchronized INetNode getNode() {
		INetNode result = currentNode;
		
		if (currentNode != null) {
			currentNode = currentNode.getNext();
		}
		
		return result;
	}

}

最后,由于本人说过:注册中心的一个核心功能 就是 负载均衡
而实现负载均衡,我们需要查询每一个App服务器的“健康状态
最主要的还是高并发情况的处理
这就需要一套精妙的算法

由于本篇博文的目的是介绍以及初步实现服务发现
因此在这里本人就使用极简的方式,来优化下客户端获取APP服务器信息的操作,来初步实现 负载均衡

负载均衡实现 —— RandomBalance类:

package edu.youzg.balance.core;

import java.util.LinkedList;
import java.util.List;

/**
 * 随机访问(初步模拟“负载均衡”)的 双向链表
 */
public class RandomBalance extends AbstractNetNodeBalance {
	private final List<INetNode> nodeList;

	/**
	 * 初始化 AbstractNetNodeBalance的map 和 nodeList
	 */
	public RandomBalance() {
		super();
		nodeList = new LinkedList<>();
	}

	/**
	 * 按照顺序加入 网络节点
	 * 并更新 AbstractNetNodeBalance的map
	 * @param node 要进行加入的网络节点
	 */
	@Override
	public void addNode(INetNode node) {
		super.addNode(node);
		if (node == null || nodeList.contains(node)) {
			return;
		}
		nodeList.add(node);
	}

	/**
	 * 删除指定的 网络节点
	 * 并更新 AbstractNetNodeBalance的map
	 * @param node
	 * @return
	 */
	@Override
	public INetNode removeNode(INetNode node) {
		if (node == null || !nodeList.contains(node)) {
			return null;
		}
		nodeList.remove(node);
		return super.removeNode(node);
	}

	/**
	 * 通过 随机数,来随机访问 nodeList中的网络节点
	 * @return 随机取出的一个 网络节点
	 */
	@Override
	public INetNode getNode() {
		if (isNodePoolEmpty()) {
			return null;
		}
		
		int index = (int) (Math.random() * (nodeList.size() + 1));
		return this.nodeList.get(index);
	}

}

那么,既然轮询以及负载均衡基本实现了,
现在本人就来铺垫下,以便我们之后实现注册中心

注册中心 的 铺垫

首先,本人给出一个用于保存某项服务的信息:

单项服务节点 —— ServiceNode类:

package edu.youzg.about_service_discovery.core;

import edu.youzg.balance.core.INetNode;

/**
 * 用于保存 某项服务所需的 ip、port 和 服务名
 */
public class ServiceNode {
	private String service;
	private INetNode node;
	
	public ServiceNode() {
	}

	public ServiceNode(String service, INetNode node) {
		this.service = service;
		this.node = node;
	}

	public String getService() {
		return service;
	}

	public void setService(String service) {
		this.service = service;
	}

	public INetNode getNode() {
		return node;
	}

	public void setNode(INetNode node) {
		this.node = node;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((node == null) ? 0 : node.hashCode());
		result = prime * result + ((service == null) ? 0 : service.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		ServiceNode other = (ServiceNode) obj;
		if (node == null) {
			if (other.node != null) {
				return false;
			}
		} else if (!node.equals(other.node)) {
			return false;
		}
		if (service == null) {
			if (other.service != null) {
				return false;
			}
		} else if (!service.equals(other.service)) {
			return false;
		}
		return true;
	}

	@Override
	public String toString() {
		return service + " : " + node;
	}
	
}

之后我们向“池子”中 增加/删除APP服务器,都是基于单项服务节点的


那么,基于上述的单项服务节点,本人再来提供给一个接口:

基于单项服务节点的 基本操作 规范 —— IServiceNodeAction接口:

package edu.youzg.about_service_discovery.core;

import java.util.List;

/**
 * 操作 单项服务节点 的 action
 */
public interface IServiceNodeAction {
	void registryService(String nodeId, ServiceNode service);
	void registryService(String nodeId, List<ServiceNode> serviceList);
	
	void logout(String nodeId);
}

相应地,本人来给出上述接口的实现类:

基于单项服务节点的 基本操作 实现 —— ServiceNodeAction类:

package edu.youzg.about_service_discovery.core;

import java.util.List;

/**
 * 实现 操作单项服务节点 的基本action
 */
public class ServiceNodeAction implements IServiceNodeAction {
	
	public ServiceNodeAction() {
	}

	@Override
	public void registryService(String nodeId, ServiceNode service) {
		ServicePool.addService(service.getService(), service.getNode());
		NodeServicePool.registryService(nodeId, service);
	}

	@Override
	public void registryService(String nodeId, List<ServiceNode> serviceList) {
		if (serviceList == null || serviceList.isEmpty()) {
			return;
		}
		for (ServiceNode service : serviceList) {
			registryService(nodeId, service);
		}
	}

	@Override
	public void logout(String nodeId) {
		List<ServiceNode> serviceList = NodeServicePool.logout(nodeId);
		if (serviceList == null || serviceList.isEmpty()) {
			return;
		}
		
		for (ServiceNode service : serviceList) {
			ServicePool.removeService(service.getService(), service.getNode());
		}
	}
	
}

可能会出现这样的情况:
一个服务器,提供多种服务
那么,我们若是想要 增删等操作一个服务器 的时候,就会很麻烦
因此,本人在这里给出一个 容器类:

网络节点池 —— NodeServicePool类:

package edu.youzg.about_service_discovery.core;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class NodeServicePool {
	private static final Map<String, List<ServiceNode>> nodeServicePool
			= new HashMap<String, List<ServiceNode>>();	// 以注册中心分配给该服务器的 id为键, 该服务器所提供的 服务列表 为值
	
	public NodeServicePool() {
	}

	/**
	 * 通过 目标服务器的 id,从map中移除该服务器的信息
	 * @param nodeId 目标服务器的 id
	 * @return 该服务器的 服务列表
	 */
	public static List<ServiceNode> logout(String nodeId) {
		return nodeServicePool.remove(nodeId);
	}

	/**
	 *
	 * @param nodeId 目标服务器的 id
	 * @param serviceNode 该项服务的 ip、port 和 服务映射名
	 */
	public static void registryService(String nodeId, ServiceNode serviceNode) {
		List<ServiceNode> serviceNodeList = nodeServicePool.get(nodeId);
		if (serviceNodeList == null) {
			serviceNodeList = new ArrayList<ServiceNode>();
			nodeServicePool.put(nodeId, serviceNodeList);
		}
		if (!serviceNodeList.contains(serviceNode)) {
			serviceNodeList.add(serviceNode);
		}
	}
	
}

由于我们接下来的操作,会对客户端发送的请求,进行解析,
然后将按照 处理该请求的服务,发送给客户端 提供这些服务的APP服务器的信息
因此,本人现在来给出一个 容器,来按照功能 分别存储 每一类服务器

服务池 —— ServicePool类:

package edu.youzg.about_service_discovery.core;

import edu.youzg.balance.core.INetNode;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ServicePool {
	private static final Map<String, List<INetNode>> servicePool
			= new HashMap<>();	// 以 APP服务器的功能名 为键,APP服务器的节点信息 为值,存储的一个map

	public ServicePool() {
	}

	/**
	 * 增加一个 APP服务器
	 * @param serviceName 目标APP服务器的 功能名
	 * @param node 目标APP服务器的 节点信息
	 */
	public void addService(String serviceName, INetNode node) {
		List<INetNode> serviceList = servicePool.get(serviceName);
		if (serviceList == null) {
			serviceList = new ArrayList<>();
			servicePool.put(serviceName, serviceList);
		}
		if (!serviceList.contains(node)) {
			serviceList.add(node);
		}
	}

	/**
	 * 删除一个 APP服务器
	 * @param serviceName 目标APP服务器的 功能名
	 * @param node 目标APP服务器的 节点信息
	 */
	public void removeService(String serviceName, INetNode node) {
		List<INetNode> serviceList = servicePool.get(serviceName);
		if (serviceList == null || !serviceList.contains(node)) {
			return;
		}
		serviceList.remove(node);
	}

	/**
	 * 从“服务器池”中获取一个 满足相应功能需求的 APP服务器的网络节点信息
	 * @param serviceName
	 * @return
	 */
	public List<INetNode> getService(String serviceName) {
		return servicePool.get(serviceName);
	}
	
}

那么,所有的铺垫工作就做好了,
现在本人来基本讲解,并基本实现下注册中心逻辑功能

首先,我们要明确的几个知识点是:

  1. 客户端APP服务器 相对于 注册中心,都是客户端
  2. 由于我们需要持续获知当前APP服务器的状态
    因此,APP服务器注册中心 之间,必须是长连接
  3. 在现代已经成熟并被广泛使用的服务发现机制中,
    客户端注册中心之间的关系大致如下:
    注册中心保存着客户端的Socket,将其放在一个容器中,
    每隔一段时间,将当前的APP服务器的状态轮询广播给这个容器中的每一个客户端的Socket

那么,本人先来完成APP服务器的基本功能:

APP服务器

由于 APP服务器 作为服务的提供者,
需要完成的基本功能为:

  1. 注册其功能
  2. 下线(告知注册中心:本APP服务器停止提供任何服务)

那么,依照上述思想,本人先来给出一个 用于规范APP服务器基本操作的接口:

IServiceProvider接口:

package edu.youzg.about_service_discovery.core;

import java.util.List;

public interface IServiceProvider {
	void registryService(String nodeId, ServiceNode service);
	void registryService(String nodeId, List<ServiceNode> serviceList);
	
	void logout(String nodeId);
}

ServiceProvider类:

相应地,本人来给出这个接口的实现类

package edu.youzg.about_service_discovery.core;

import java.util.List;

public class ServiceProvider implements IServiceProvider {

	public ServiceProvider() {
	}

	/**
	 * 注册服务
	 * @param nodeId 提供该服务的APP服务器的id(由注册中心分配)
	 * @param service 提供的服务
	 */
	@Override
	public void registryService(String nodeId, ServiceNode service) {
		ServicePool.addService(service.getService(), service.getNode());
		NodeServicePool.registryService(nodeId, service);
	}

	/**
	 * 注册服务列表
	 * @param nodeId 提供该服务的APP服务器的id(由注册中心分配)
	 * @param serviceList 提供的服务列表
	 */
	@Override
	public void registryService(String nodeId, List<ServiceNode> serviceList) {
		if (serviceList == null || serviceList.isEmpty()) {
			return;
		}
		for (ServiceNode service : serviceList) {
			registryService(nodeId, service);
		}
	}

	/**
	 * 注销 APP服务器
	 * @param nodeId  要注销的APP服务器的id(由注册中心分配)
	 */
	@Override
	public void logout(String nodeId) {
		List<ServiceNode> serviceList = NodeServicePool.logout(nodeId);
		if (serviceList == null || serviceList.isEmpty()) {
			return;
		}

		for (ServiceNode service : serviceList) {
			ServicePool.removeService(service.getService(), service.getNode());
		}
	}

}

那么,接下来,接下来就是客户端的实现:

但是呢,由于写到这里,篇幅已经够长了
而且本文的目的,不是完全实现 服务发现机制
而是带领同学们 了解服务发现,熟悉服务发现的基本实现流程

因此,在这里,本人仅初步实现下客户端与注册中心之间的基本任务

客户端(若需完成请自行思考)

思路:

注册中心需要对客户端提供哪些服务呢?

  1. 客户端向注册中心进行“注册”(即“服务请求”)
  2. 刷新服务地址列表”功能,
    可以通过配置完成“服务发现框架”(例如:Netty框架)的启动
    也可以启动“后台”执行的“刷新服务地址列表”的功能
  3. 负载均衡”功能,
    可以通过配置实现“负载均衡(策略)”的激活

IServiceCustomer接口:

客户端与注册中心之间,最基本要实现的功能就是 获取指定服务APP链

package edu.youzg.about_service_discovery.core;

import edu.youzg.balance.core.INetNode;

import java.util.List;

public interface IServiceCustomer {
	List<INetNode> getServiceAddressList(String service);
}

相应地,本人来给出实现类:

ServiceCustomer类:

package edu.youzg.about_service_discovery.core;

import edu.youzg.balance.core.INetNode;

import java.util.List;

public class ServiceCustomer implements IServiceCustomer {

	public ServiceCustomer() {
	}
	
	@Override
	public List<INetNode> getServiceAddressList(String service) {
		return ServicePool.getService(service);
	}

}

在这里本人再次声明
此处的客户端实现的是短链接模式,与实际实现模式有很大区别
而且很多的逻辑没有实现
仅是为了后文的注册中心的基本代码而给出的 不完整代码


注册中心

铺垫了这么久,
但是,本人还是要遗憾地告诉同学们:

在这里,注册中心也是没有完全实现的
仅是在模仿,且为了实现方便,模拟也不会跟现有的完全一样
由于客户端的逻辑太过于复杂,因此在本文中只实现APP服务器与注册中心之间的基本逻辑

“基本”实现注册中心 —— RegistryCenter类:

package edu.youzg.about_service_discovery.core;

import edu.youzg.netframework.core.INetListener;
import edu.youzg.netframework.core.IServerAction;
import edu.youzg.netframework.core.Server;
import edu.youzg.netframework.core.ServerConversation;
import edu.youzg.rmi.core.RMIFactory;
import edu.youzg.rmi.core.RMIServer;
import edu.youzg.util.IMecView;
import edu.youzg.util.PropertiesParser;
import edu.youzg.util.ViewTool;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.border.TitledBorder;

public class RegistryCenter implements IMecView, INetListener {
	private static final int VIEW_WIDTH = 600;
	private static final int VIEW_HTIGHT = 400;
	private static final int VIEW_MIN_WIDTH = 600;
	private static final int VIEW_MIN_HTIGHT = 400;

	private Server server;
	private RMIServer serviceProvider;
	private RMIServer serviceCustomer;

	private static int serverPort;
	private static int serviceProviderPort;
	private static int serviceCustomerPort;

	private JFrame jfrmView;
	private JTextField jtxtCommand;
	private JTextArea jtatMessage;

	static {
		readConfig();
	}

	public RegistryCenter() {
	}

	public static void readConfig(String configPath) {
		PropertiesParser parser = new PropertiesParser();
		parser.loadProperties(configPath);
		serverPort = Integer.valueOf(parser.value("port"));
		serviceProviderPort = Integer.valueOf(parser.value("provider_port"));
		serviceCustomerPort = Integer.valueOf(parser.value("customer_port"));

		RMIFactory.scanRMIMapping("src/RegistryCenterRMIMapping.xml");
	}

	public static void readConfig() {
		readConfig("src/netcfg.properties");
	}

	@Override
	public void reinit() {
		jtatMessage.setFocusable(false);
		jtatMessage.setEditable(false);
		jtxtCommand.requestFocus();

		initServer();
	}

	private void initServer() {
		server = new Server();
		server.setServerAction(new IServerAction() {
			@Override
			public void clientAbnormalDrop(ServerConversation client) {
				new ServiceProvider().logout(client.getId());
			}
		});
		server.setPort(serverPort);
		server.addListener(this);

		serviceProvider = new RMIServer();
		serviceCustomer = new RMIServer();
		serviceProvider.setPort(serviceProviderPort);
		serviceCustomer.setPort(serviceCustomerPort);
	}

	private void closeView() {
		if (server == null || server.isStartup()) {
			ViewTool.showMessage(jfrmView, "服务器为null或服务器未宕机");
			return;
		}
		jfrmView.dispose();
		System.exit(0);
	}

	@Override
	public void dealEvent() {
		jfrmView.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				closeView();
			}
		});

		jtxtCommand.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				String command = jtxtCommand.getText().trim();
				if (command.length() > 0) {
					dealCommand(command);
				}
				jtxtCommand.setText("");
			}
		});
	}

	private void startupServer() {
		server.startup();
		serviceProvider.startup();
		serviceCustomer.startup();
	}

	private void shutdownServer() {
		server.shutdown();
		if (server.isStartup()) {
			return;
		}
		serviceProvider.shutdown();
		serviceCustomer.shutdown();
	}

	private void dealCommand(String command) {
		if (command.equalsIgnoreCase("startup")
				|| command.equalsIgnoreCase("st")) {
			startupServer();
		} else if (command.equalsIgnoreCase("shutdown")
				|| command.equalsIgnoreCase("sd")) {
			shutdownServer();
		} else if (command.equalsIgnoreCase("exit")
				|| command.equalsIgnoreCase("x")) {
			closeView();
		} else if (command.equalsIgnoreCase("fd")
				|| command.equalsIgnoreCase("forcedown")) {
			// TODO 注册中心强行宕机,这里的操作需要进一步讨论
		}
	}

	@Override
	public JFrame getFrame() {
		return jfrmView;
	}

	@Override
	public synchronized void dealMessage(String message) {
		jtatMessage.append(message);
		jtatMessage.append("\n");
		jtatMessage.setCaretPosition(jtatMessage.getText().length());
	}

	@Override
	public void init() {
		jfrmView = new JFrame("服务发现 - 注册中心 - 监视器");
		jfrmView.setSize(VIEW_WIDTH, VIEW_HTIGHT);
		jfrmView.setMinimumSize(new Dimension(VIEW_MIN_WIDTH, VIEW_MIN_HTIGHT));
		jfrmView.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
		jfrmView.setLocationRelativeTo(null);
		jfrmView.setLayout(new BorderLayout());

		JLabel jlblCenterTopic = new JLabel("服务发现 - 注册中心 - 监视器", JLabel.CENTER);
		jlblCenterTopic.setFont(topicFont);
		jlblCenterTopic.setForeground(topicColor);
		jfrmView.add(jlblCenterTopic, BorderLayout.NORTH);

		JPanel jpnlLeftBlank = new JPanel();
		jfrmView.add(jpnlLeftBlank, BorderLayout.WEST);
		JPanel jpnlRightBlank = new JPanel();
		jfrmView.add(jpnlRightBlank, BorderLayout.EAST);

		jtatMessage = new JTextArea();
		jtatMessage.setFont(normalFont);
		JScrollPane jscpMessage = new JScrollPane(jtatMessage);
		jfrmView.add(jscpMessage, BorderLayout.CENTER);
		TitledBorder ttbdMessage = new TitledBorder("系统消息");
		ttbdMessage.setTitleFont(normalFont);
		ttbdMessage.setTitleColor(Color.red);
		ttbdMessage.setTitlePosition(TitledBorder.ABOVE_TOP);
		ttbdMessage.setTitleJustification(TitledBorder.CENTER);
		jscpMessage.setBorder(ttbdMessage);

		JPanel jpnlFooter = new JPanel(new FlowLayout(FlowLayout.CENTER));
		jfrmView.add(jpnlFooter, BorderLayout.SOUTH);
		JLabel jlblCommand = new JLabel("命令");
		jlblCommand.setFont(normalFont);
		jpnlFooter.add(jlblCommand);

		jtxtCommand = new JTextField(30);
		jtxtCommand.setFont(normalFont);
		jpnlFooter.add(jtxtCommand);
	}

}

上面的代码,引用了很多本人之前的小工具,
若有疑问,请观看本人往期博文


RMI配置文件 —— RegistryCenterRMIMapping.xml:

<?xml version="1.0" encoding="UTF-8"?>
<RmiMapping>
	<mapping interface="com.mec.service_discovery.core.IServiceProvider"
	class="com.mec.service_discovery.core.ServiceProvider"></mapping>
</RmiMapping>

初始化配置文件 —— netcfg.properties:

port=54188
provider_port=54189
customer_port=54190
max_client_count=20
autoStartup=true


结果展示

那么,本人在这里仅展示下界面:
界面 展示


posted @ 2020-05-19 10:31  在下右转,有何贵干  阅读(383)  评论(0编辑  收藏  举报