网络通信基本常识
-
编程中的Socket是什么?
-
Socket是应用层与TCP/IP协议族通信的间软件抽象层,它是一组接口,其实就是一个门面模式。TCP用主机的IP地址加上主机上的端口号作为TCP连接的端点,这种端点就叫做套接字(socket)。
-
socket是一种门面模式,屏蔽了与机器的交流过程,方便业务程序员开发代码
-
A的socket ---- B的socket
两个socket建立关系,从而A和B就可以通信了
-
-
短连接
-
一次通讯完了马上就关闭连接
-
服务器要服务成千上亿的服务
-
短连接和长连接到底如何选择,需要根据实际情况
-
-
长连接
-
一次连接内会进行多次通讯
扫描二维码关注公众号,回复: 12473595 查看本文章 -
维持住长连接主要消耗的是内存和FD文件描述符
-
如果服务器性能好,可以维持上百万的长连接
-
操作频繁、或者点对点的通讯上,连接池来维护这种长连接
-
-
InetAddress类 --> 只表示地址(主机)
-
InetAddress address = InetAddress.getByName(“www.baidu.com”)
-
可以获得百度的ip地址
-
怎么获得的?-- dns解析
-
InetAddress address2 = InetAddress.getByName(“124.232.170.22”)
System.out.println(address2.getHostName())
同样可以根据ip地址获取域名,如果找不到,就会打印源ip地址
-
InetAddress[] allIp= InetAddress.getAllByName(“www.baidu.com”)
获取百度域名下面所有的ip地址
-
把一个ip地址包装成InetAddress对象
byte[] bytes = {(byte)192,(byte)168,56,1};
InetAddress address = InetAddress.getByAddress(bytes);
InetSocketAddress类 --> 主机名+端口
NetoworkInterface类
-
打开设备管理器-网络适配器
NetoworkInterface会把这些全部找出来,除了这些,还有别的
// 127.0.0.1-------本地回环接口
InetAddress address = InetAddress.getByName(“127.0.0.1”);
NetworkInterface byInetAddress = NetoworkInterface.getByInetAddress(address);
// 获取所有的网络通讯接口类
// 包括本地回环、网络适配器、wifi、广域网、硬件设备通讯
NetoworkInterface.getNetoworkInterfaces();
-
-
服务端、客户端、通信编程关注的三件事
-
所有网络通讯一定有服务端和客户端这两样
-
提供服务的称为服务端
-
连接服务的称为客户端
-
某个类有Server、ServerSocket,那么这个类往往是给服务端使用的
ServerSocket只是个容器,容纳网络服务用的
-
某个类只有socket,一般是负责具体的网络读写
真实网络通讯中,真正进行网络读写的都是socket
-
-
网络编程中一定要关注的点
- 网络连接
- 读网络数据
- 写网络数据
-
jdk网络通讯
-
bio
-
nio
-
aio—非主流
-
原生JDK网络编程-BIO
-
BIO:block ,即阻塞式io
-
ServerSocket负责绑定IP地址,启动监听端口;
-
Socket负责发起连接操作。连接成功后,双方通过输入和输出流进行同步阻塞式通信
-
ServerSocket启动了处于监听状态 通过accept()方法来响应请求的socket() 新生成一个线程,在线程中生成一个socket()来与请求的socket()通讯
服务器端
-
package WangLuoBianCheng.wangLuoBianCheng3; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; public class Server{ public static void main(String[] args)throws IOException { ServerSocket server = new ServerSocket(); server.bind(new InetSocketAddress(10001)); System.out.println("Server is started..."); while(true){ // 来一个socket请求,就会new一个线程,同时new 一个socket // 执行server.accept()成功,如果三次握手完成会返回一个Socket // 更优的写法是用线程池,实现复用 new Thread(new ServerTask(server.accept())).start(); } } private static class ServerTask implements Runnable{ private Socket socket = null; public ServerTask(Socket socket) { this.socket = socket; } @Override public void run(){ try (ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream())){ String userName = input.readUTF(); System.out.println("Accept client Message:"+userName); output.writeUTF("Hello,"+userName); // 上一句只是写入缓存,准备发送 // 强制刷出 output.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
客户端
-
package WangLuoBianCheng.wangLuoBianCheng3; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.InetSocketAddress; import java.net.Socket; public class Client { public static void main(String[] args) throws IOException { // 用来通讯的socket Socket socket = null; // 注意客户端必须是输出在上,输入在下,确保流通道建立起来 // 因为如果两端都是输入,怎么传递? // 或者两端都是先创建输出流,再创建输入流,未测试? ObjectOutputStream objectOutputStream = null; InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 10001); ObjectInputStream objectInputStream = null; try { socket = new Socket(); // 建立连接 socket.connect(inetSocketAddress); objectOutputStream = new ObjectOutputStream(socket.getOutputStream()); objectInputStream = new ObjectInputStream(socket.getInputStream()); // 发出消息 objectOutputStream.writeUTF("James"); objectOutputStream.flush(); // 接受服务器响应的信息并打印 // 执行完后关闭了网络连接 System.out.println(objectInputStream.readUTF()); } catch (IOException e) { e.printStackTrace(); } finally { if (socket!=null) socket.close(); if (objectOutputStream!=null) objectOutputStream.close(); if (objectInputStream!=null) objectInputStream.close(); } } }
改进—伪异步IO模型
-
上述服务器端是来一个socket请求,就会new一个线程,同时new 一个socket
-
为了实现线程复用,应该采用线程池的线程来处理任务,而这种模式又称为伪异步IO模型
-
package WangLuoBianCheng.wangLuoBianCheng3; 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.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ServerPool { private static ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*2); public static void main(String[] args)throws IOException { ServerSocket server = new ServerSocket(); server.bind(new InetSocketAddress(10001)); System.out.println("Server is started..."); while(true){ // 执行server.accept()成功,如果三次握手完成会返回一个Socket executorService.execute(new ServerTask(server.accept())); } } private static class ServerTask implements Runnable{ private Socket socket = null; public ServerTask(Socket socket) { this.socket = socket; } @Override public void run(){ try (ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream())){ String userName = input.readUTF(); System.out.println("Accept client Message:"+userName); output.writeUTF("Hello,"+userName); // 强制刷出 output.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
BIO应用-RPC框架
-
单WEB项目
- 1.订单服务
- 2.扣减库存服务
- 3.发送短信提示服务
-
之前上述三个服务全是单线程执行,当调用服务的用户数很大时,就不能充分发挥并发编程的高效,因此考虑将三个服务拆分,都分别由单独的线程来执行
-
进一步把不同的服务部署到不同的服务器上,而且每个服务是通过服务器集群的形式来部署,从而形成了分布式的架构
- 这时不同服务之间的调用就需要引入rpc,因为是跨服务器跨网络的调用,不再是单机内部的方法调用
什么是RPC?
- RPC(Remote Procedure Call ——远程过程调用),它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络的技术。–来自《百度百科》
客户端存根–server stub
-
扣减库存服务,为了使调用远端服务和调用本地服务没有什么区别,就需要引入客户端存根
-
客户端存根就是远程方法在本地的模拟,包括方法名、方法参数
服务端存根–client stub
- 负责把客户端请求发来的请求体,解析成跟服务器实际方法相同的方法名、方法参数
总结
- rpc方法就是把上述过程全部包装起来,使得调用远程方法就跟调用本地方法一样
RPC和HTTP
-
TCP/IP概念层模型 功能 TCP/IP协议族 文件传输、电子邮件、文件服务、虚拟终端 TFTP、HTTP、SNMP、FTP、SMTP、DNS、Telnet 应用层 数据格式化、代码转换、数据加密 无 解除或建立与别的接点的联系 无 传输层 提供端与端的接口 TCP、UDP 网络层 为数据包选择路由 IP、ICMP、RIP、OSPF、BGP、IGMP 链路层 传输有地址的帧以及错误检测功能 SLIP、CSLIP、PPP、ARP、RARP、MTU 以二进制数据形式在物理媒体上传输数据 ISO2110、IEEE802、IEEE802.2
rpc究竟在上面协议分层的哪一层?
- rpc只是一种思想,对不同服务调用的一种描述,既可以通过http实现,也可以通过tcp、htp,所以rpc和http不是一个层级的东西
实现RPC框架需要解决的那些问题?
-
通信问题
- 不要每次方法调用,都需要建立socket连接
-
代理问题
-
可以不可以通过代理的方式,每次的通信都通过指定的代理来解决
-
代理问题的解决
代理模式,用动态代理,为什么不用静态代理?-- 针对每一个服务都要创建相应的代理类,而rpc框架不知道要建立哪些静态代理,所以使用动态代理,不管你要调用什么服务,通过动态代理一把解决
-
-
序列化问题
-
消息在网络中传输是字节的形式,怎么把01字节变成javaBean的形式
-
序列化问题的解决
实现Serializable接口,但是jdk的序列化性能很差
测试结果1-----jdk的字节码长度:133 自己字节码长度:24
测试结果2-----jdk序列化耗时:1364ms 自己序列化耗时:108ms
-
-
服务实例化
-
方法调用时只有一个方法名和方法参数,怎么转化成调用具体的服务
-
服务实例化的解决
通过反射解决
-
序列化耗时测试
测试1
-
package WangLuoBianCheng.wangLuoBianCheng3.rpc.prepare.serial.protogenesis; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; /** * @author Mark老师 享学课堂 https://enjoy.ke.qq.com * 类说明:测试序列化后字节大小 */ public class TestUserInfo { /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { UserInfo info = new UserInfo(); info.buildUserID(100).buildUserName("Welcome to Netty"); //使用jdk的序列化 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream os = new ObjectOutputStream(bos); os.writeObject(info); os.flush(); os.close(); byte[] b = bos.toByteArray(); System.out.println("The jdk serializable length is : " + b.length); bos.close(); //使用自行的序列化 System.out.println("-------------------------------------"); System.out.println("The byte array serializable length is : " + info.codeC().length); } }
测试2
-
package WangLuoBianCheng.wangLuoBianCheng3.rpc.prepare.serial.protogenesis; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.nio.ByteBuffer; /** * @author Mark老师 享学课堂 https://enjoy.ke.qq.com * 类说明:测试序列化性能差异 */ public class PerformTestUserInfo { public static void main(String[] args) throws IOException { UserInfo info = new UserInfo(); info.buildUserID(100).buildUserName("Welcome to Netty"); int loop = 1000000; //使用jdk的序列化 ByteArrayOutputStream bos = null; ObjectOutputStream os = null; long startTime = System.currentTimeMillis(); for (int i = 0; i < loop; i++) { bos = new ByteArrayOutputStream(); os = new ObjectOutputStream(bos); os.writeObject(info); os.flush(); os.close(); byte[] b = bos.toByteArray(); bos.close(); } long endTime = System.currentTimeMillis(); System.out.println("The jdk serializable cost time is : " + (endTime - startTime) + " ms"); //使用自行的序列化 System.out.println("-------------------------------------"); ByteBuffer buffer = ByteBuffer.allocate(1024); startTime = System.currentTimeMillis(); for (int i = 0; i < loop; i++) { byte[] b = info.codeC(buffer); } endTime = System.currentTimeMillis(); System.out.println("The byte array serializable cost time is : " + (endTime - startTime) + " ms"); } }
实现rpc框架
- 1.服务端定义接口和服务实现类并且注册服务
- 2.客户端使用动态代理调用服务(动态代理)
- 3.客户端代理把调用对象、方法、参数序列化成数据
- 4.客户端代理与服务端通过Socket通讯传输数据
- 5.服务端反序列化数据成对象、方法、参数。
- 6.服务端代理拿到这些对象和参数后通过反射的机制调用服务的实例。
将短信服务拆分成rpc服务—服务端
- 服务实体类,服务需要的相关参数类型 -----> 这些可以看出服务端存根
所有服务使用的服务框架----RpcServerFrame
-
package cn.enjoyedu.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; /** *@author Mark老师 * *类说明:rpc框架的服务端部分 */ @Service public class RpcServerFrame { // @Autowired // private RegisterService registerService; @Autowired private RegisterServiceWithRegCenter registerServiceWithRegCenter; //服务的端口号 private int port; /*处理服务请求任务*/ private static class ServerTask implements Runnable{ private Socket socket; private RegisterServiceWithRegCenter registerServiceWithRegCenter; public ServerTask(Socket client, RegisterServiceWithRegCenter registerServiceWithRegCenter) { this.socket = client; this.registerServiceWithRegCenter = registerServiceWithRegCenter; } // 接受客户端的请求,并调用实际的方法 // 接受的内容---1.方法所在的类名接口名 2.调用的方法名 3.方法参数及方法属性、具体的参数值 // @Override public void run() { try( ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream()); ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream())){ /*方法所在类名接口名*/ // 读字符串inputStream.readUTF() String serviceName = inputStream.readUTF(); /*方法的名字*/ String methodName = inputStream.readUTF(); /*方法的入参类型*/ // 读对象inputStream.readObject() // 强制转型,转成类 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 method = serviceClass.getMethod(methodName, paramTypes); // 通过动态代理调用响应的方法 Object result = method.invoke(serviceClass.newInstance(),args); /*将服务的执行结果通知调用者*/ // 将调用结果输出给调用者,即客户端 outputStream.writeObject(result); outputStream.flush(); }catch (Exception e){ e.printStackTrace(); }finally { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } public void startService(String serviceName, String host, int port, Class impl) throws Throwable{ ServerSocket serverSocket = new ServerSocket(); serverSocket.bind(new InetSocketAddress(port)); System.out.println("RPC server on:"+port+":运行"); // registerService.regRemote(ServiceName, impl); registerServiceWithRegCenter.regRemote(serviceName,host,port,impl); try{ while(true){ new Thread(new ServerTask(serverSocket.accept(), registerServiceWithRegCenter)).start(); } }finally { serverSocket.close(); } } }
服务注册中心
-
package cn.enjoyedu.rpc.rpc.base; import org.springframework.stereotype.Service; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 类说明:注册服务到本地缓存 */ @Service public class RegisterService { /*本地可以提供服务的一个容器*/ private static final Map<String,Class> serviceCache = new ConcurrentHashMap<>(); /*注册本服务*/ public void regService(String serviceName,Class impl){ serviceCache.put(serviceName,impl); } /*获取服务*/ public Class getLocalService(String serviceName){ return serviceCache.get(serviceName); } }
短信服务启动
SmsRpcServer
-
package cn.enjoyedu.rpc.rpc.sms; import cn.enjoyedu.rpc.rpc.base.RpcServerFrame; import cn.enjoyedu.rpc.remote.SendSms; 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 { // rpc的base框架部分注入 @Autowired private RpcServerFrame rpcServerFrame; @PostConstruct public void server() throws Throwable { Random r = new Random(); // 端口号 int port = 8778+r.nextInt(100); // 启动服务 rpcServerFrame.startService(SendSms.class.getName(), "127.0.0.1",port,SendSmsImpl.class); } }
SendSmsImpl—具体的服务实现类
-
package cn.enjoyedu.rpc.rpc.sms; import cn.enjoyedu.rpc.remote.vo.UserInfo; import cn.enjoyedu.rpc.remote.SendSms; /** *@author Mark老师 享学课堂 https://enjoy.ke.qq.com * *类说明:短信息发送服务的实现 */ public class SendSmsImpl implements SendSms { @Override public boolean sendMail(UserInfo user) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("已发送短信息给:"+user.getName()+"到【"+user.getPhone()+"】"); return true; } }
将短信服务拆分成rpc服务—客户端
- 类似于服务器端存根,也需要有客户端存根
客户端框架类
-
package cn.enjoyedu.rpc.client.rpc; import cn.enjoyedu.rpc.remote.vo.RegisterServiceVo; 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; /** *@author Mark老师 *类说明:rpc框架的客户端代理部分 */ @Service public class RpcClientFrame { /*远程服务的代理对象,参数为客户端要调用的的服务*/ // 传递的应该是方法所在的类名或接口名 public static<T> T getRemoteProxyObject(final Class<?> serviceInterface) throws Exception { /*获得远程服务的一个网络地址*/ InetSocketAddress addr = //new InetSocketAddress("127.0.0.1",8778); getService(serviceInterface.getName()); /*拿到一个代理对象,由这个代理对象通过网络进行实际的服务调用*/ // 应该产生一个代理对象,和服务器进行通讯 // 需要实现了InvocationHandler的类DynProxy return (T)Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class<?>[]{ serviceInterface}, new DynProxy(serviceInterface,addr)); } /*动态代理,实现对远程服务的访问*/ // 连接服务器,发送接口名、方法名、方法参数 private static class DynProxy implements InvocationHandler{ private Class<?> serviceInterface; private InetSocketAddress addr; public DynProxy(Class<?> serviceInterface, InetSocketAddress addr) { this.serviceInterface = serviceInterface; this.addr = addr; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Socket socket = null; ObjectInputStream inputStream = null; ObjectOutputStream outputStream = null; try{ // 1.建立网络连接 socket = new Socket(); socket.connect(addr); outputStream = new ObjectOutputStream(socket.getOutputStream()); // 2.发送接口名、方法名、参数类型、参数值 //方法所在类名接口名 outputStream.writeUTF(serviceInterface.getName()); //方法的名字 outputStream.writeUTF(method.getName()); //方法的入参类型 outputStream.writeObject(method.getParameterTypes()); //方法入参的值 outputStream.writeObject(args); outputStream.flush(); // 3.接受服务器传回的结果 inputStream = new ObjectInputStream(socket.getInputStream()); /*接受服务器的输出*/ System.out.println(serviceInterface+" remote exec success!"); return inputStream.readObject(); }finally { if(socket!=null) socket.close(); if(outputStream!=null) outputStream.close(); if(inputStream!=null) inputStream.close(); } } } /*----------------以下和动态获得服务提供者有关------------------------------*/ private static Random r = new Random(); /*获得远程服务的地址*/ private static InetSocketAddress getService(String serviceName) throws Exception { //获得服务提供者的地址列表 List<InetSocketAddress> serviceVoList = getServiceList(serviceName); InetSocketAddress addr = serviceVoList.get(r.nextInt(serviceVoList.size())); System.out.println("本次选择了服务器:"+addr); return addr; } /*获得服务提供者的地址*/ private static List<InetSocketAddress> getServiceList(String serviceName) throws Exception { Socket socket = null; ObjectOutputStream output = null; ObjectInputStream input = null; try{ socket = new Socket(); socket.connect(new InetSocketAddress("127.0.0.1",9999)); output = new ObjectOutputStream(socket.getOutputStream()); //需要获得服务提供者 output.writeBoolean(true); //告诉注册中心服务名 output.writeUTF(serviceName); output.flush(); input = new ObjectInputStream(socket.getInputStream()); Set<RegisterServiceVo> result = (Set<RegisterServiceVo>)input.readObject(); List<InetSocketAddress> services = new ArrayList<>(); for(RegisterServiceVo serviceVo : result){ String host = serviceVo.getHost();//获得服务提供者的IP int port = serviceVo.getPort();//获得服务提供者的端口号 InetSocketAddress serviceAddr = new InetSocketAddress(host,port); services.add(serviceAddr); } System.out.println("获得服务["+serviceName +"]提供者的地址列表["+services+"],准备调用."); return services; }finally{ if (socket!=null) socket.close(); if (output!=null) output.close(); if (input!=null) input.close(); } } }
远端的服务怎么通过bean接管
-
package cn.enjoyedu.rpc.client.config; import cn.enjoyedu.rpc.client.rpc.RpcClientFrame; import cn.enjoyedu.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 BeanConfig { @Autowired private RpcClientFrame rpcClientFrame; // 获取服务端的方法名 @Bean public SendSms getSmsService() throws Exception{ return rpcClientFrame.getRemoteProxyObject(SendSms.class); } }
rpc测试
启动服务端
-
package cn.enjoyedu.rpc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class RpcServerSmsApplication { public static void main(String[] args) { SpringApplication.run(RpcServerSmsApplication.class, args); } }
客户端测试方法
-
package cn.enjoyedu.rpc.client; import cn.enjoyedu.rpc.client.service.NormalBusi; import cn.enjoyedu.rpc.remote.SendSms; import cn.enjoyedu.rpc.remote.StockService; import cn.enjoyedu.rpc.remote.vo.UserInfo; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class RpcClientApplicationTests { @Autowired private NormalBusi normalBusi; // @Autowired // private StockService stockService; @Autowired private SendSms sendSms; @Test void rpcTest() { long start = System.currentTimeMillis(); normalBusi.business(); /*发送邮件*/ UserInfo userInfo = new UserInfo("Mark","[email protected]"); System.out.println("Send mail: "+ sendSms.sendMail(userInfo)); System.out.println("共耗时:"+(System.currentTimeMillis()-start)+"ms"); /*扣减库存*/ // stockService.addStock("A001",1000); // stockService.deduceStock("B002",50); } }
测试总结
- 现在在客户端只有SendSms这样一个接口,并没有实现类
- 把代理类作为实现类注入到spring的容器里面
- 代理类里面,把对方法的调用通过网络转给了远端短信发送服务器上
- 而远端短信发送服务器上,通过一个map容器保存了改服务器上能提供的服务
- 在服务器端启动rpc服务时,把具体的实现放到hashmap中,并且启动了一个网络相关的服务,专门接受客户端传递过来的方法调用请求
- 从而实现了一次rpc的远程方法调用
注册中心
-
dubbo等rpc框架存在一个注册中心,而现在的rpc调用只有一个,如果要实现集群化,如果要提供多个rpc服务,难道客户端调用的时候要写多个服务器地址进去?
-
注册中心本质也是提供rpc服务,只有两种,提供服务的注册,提供服务的查询
RegisterCenter
-
package cn.enjoyedu.rpc.rpc.reg.service; import cn.enjoyedu.rpc.remote.vo.RegisterServiceVo; 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; /** * @author Mark老师 * 类说明:服务注册中心,服务提供者在启动时需要在注册中心登记自己的信息 */ @Service public class RegisterCenter { /*key表示服务名,value代表服务提供者地址的集合*/ private static final Map<String,Set<RegisterServiceVo>> serviceHolder = new HashMap<>(); /*注册服务的端口号*/ private int port; /*服务注册,考虑到可能有多个提供者同时注册,进行加锁*/ private static synchronized void registerService(String serviceName, String host,int port){ //获得当前服务的已有地址集合 Set<RegisterServiceVo> serviceVoSet = serviceHolder.get(serviceName); if(serviceVoSet==null){ //已有地址集合为空,新增集合 serviceVoSet = new HashSet<>(); serviceHolder.put(serviceName,serviceVoSet); } //将新的服务提供者加入集合 serviceVoSet.add(new RegisterServiceVo(host,port)); System.out.println("服务已注册["+serviceName+"]," + "地址["+host+"],端口["+port+"]"); } /*取出服务提供者*/ private static Set<RegisterServiceVo> getService(String serviceName){ return serviceHolder.get(serviceName); } /*处理服务请求的任务,其实无非就是两种服务: 1、服务注册服务 2、服务查询服务 */ private static class ServerTask implements Runnable{ private Socket client = null; public ServerTask(Socket client){ this.client = client; } public void run() { try(ObjectInputStream inputStream = new ObjectInputStream(client.getInputStream()); ObjectOutputStream outputStream = new ObjectOutputStream(client.getOutputStream())){ /*检查当前请求是注册服务还是获得服务*/ boolean isGetService = inputStream.readBoolean(); /*服务查询服务,获得服务提供者*/ if(isGetService){ String serviceName = inputStream.readUTF(); /*取出服务提供者集合*/ Set<RegisterServiceVo> result = getService(serviceName); /*返回给客户端*/ outputStream.writeObject(result); 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(); } }catch(Exception e){ e.printStackTrace(); }finally { try { client.close(); } catch (IOException 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){ new Thread(new ServerTask(serverSocket.accept())).start(); } }finally { serverSocket.close(); } } // 服务注册中心端口是9999 @PostConstruct public void init() { this.port = 9999; new Thread(new Runnable() { public void run() { try{ startService(); }catch(IOException e){ e.printStackTrace(); } } }).start(); } }
测试使用服务注册中心
-
package cn.enjoyedu.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); } }
修改响应的服务提供者
- 之前的服务都是注册在本地,现在除了在本地存一份,现在改成注册到注册中心,在哪个ip地址上提供了哪些服务
RegisterServiceWithRegCenter
-
package cn.enjoyedu.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 serviceName, String host, int port, Class impl) throws Throwable{ //登记到注册中心 Socket socket = null; ObjectOutputStream output = null; ObjectInputStream input = null; try{ socket = new Socket(); socket.connect(new InetSocketAddress("127.0.0.1",9999)); output = new ObjectOutputStream(socket.getOutputStream()); /*注册服务*/ output.writeBoolean(false); /*提供的服务名*/ output.writeUTF(serviceName); /*服务提供方的IP*/ output.writeUTF(host); /*服务提供方的端口*/ output.writeInt(port); output.flush(); input = new ObjectInputStream(socket.getInputStream()); if(input.readBoolean()){ System.out.println("服务["+serviceName+"]注册成功!"); } /*可提供服务放入本地缓存*/ serviceCache.put(serviceName,impl); } catch (IOException e) { e.printStackTrace(); } finally{ if (socket!=null) socket.close(); if (output!=null) output.close(); if (input!=null) input.close(); } } /*获取服务*/ public Class getLocalService(String serviceName) { return serviceCache.get(serviceName); } }
修改rpcServerFrame
-
package cn.enjoyedu.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; /** *@author Mark老师 * *类说明:rpc框架的服务端部分 */ @Service public class RpcServerFrame { // @Autowired // private RegisterService registerService; // 修改1 @Autowired private RegisterServiceWithRegCenter registerServiceWithRegCenter; //服务的端口号 private int port; /*处理服务请求任务*/ private static class ServerTask implements Runnable{ private Socket socket; // 修改3 private RegisterServiceWithRegCenter registerServiceWithRegCenter; // 修改4 public ServerTask(Socket client, RegisterServiceWithRegCenter registerServiceWithRegCenter) { this.socket = client; this.registerServiceWithRegCenter = registerServiceWithRegCenter; } // 接受客户端的请求,并调用实际的方法 // 接受的内容---1.方法所在的类名接口名 2.调用的方法名 3.方法参数及方法属性、具体的参数值 // @Override public void run() { try( ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream()); ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream())){ /*方法所在类名接口名*/ // 读字符串inputStream.readUTF() String serviceName = inputStream.readUTF(); /*方法的名字*/ String methodName = inputStream.readUTF(); /*方法的入参类型*/ // 读对象inputStream.readObject() // 强制转型,转成类 Class<?>[] paramTypes = (Class<?>[]) inputStream.readObject(); /*方法的入参的值*/ Object[] args = (Object[]) inputStream.readObject(); // 修改5 /*从容器中拿到服务的Class对象*/ Class serviceClass = registerServiceWithRegCenter.getLocalService(serviceName); if(serviceClass == null){ throw new ClassNotFoundException(serviceName+ " not found"); } /*通过反射,执行实际的服务*/ // 通过反射获取具体的方法Method对象 // 入参包括两个,方法名和参数类型数组 Method method = serviceClass.getMethod(methodName, paramTypes); // 通过动态代理调用响应的方法 Object result = method.invoke(serviceClass.newInstance(),args); /*将服务的执行结果通知调用者*/ // 将调用结果输出给调用者,即客户端 outputStream.writeObject(result); outputStream.flush(); }catch (Exception e){ e.printStackTrace(); }finally { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } public void startService(String serviceName, String host, int port, Class impl) throws Throwable{ ServerSocket serverSocket = new ServerSocket(); serverSocket.bind(new InetSocketAddress(port)); System.out.println("RPC server on:"+port+":运行"); // 修改2 // registerService.regRemote(ServiceName, impl); registerServiceWithRegCenter.regRemote(serviceName,host,port,impl); try{ while(true){ new Thread(new ServerTask(serverSocket.accept(), registerServiceWithRegCenter)).start(); } }finally { serverSocket.close(); } } }
测试注册中心
-
为了多注册服务,端口号取随机的
-
启动sms
-
再启动一次sms
-
客户端启动
-
动态代理的地址就不能写死了,应该从注册中心获取地址
-
package cn.enjoyedu.rpc.client.rpc; import cn.enjoyedu.rpc.remote.vo.RegisterServiceVo; 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; /** *@author Mark老师 *类说明:rpc框架的客户端代理部分 */ @Service public class RpcClientFrame { /*远程服务的代理对象,参数为客户端要调用的的服务*/ // 传递的应该是方法所在的类名或接口名 public static<T> T getRemoteProxyObject(final Class<?> serviceInterface) throws Exception { /*获得远程服务的一个网络地址*/ // 修改1 // InetSocketAddress addr = new InetSocketAddress("127.0.0.1",8778); InetSocketAddress addr = getService(serviceInterface.getName()); /*拿到一个代理对象,由这个代理对象通过网络进行实际的服务调用*/ // 应该产生一个代理对象,和服务器进行通讯 // 需要实现了InvocationHandler的类DynProxy return (T)Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class<?>[]{ serviceInterface}, new DynProxy(serviceInterface,addr)); } /*动态代理,实现对远程服务的访问*/ // 连接服务器,发送接口名、方法名、方法参数 private static class DynProxy implements InvocationHandler{ private Class<?> serviceInterface; private InetSocketAddress addr; public DynProxy(Class<?> serviceInterface, InetSocketAddress addr) { this.serviceInterface = serviceInterface; this.addr = addr; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Socket socket = null; ObjectInputStream inputStream = null; ObjectOutputStream outputStream = null; try{ // 1.建立网络连接 socket = new Socket(); socket.connect(addr); outputStream = new ObjectOutputStream(socket.getOutputStream()); // 2.发送接口名、方法名、参数类型、参数值 //方法所在类名接口名 outputStream.writeUTF(serviceInterface.getName()); //方法的名字 outputStream.writeUTF(method.getName()); //方法的入参类型 outputStream.writeObject(method.getParameterTypes()); //方法入参的值 outputStream.writeObject(args); outputStream.flush(); // 3.接受服务器传回的结果 inputStream = new ObjectInputStream(socket.getInputStream()); /*接受服务器的输出*/ System.out.println(serviceInterface+" remote exec success!"); return inputStream.readObject(); }finally { if(socket!=null) socket.close(); if(outputStream!=null) outputStream.close(); if(inputStream!=null) inputStream.close(); } } } /*----------------以下和动态获得服务提供者有关------------------------------*/ private static Random r = new Random(); /*获得远程服务的地址*/ private static InetSocketAddress getService(String serviceName) throws Exception { //获得服务提供者的地址列表 List<InetSocketAddress> serviceVoList = getServiceList(serviceName); InetSocketAddress addr = serviceVoList.get(r.nextInt(serviceVoList.size())); System.out.println("本次选择了服务器:"+addr); return addr; } /*获得服务提供者的地址*/ private static List<InetSocketAddress> getServiceList(String serviceName) throws Exception { Socket socket = null; ObjectOutputStream output = null; ObjectInputStream input = null; try{ socket = new Socket(); socket.connect(new InetSocketAddress("127.0.0.1",9999)); output = new ObjectOutputStream(socket.getOutputStream()); //需要获得服务提供者 output.writeBoolean(true); //告诉注册中心服务名 output.writeUTF(serviceName); output.flush(); input = new ObjectInputStream(socket.getInputStream()); Set<RegisterServiceVo> result = (Set<RegisterServiceVo>)input.readObject(); List<InetSocketAddress> services = new ArrayList<>(); for(RegisterServiceVo serviceVo : result){ String host = serviceVo.getHost();//获得服务提供者的IP int port = serviceVo.getPort();//获得服务提供者的端口号 InetSocketAddress serviceAddr = new InetSocketAddress(host,port); services.add(serviceAddr); } System.out.println("获得服务["+serviceName +"]提供者的地址列表["+services+"],准备调用."); return services; }finally{ if (socket!=null) socket.close(); if (output!=null) output.close(); if (input!=null) input.close(); } } }
-
取服务时,如果有多个,做负载均衡?
用随机数
-
高并发RPC解决方案
-
基于TCP的RPC实现
- Dubbo:
阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的RPC实现服务的输出和输入功能,可以和Spring框架无缝集成。
- Dubbo:
-
Provider: 暴露服务的服务提供方。
-
Consumer: 调用远程服务的服务消费方。
-
Registry: 服务注册与发现的注册中心。
-
Monitor: 统计服务的调用次调和调用时间的监控中心。
-
Container: 服务运行容器。