介绍
远程调用是一种非常好的程序设计思想和技术,是现代WEB应用的前驱,也是目前许多基于C/S架构的APP应用软件的主流技术。
客户端可以调用服务端提供的远程服务,服务端也可以回调客户端的远程服务对象,从而实现真正的分布式、协同式软件。一些典型的应用需要服务器调用客户端的对象,如聊天系统中服务器需要刷新客户端的信息显示窗口;股票系统中服务器需要刷新客户端的实时交易数据信息等,因这些动作的事件触发在服务器端,而客户端是被动的。服务器主动刷新客户端的方式,是重要的推送服务程序设计技术。
在上一篇,已经了解了服务器如何主动调用客户端的远程服务(相互的过程),实现方式是客户端发布远程服务(客户端需要发布),由服务端“lookup”其远程服务,进行远程调用;其实还有另外一种使用方式:就是服务端定义的远程服务方法中,提供客户端的远程接口类型参数,客户端采用本地方法调用的方式,将客户端的远程接口实现类作为参数传递给服务端,而不需要特意进行远程服务的发布(将实现类传给服务端,客户端不需要发布)。
程序描述
使用RMI的方式,实现简单的群聊功能。
关键技术:将客户端窗口的信息刷新方法定义成远程接口中的方法,由服务器远程调用实现刷新,从而实现由服务器控制客户聊天窗口信息的显示进程。
程序设计
核心算法
创建两个远程接口ClientService和ServerService,分别由客户端和服务端实现。接口定义如以下代码所示(强调,服务端和客户端都各自拥有相同的远程接口,所在package应一致)。
客户端远程接口ClientService:
package chapter13.rmi;
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* 客户端远程对象接口,该接口为服务端提供回调服务
*/
public interface ClientService extends Remote
{
/*
* 由服务端主动推送消息到客户端,客户端刷新聊天信息
*/
public void showMsg(String msg) throws RemoteException;
}
服务端远程接口ServerService:
package chapter13_2.rmi;
import java.rmi.Remote;
/**
* 服务端远程对象接口,该接口为客户端提供服务
* 由服务端实现以下远程方法
*/
public interface ServerService extends Remote {
// 客户端调用服务器方法,进行登陆操作
String login(String client, ClientService clientService) throws RemoteException;
}
创建客户端远程接口实现类:
package chapter13_2.client;
import chapter13_2.rmi.ClientService;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class ClientServiceImpl extends UnicastRemoteObject implements ClientService {
protected ClientServiceImpl() throws RemoteException {
}
@Override
public void showMsg(String msg) throws RemoteException {
//客户端刷新窗体信息显示的方法
System.out.println(msg);
//...
}
}
创建服务端远程接口实现类:
package chapter13_2.server;
import chapter13_2.rmi.ClientService;
import chapter13_2.rmi.ServerService;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.concurrent.ConcurrentHashMap;
public class ServerServiceImpl extends UnicastRemoteObject implements ServerService{
//存储在线用户的map
private static ConcurrentHashMap<String, ClientService> onlineGroup = new ConcurrentHashMap<>();
// constructor
public ServerServiceImpl() throws RemoteException {
}
// 实现接口的方法
@Override
public String login(String client, ClientService clientService) throws RemoteException {
boolean isLogin = false;
//避免反复登录,关键是判断在线map中是否已经存在相同的clientService
for (String onlineUser : onlineGroup.keySet()) {
ClientService cs = onlineGroup.get(onlineUser);
if(cs == clientService){
isLogin = true;
}
}
// 如果没登陆
if (!isLogin) {
// 加入到用户map中
onlineGroup.put(client.trim(), clientService);
// 群发新用户上线的信息
return "From 服务器:" + client.trim() + " 登陆!";
}
else {
return "From 服务器:不要反复登录";
}
}
}
创建服务端的远程服务发布程序:
package chapter13_2.server;
import chapter13_2.rmi.ServerService;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class Server {
public static void main(String[] args) throws RemoteException {
try {
//对于有多个网卡的机器,建议用下面的命令指绑定固定的ip,因为默认是绑定到0.0.0.0
//System.setProperty("java.rmi.server.hostname",本机器的ip地址);
//第一步,启动RMI注册器
Registry registry = LocateRegistry.createRegistry(8008);
//第二步,实例化远程服务对象
ServerService serverService = new ServerServiceImpl();
//第三步,用助记符来注册及发布远程服务对象,助记符建议和远程服务接口命名相同,方便使用
registry.rebind("ServerService", serverService);
System.out.println("服务端发布了ServerService远程服务");
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
客户端测试程序:
package chapter13_2.client;
import chapter13_2.rmi.ClientService;
import chapter13_2.rmi.ServerService;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class ClientTest {
public static void main(String[] args) throws RemoteException, NotBoundException {
String ip = "localhost";
//为了不和上一讲端口冲突,临时修改为8008,一般不做特别说明,是使用1099
int port = 8008;
//获取RMI注册器
Registry registry = LocateRegistry.getRegistry(ip, port);
for (String name : registry.list()) {
System.out.println(name);
}
//客户端(调用端)到注册器中使用助记符寻找并创建远程服务对象的客户端(调用端)stub,
// 之后本地调用serverService的方法,实质就是调用了远程同名接口下的同名方法
ServerService serverService = (ServerService) registry.lookup("ServerService");
ClientService clientService = new ClientServiceImpl();
String res = serverService.login("Ananyo", clientService);
System.out.println(res);
}
}
启动服务器,和测试代码。
效果展示:

我们将自己的远程接口实现类作为参数传递给服务端,而不需要特意进行远程服务的发布,这里有别于RMI程序设计(一)。
远程接口实现类作为参数传递&spm=1001.2101.3001.5002&articleId=110371164&d=1&t=3&u=3dc5687c634443d6a5fda3ee08e9a35a)
1193

被折叠的 条评论
为什么被折叠?



