编程中的Socket是什么?
Socket是应用层与TCP/IP协议族通信的间软件抽象层,它是一组接口,其实就是一个门面模式。TCP用主机的IP地址加上主机上的端口号作为TCP连接的端点,这种端点就叫做套接字(socket)。
socket是一种门面模式,屏蔽了与机器的交流过程,方便业务程序员开发代码
A的socket ---- B的socket
两个socket建立关系,从而A和B就可以通信了
短连接
一次通讯完了马上就关闭连接
服务器要服务成千上亿的服务
短连接和长连接到底如何选择,需要根据实际情况
长连接
一次连接内会进行多次通讯
维持住长连接主要消耗的是内存和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—非主流
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();
}
}
}
上述服务器端是来一个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();
}
}
}
}
}
单WEB项目
之前上述三个服务全是单线程执行,当调用服务的用户数很大时,就不能充分发挥并发编程的高效,因此考虑将三个服务拆分,都分别由单独的线程来执行
进一步把不同的服务部署到不同的服务器上,而且每个服务是通过服务器集群的形式来部署,从而形成了分布式的架构
扣减库存服务,为了使调用远端服务和调用本地服务没有什么区别,就需要引入客户端存根
客户端存根就是远程方法在本地的模拟,包括方法名、方法参数
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框架不知道要建立哪些静态代理,所以使用动态代理,不管你要调用什么服务,通过动态代理一把解决
序列化问题
消息在网络中传输是字节的形式,怎么把01字节变成javaBean的形式
序列化问题的解决
实现Serializable接口,但是jdk的序列化性能很差
测试结果1-----jdk的字节码长度:133 自己字节码长度:24
测试结果2-----jdk序列化耗时:1364ms 自己序列化耗时:108ms
服务实例化
方法调用时只有一个方法名和方法参数,怎么转化成调用具体的服务
服务实例化的解决
通过反射解决
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);
}
}
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");
}
}
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);
}
}
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);
}
}
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;
}
}
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();
}
}
}
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);
}
}
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);
}
}
dubbo等rpc框架存在一个注册中心,而现在的rpc调用只有一个,如果要实现集群化,如果要提供多个rpc服务,难道客户端调用的时候要写多个服务器地址进去?
注册中心本质也是提供rpc服务,只有两种,提供服务的注册,提供服务的查询
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);
}
}
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);
}
}
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();
}
}
}
取服务时,如果有多个,做负载均衡?
用随机数
基于TCP的RPC实现
Provider: 暴露服务的服务提供方。
Consumer: 调用远程服务的服务消费方。
Registry: 服务注册与发现的注册中心。
Monitor: 统计服务的调用次调和调用时间的监控中心。
Container: 服务运行容器。