目录
什么是 RPC?
- RPC(Remote Procedure Call ——远程过程调用),它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络的技术。
- 一次完整的 RPC 同步调用流程:
1)服务消费方(client)以本地调用方式调用客户端存根;
2)什么叫客户端存根?就是远程方法在本地的模拟对象,一样的也有方法名,也有方法参数,client stub(客户端存根) 接收到调用后负责将方法名、方法的参数等包装,并将包装后的信息通过网络发送到服务端;
3)服务端收到消息后,交给代理存根在服务器的部分后进行解码为实际的方法名和参数;
4)server stub(服务端存根) 根据解码结果调用服务器上本地的实际服务;
5)本地服务执行并将结果返回给 server stub;
6)server stub 将返回结果打包成消息并发送至消费方;
7)client stub 接收到消息,并进行解码;
8)服务消费方得到最终结果。 - RPC 框架的目标就是要中间步骤都封装起来,让我们进行远程方法调用的时候感觉到就 像在本地调用一样。
RPC 和 HTTP
- rpc 字面意思就是远程过程调用,只是对不同应用间相互调用的一种描述,一种思想。 具体怎么调用?实现方式可以是最直接的 tcp 通信,也可以是 http 方式,在很多的消息中间件的技术书籍里,甚至还有使用消息中间件来实现 RPC 调用的,我们知道的 dubbo 是基于 tcp 通信的,gRPC 是 Google 公布的开源软件,基于最新的 HTTP2.0 协议,底层使用到了 Netty 框架的支持。所以总结来说,rpc 和 http 是完全两个不同层级的东西,他们之间并没有什么可比性。
实现 RPC 框架
- 实现 RPC 框架需要解决的那些问题
代理问题
- 代理本质上是要解决什么问题?要解决的是被调用的服务本质上是远程的服务,但是调用者不知道也不关心,调用者只要结果,具体的事情由代理的那个对象来负责这件事。既然是远程代理,当然是要用代理模式了。
序列化问题
- 序列化问题在计算机里具体是什么?我们的方法调用,有方法名,方法参数,这些可能是字符串,可能是我们自己定义的 java 的类,但是在网络上传输或者保存在硬盘的时候, 网络或者硬盘并不认得什么字符串或者 javabean,它只认得二进制的 0、1 串,所以要进行序列化,网络传输后要进行实际调用,就要把二进制的 0、1 串变回我们实际的 java 的类, 这个叫反序列化。java 里已经为我们提供了相关的机制 Serializable。
通信问题
- 我们在用序列化把东西变成了可以在网络上传输的二进制的 0、1 串,但具体如何通过网络传输?使用 JDK 为我们提供的 BIO。
登记的服务实例化
-
登记的服务有可能在我们的系统中就是一个名字,怎么变成实际执行的对象实例,当然是使用反射机制。
-
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对 于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。
-
反射机制主要提供了以下功能:
1、在运行时判断任意一个对象所属的类;
2、在运行时构造任意一个类的对象;
3、在运行时判断任意一个类所具有的成员变量和方法;
4、在运行时调用任意一个对象的方法;
5、生成动态代理。 -
下面实现一个简单rpc框架,像负载均衡、容灾、集群、断线重连这些复杂功能就不实现了。
创建注册中心
1. pom.xml
- 使用一个最简单的web项目
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>rpc-reg</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rpc-reg</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. properties
- 设置服务的端口,因为后面还有服务端和客户端
server.port=8080
3. 启动类
package com.example.rpc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RpcRegApplication {
public static void main(String[] args) {
SpringApplication.run(RpcRegApplication.class, args);
}
}
4. 注册中心实体类
- 要实现序列化接口
package com.example.rpc.remote.vo;
import java.io.Serializable;
//注册中心实体
public class RegisterVO implements Serializable {
private String host;
private int port;
public RegisterVO(String host, int port) {
this.host = host;
this.port = port;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
5. 注册中心实现类
- 服务端启动时在注册中心注册自己的信息,客户端在启动时查询服务端信息
package com.example.rpc.service;
import com.example.rpc.remote.vo.RegisterVO;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//注册中心,服务端启动时在注册中心注册自己的信息,客户端在启动时查询服务端信息
@Service
public class RegisterCenter {
private static ExecutorService executorService =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//key表示服务名,value表示服务地址的集合
private static final Map<String, Set<RegisterVO>> serviceHoler = new HashMap<>();
//注册服务的端口号
private int port;
//服务注册
private static synchronized void registerService(String serviceName, String host, int port) {
//获得当前服务的已有地址集合
Set<RegisterVO> voSet = serviceHoler.get(serviceName);
if (voSet == null) {
//已有地址集合为空,则新增集合
voSet = new HashSet<>();
serviceHoler.put(serviceName, voSet);
System.out.println("服务注册成功," + serviceName + "," + host + ":" + port);
}
//把新加入的服务地址加入集合
voSet.add(new RegisterVO(host, port));
System.out.println("服务地址添加成功," + host + ":" + port);
}
//取出服务提供者
private static Set<RegisterVO> getService(String serviceName) {
return serviceHoler.get(serviceName);
}
//处理服务请求的任务,1、注册服务,2、查询服务
private static class ServerTask implements Runnable {
private Socket socket = null;
public ServerTask(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try (ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream())) {
//检查当前请求是注册还是查询
boolean isGetService = inputStream.readBoolean();
//查询服务
if (isGetService) {
String serviceName = inputStream.readUTF();
//取出服务提供者的地址集合
Set<RegisterVO> voSet = getService(serviceName);
//返回给客户端
outputStream.writeObject(voSet);
outputStream.flush();
System.out.println("将查询的服务-" + serviceName + "发送给客户端");
}
//注册服务
else {
//获取注册的服务名,ip,端口
String serviceName = inputStream.readUTF();
String host = inputStream.readUTF();
int port = inputStream.readInt();
//注册中心保存
registerService(serviceName, host, port);
outputStream.writeBoolean(true);
outputStream.flush();
System.out.println("服务注册成功:" + serviceName);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void startService() throws IOException {
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(port));
System.out.println("注册中心 on:" + port);
try {
while (true) {
executorService.execute(new ServerTask(serverSocket.accept()));
}
} finally {
serverSocket.close();
}
}
//启动注册中心服务
@PostConstruct
public void init() {
this.port = 9001;
new Thread(() -> {
try {
startService();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
创建服务端
1. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>rpc-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rpc-server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. properties
server.port=8081
3. 启动类
package com.example.rpc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RpcServerApplication {
public static void main(String[] args) {
SpringApplication.run(RpcServerApplication.class, args);
System.out.println("hello world");
}
}
4. 服务端发送消息的实体类
package com.example.rpc.remote.vo;
import java.io.Serializable;
public class UserInfo implements Serializable {
private String name;
private String phone;
public UserInfo(String name, String phone) {
this.name = name;
this.phone = phone;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
5. 对外提供的业务方法
package com.example.rpc.remote;
import com.example.rpc.remote.vo.UserInfo;
public interface SendSms {
//发送短信
boolean sendMsg(UserInfo userInfo);
}
6. 业务方法具体实现
package com.example.rpc.sms;
import com.example.rpc.remote.SendSms;
import com.example.rpc.remote.vo.UserInfo;
import org.springframework.stereotype.Service;
//短信息发送服务的实现
@Service
public class SendSmsImpl implements SendSms {
@Override
public boolean sendMsg(UserInfo userInfo) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("已发送短信到:" + userInfo.getName());
return true;
}
}
7. 完成服务注册
package com.example.rpc.rpc.base;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
//注册服务
@Service
public class RegisterServiceWithRegCenter {
//本地可提供服务的一个名单,用缓存实现
private static final Map<String, Class> serviceCache = new ConcurrentHashMap<>();
//往远程服务器注册本服务,同时在本地注册本服务
public void regRemote(String serverName, String host, int port, Class impl) throws IOException {
//登记到注册中心
Socket socket = new Socket();
//服务端的通信地址
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 9001);
//连接服务器
socket.connect(inetSocketAddress);
//实例化与客户端通信的输入输出流,先输出再输入
try (ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream())) {
//用false表示注册服务,true表示查询服务
outputStream.writeBoolean(false);
//提供服务名
outputStream.writeUTF(serverName);
//提供ip
outputStream.writeUTF(host);
//提供端口
outputStream.writeInt(port);
outputStream.flush();
//接收服务端的输出
if (inputStream.readBoolean()) {
System.out.println(serverName + "服务注册成功");
}
//可提供的服务放入本地缓存
serviceCache.put(serverName, impl);
} finally {
if (socket != null) {
socket.close();
}
}
}
//获取服务
public Class getLocalService(String serviceName) {
return serviceCache.get(serviceName);
}
}
8. 服务端实现
- 支持远程过程调用,提供业务方法
package com.example.rpc.rpc.base;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//服务端
@Service
public class RpcServerFrame {
private static ExecutorService executorService =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
@Autowired
private RegisterServiceWithRegCenter registerServiceWithRegCenter;
//处理服务请求任务
private static class ServerTask implements Runnable {
private Socket socket;
private RegisterServiceWithRegCenter registerServiceWithRegCenter;
public ServerTask(Socket socket, RegisterServiceWithRegCenter registerServiceWithRegCenter) {
this.socket = socket;
this.registerServiceWithRegCenter = registerServiceWithRegCenter;
}
@Override
public void run() {
try (ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream())) {
//方法所在的类名
String serviceName = inputStream.readUTF();
//方法名
String methodName = inputStream.readUTF();
//方法的入参类型
Class<?>[] paramTypes = (Class<?>[]) inputStream.readObject();
//方法的入参值
Object[] args = (Object[]) inputStream.readObject();
//从容器中拿到服务的class对象
Class serviceClass = registerServiceWithRegCenter.getLocalService(serviceName);
if (serviceClass == null) {
throw new ClassNotFoundException(serviceName + "not found");
}
//通过反射,执行实际的方法
Method method = serviceClass.getMethod(methodName, paramTypes);
Object result = method.invoke(serviceClass.newInstance(), args);
//将服务的执行结果返回给调用者
outputStream.writeObject(result);
outputStream.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public void startService(String serverName, String host, int port, Class impl) throws Throwable {
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(port));
System.out.println("server is on :" + port);
registerServiceWithRegCenter.regRemote(serverName, host, port, impl);
try {
while (true) {
executorService.execute(new ServerTask(serverSocket.accept(), registerServiceWithRegCenter));
}
} finally {
serverSocket.close();
}
}
}
9. 开启服务
package com.example.rpc.sms;
import com.example.rpc.remote.SendSms;
import com.example.rpc.rpc.base.RpcServerFrame;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.Random;
//rpc的服务端,提供服务
@Service
public class SmsRpcServer {
@Autowired
private RpcServerFrame rpcServerFrame;
@PostConstruct
public void server() {
Random random = new Random();
int port = 8888 + random.nextInt(100);
try {
//往远程服务器注册本服务,并提供业务类
rpcServerFrame.startService(SendSms.class.getName(), "127.0.0.1", port, SendSmsImpl.class);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
创建客户端
1. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>rpc-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rpc-client</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. properties
server.port=8082
3. 启动类
package com.example.rpc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RpcClientApplication {
public static void main(String[] args) {
SpringApplication.run(RpcClientApplication.class, args);
}
}
4. 存根远端服务的类
- 如果要想像使用自己项目中的方法一样,使用远端服务上的方法,就必须本地存根
- 这里需要存根包括注册中心的实体类RegisterVO,和注册中心项目的代码一样
package com.example.rpc.remote.vo;
import java.io.Serializable;
//注册中心实体
public class RegisterVO implements Serializable {
private String host;
private int port;
public RegisterVO(String host, int port) {
this.host = host;
this.port = port;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
- 服务端的实体类UserInfo
package com.example.rpc.remote.vo;
import java.io.Serializable;
public class UserInfo implements Serializable {
private String name;
private String phone;
public UserInfo(String name, String phone) {
this.name = name;
this.phone = phone;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
- 服务端的发送信息的接口
package com.example.rpc.remote;
import com.example.rpc.remote.vo.UserInfo;
public interface SendSms {
//发送短信
boolean sendMsg(UserInfo userInfo);
}
- 这里需要注意的是,本地存根的类的全限定名要和远端类的全限定名一致,不然当你通过反射在服务端去获取类的时候会找不到,比如下面这个类的全限定名在客户端和服务端都是com.example.rpc.remote.SendSms
5. 客户端实现
- 通过动态代理调用远端服务的方法
package com.example.rpc.client.rpc;
import com.example.rpc.remote.vo.RegisterVO;
import org.springframework.stereotype.Service;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Set;
//客户端代理部分
@Service
public class RpcClientFrame {
//获得服务提供方的地址列表
private static List<InetSocketAddress> getAddressList(String serviceName) throws Exception {
Socket socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1", 9001));
try (ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream())) {
//查询服务列表
outputStream.writeBoolean(true);
//发送服务名称
outputStream.writeUTF(serviceName);
outputStream.flush();
Set<RegisterVO> voSet = (Set<RegisterVO>) inputStream.readObject();
List<InetSocketAddress> services = new ArrayList<>();
for (RegisterVO registerVO : voSet) {
String host = registerVO.getHost();
int port = registerVO.getPort();
InetSocketAddress address = new InetSocketAddress(host, port);
services.add(address);
}
System.out.println("查询服务列表成功:" + serviceName);
return services;
} finally {
if (socket != null) {
socket.close();
}
}
}
//获取的远程服务的地址
private static InetSocketAddress getService(String serviceName) throws Exception {
Random random = new Random();
//获得服务提供方的地址列表
List<InetSocketAddress> addressList = getAddressList(serviceName);
//随机选一台
InetSocketAddress address = addressList.get(random.nextInt(addressList.size()));
System.out.println("本次调用选择的服务器是:" + address);
return address;
}
//动态代理,实现对远程服务的访问
private static class DynProxy implements InvocationHandler {
private Class<?> serviceInterface;
private InetSocketAddress socketAddress;
public DynProxy(Class<?> serviceInterface, InetSocketAddress socketAddress) {
this.serviceInterface = serviceInterface;
this.socketAddress = socketAddress;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket socket = new Socket();
socket.connect(socketAddress);
try (ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream())) {
//方法所在类的名字
outputStream.writeUTF(serviceInterface.getName());
//方法名
outputStream.writeUTF(method.getName());
//方法入参类型
outputStream.writeObject(method.getParameterTypes());
//方法入参
outputStream.writeObject(args);
outputStream.flush();
//接收服务器的输出
System.out.println(serviceInterface + "remote exec success !");
return inputStream.readObject();
} finally {
if (socket != null) {
socket.close();
}
}
}
}
//远程服务的代理对象,参数为客户端要调用的服务
public static Object getRemoteProxyObject(Class serviceInterface) throws Exception {
//获得远程服务的一个网络地址
InetSocketAddress address = getService(serviceInterface.getName());
//拿到一个代理对象,由代理对象通过网络进行实际的服务调用
return Proxy.newProxyInstance(serviceInterface.getClassLoader(),
new Class[]{serviceInterface}, new DynProxy(serviceInterface, address));
}
}
6. 注入远端服务的bean
- 通过@Bean把SendSms加到spring容器中,SendSms实际是一个代理对象
package com.example.rpc.client.config;
import com.example.rpc.client.rpc.RpcClientFrame;
import com.example.rpc.remote.SendSms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ClientConfig {
@Autowired
private RpcClientFrame rpcClientFrame;
//注入远端服务的SendSms类,可以在controller中使用其中的方法
@Bean
public SendSms getSendSms() throws Exception {
return (SendSms) rpcClientFrame.getRemoteProxyObject(SendSms.class);
}
}
7. 创建Controller
- 用于调用服务端SendSms类中发送信息的方法
package com.example.rpc.Controller;
import com.example.rpc.remote.SendSms;
import com.example.rpc.remote.vo.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ClientController {
@Autowired
private SendSms sendSms;
@RequestMapping(value = "sendMsg")
public void sendMsg() {
long start = System.currentTimeMillis();
UserInfo userInfo = new UserInfo("fisher", "123456");
System.out.println("send message: " + sendSms.sendMsg(userInfo));
System.out.println("共耗时:" + (System.currentTimeMillis() - start) + "ms");
}
}
8. 启动服务,查看测试结果
- 先启动注册中心,再启动服务端,最后启动客户端
- 注册功能使用9001端口,启动服务端后,服务端的地址127.0.0.1:8940成功注册到注册中心,最后启动客户端,客户端会到注册中心查询服务列表
- 请求http://localhost:8082/sendMsg
- 客户端打印
- 服务端打印