【Java】远程调用、线程池手写一个简单服务器

image.png

关键字远程调用、序列化、反序列化、反射、动态代理、客户端、服务端、线程池 > 思考题:带着这几个问题可以先思考,然后看完文章再去理解,也可以在评论区讨论喔~

  1. 反射和动态代理关系和区别?

  2. RPC和HTTP的关系和区别?

  3. 远程调用如何处理大量请求?

  4. 线程池是解决什么问题?

原理分析

通俗理解:首先要理解什么是远程调用,客户端异地也能使用远处服务端的服务。一般有 RPC 和 HTTP 两种实现方式。 ​

首先弄清楚 RPC HTTP 协议的区别 ​

  • 相同:都基于 socket 通讯,都是远程调用和远程服务

  • HTTP通用性强,服务不需要关注消费和服务者的语言,基于 restful 风格接口原则就能通信。

HTTP 框架解决的问题是:通过路由映射一个远程接口服务器,基于 http 协议,请求路由就能获取远程接口执行的结果。

  • RPC:调用快,较 http 更轻量没太多复杂的协议头,要求两边使用相同的 RPC 框架,局限性高。

RPC 框架需要解决的一个问题是:像调用本地接口一样调用远程的接口。 ​

举个例子,你突然想吃外面的 (服务端) 炸鸡了,但你(客户端)又不会做。

  • 此时选择 美团(HTTP)商家使用美团提供的平台和规范(HTTP 协议),可以在美团平台被检索,你想找炸鸡此时你只能试探的去询问(路由去映射服务):星巴克卖炸鸡吗(查询路由)?星巴克查询他的菜单(服务端函数)发现没有,拒绝服务;麦当劳有炸鸡吗?查询到对应的菜单,麦当劳通过订单完成炸鸡(反射:根据订单内容名反射为实际的菜品加工类和流程)经过美团外卖(restful 风格数据)送到你手上(数据包封装好直接送过来);【中间可能传递的慢,外卖小哥可能还会顺路去取送其他订单,并且要经过美团的流程,浪费了资源和时间,但通用性强,只要和美团签订协议,商家就内提供服务给消费者】

  • 此时选择 肯德基宅急送(RPC)下载肯德基专门 app,此时你有肯德基的菜单(服务接口),肯德基也有菜单(服务接口)和厨师(接口实现类),你能像就在肯德基一样直接基于菜单指定需要的菜品并且那边会执行(像调用本地接口一样调用远程的接口。)然后肯德基宅急送就专门负责肯德基送餐。【没有美团封锁的流程,是直接对接的商家,更快,更轻量,但局限性高,要求客户也下载专门 app】


**流程 **:服务端建立 **socket **监听端口 <--> 客户端连接 <--> 请求响应 【服务端创建 socket 监听端口 --> 如果有服务来就开启一个线程去响应服务】 【客户端 调用接口 --> 向服务端建立 socket --》 服务端通过反射和代理实现调用内容通过 socket 返回】


rpc.gif

上图是服务器上面发送** 一个** 请求的模式。

但实际情况是 1:n 的访问情况,一个服务端被多个客户端访问。 如果只有一个 java 线程,就会顺序执行请求,对于客户端来说体验不太友好,人多的时候需要排队。 ​

这时候就需要服务端开启 n 个线程,来一个请求创建一个,但未知数的客户端来说 n 的数量级可能成千上万,启动太多线程,一个对服务器资源达到浪费,一个服务器可能承受不了这么多请求而崩溃。 ​

为了缓解这个问题,这时候就需要用 池化思想 来进行处理了。 ​

线程池具体是如何解决上述两个问题的呢,可以看下面解释

多线程的实现

这里是自习室,签到 1 小时(方便演示实际 1s)才能离开... 这里座位 200 个,门口队伍可以排 100 个人 每次只能坐 5 个人,多余的人排队,排满 100 人才能再坐第 6 个及后续座位 当排队人达到 100,教室也坐满 200 人,再来同学就拒绝服务了 学生可以入场了!!

/**
   * 线程池思想:先交给corePool去服务,
   * 当线程数大于基本的服务人数时,去等待区等待
   * 当等待区满的时候,就叫新服务员来
   * 当新服务器大于maxmumPoolSize时就会执行解决策略
   *
   * 比如5基础,200max,100等待队列
   * 4个人  启动4线程
   * 6个人  5个服务 1个等待
   * 105个人 5个人服务 100人等待
   * 106个人 6个人服务 100人等待
   * 300个人 200个人服务 100人等待
   * 301个人 200个人服务 100等待 多出1个触发解决策略,看配置执行对应内容 可以拒绝也可以给其他线程处理
   */
  //
  //corePoolSize 默认服务线程
  ///capacity 阻塞队列可以容纳的数量
  //maximumPoolSize 先填满阻塞后可以
  threadPool =  new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(
    100
  ));

下面是120 人并发打卡模拟 根据上面的推导 100 人排队,5 人默认服务。多出 15 人触发服务增加,所以本次可以使用 20 个座位 等前面 20 个学习完 1 小时后,后续的同学可以继续用这 20 个位子,所以本次的位子上限是 20 人 直到有一会(0s)位子上没人,就被回收了,下次来还是 5 个位子

pool.gif

本地和服务器部署方法

单机本地模式

就是普通的 java 项目,然后也不用在意包,但为了区分,就将两个系统分开放,这里注意下,HelloService 两个文件夹(包)下面都有,引用的时候要隔离。先后启动服务端 Main 和客户端 RpcClient 就行。

image.png

项目结构

├─rpcClient (客户端)
│   │  HelloService.java
│   │  RpcClient.java
│
└─rpcServer (服务端)
    │  HelloService.java
    │  HelloServiceImpl.java
    │  Main.java
    │  RpcServer.java

服务器模式

分开放,一个包放一个服务器下面,一定要打包!!可以先本地打包然后放服务器 这里要注意是多文件打包,所以如果直接用 idea 打好的 class,使用起来会报错,需要手动打

在当前目录下:ithm\src\rpcServer>javac -d 去打包内容如下 ​

这样打好的包会存在当前文件夹包名下 javac -d . -encoding utf-8 HelloService.java javac -d . -encoding utf-8 HelloServiceImpl.java javac -d . -encoding utf-8 RpcServer.java javac -d . -encoding utf-8 Main.java

本地 cmd 启动是java rpcServer.Main (当前目录下) ​

然后一起扔服务器下面,服务器配好 java 环境,同样启动方式

image.png

一定要注意**开放端口 **这边是 9002 就需要开放 ​

这时候可以直接用本地客户端去 ping 你服务器了~ ​

直接修改 rpcClient/RpcClient.java ​

HelloService helloService = _getClient_(HelloService.class, "你服务器的ip地址", 9002);

然后本地运行就发现已经连接通啦~~ ​

rpc.gif

rpc.gif

当然同样方法可以将 客户端 也放到自己其他服务器上面去

服务端源码 rpcServer

该模块使用 rpcServer 包 启动 Main 即可

HelloService

package rpcServer;

public interface HelloService{

 public String hello(String name);
 public String sign(String name);
}

HelloServiceImpl

package rpcServer;

import java.util.HashMap;
import java.util.Map;

public class HelloServiceImpl implements HelloService {

 public Map<String,Integer> names = new HashMap<>();


 @Override
 public String hello(String name) {
  return "Hello, " + name;
 }


 @Override
 public String sign(String name) {
  if(!names.containsKey(name)){
   names.put(name,1);
   return name + "是第" + names.size() + "个打卡的同学";
  }else{
   names.put(name,names.get(name) + 1);
   return name + "同学第" + names.get(name) + "次签到";
  }
 }



}

Main

package rpcServer;

public class Main {

 public static void main(String args[]){
  HelloService helloService = new HelloServiceImpl();
  //单例模式
  RPCServer server = new RPCServer();
  server.register(helloService, 9002);
 }
}

RpcServer

package rpcServer;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


public class RpcServer {

 private ExecutorService threadPool;

 public RpcServer(){
  /**
   * 线程池思想:先交给corePool去服务,
   * 当线程数大于基本的服务人数时,去等待区等待
   * 当等待区满的时候,就叫新服务员来
   * 当新服务器大于maxmumPoolSize时就会执行解决策略
   *
   * 比如5基础,200max,100等待队列
   * 4个人  启动4线程
   * 6个人  5个服务 1个等待
   * 105个人 5个人服务 100人等待
   * 106个人 6个人服务 100人等待
   * 300个人 200个人服务 100人等待
   * 301个人 200个人服务 100等待 多出1个触发解决策略,看配置执行对应内容 可以拒绝也可以给其他线程处理
   */
  //
  //corePoolSize 默认服务线程
  ///capacity 阻塞队列可以容纳的数量
  //maximumPoolSize 先填满阻塞后可以
  threadPool =  new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(
    100
  ));
 }

 public void register(Object service, int port){
  try {
   System.out.println("这里是自习室,签到1小时(方便演示实际1s)才能离开...");
   System.out.println("这里座位" + 200 + "个,门口队伍可以排"+100+"个人");
   System.out.println("每次只能坐" + 5 + "个人,多余的人排队,排满100人才能再坐第6个及后续座位");
   System.out.println("当排队人达到100,教室也坐满200人,再来同学就拒绝服务了");
   System.out.println("学生可以入场了!!");
   ServerSocket server = new ServerSocket(port);
   Socket socket = null;
   while((socket = server.accept()) != null){
    System.out.println("有新的同学进入教室..." + socket.getInetAddress() + " " + socket.getLocalAddress());
    threadPool.execute(new Processor(socket, service));
   }
  } catch (IOException e) {
   e.printStackTrace();
  }
 }

 class Processor implements Runnable{
  Socket socket;
  Object service;

  public Processor(Socket socket, Object service){
   this.socket = socket;
   this.service = service;
  }
  public void process(){

  }
  @Override
  public void run() {
   try {
    Thread.sleep(1000);
    ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
    String methodName = in.readUTF();
    Class<?>[] parameterTypes = (Class<?>[]) in.readObject();
    Object[] parameters = (Object[]) in.readObject();
    Method method = service.getClass().getMethod(methodName, parameterTypes);
    try {
     Object result = method.invoke(service, parameters);
     ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
     out.writeObject(result);


    } catch (IllegalAccessException e) {
     e.printStackTrace();
    } catch (IllegalArgumentException e) {
     e.printStackTrace();
    } catch (InvocationTargetException e) {
     e.printStackTrace();
    }
   } catch (IOException e) {
    e.printStackTrace();
   } catch (NoSuchMethodException e) {
    e.printStackTrace();
   } catch (SecurityException e) {
    e.printStackTrace();
   } catch (ClassNotFoundException e1) {
    e1.printStackTrace();
   } catch (InterruptedException e) {
    e.printStackTrace();
   }finally {
    ThreadPoolExecutor tpe = ((ThreadPoolExecutor) threadPool);
    int queueSize = tpe.getQueue().size();
    int activeCount = tpe.getActiveCount();
    long completedTaskCount = tpe.getCompletedTaskCount();
    System.out.printf("|排队人数|当前人数|总打卡数|\n|%-8d|%-8d|%-8d|\n",queueSize,activeCount,completedTaskCount);
   }
  }
 }
}

客户端源码 rpcClient

启动 RpcServicec

HelloService

package rpcClient;

public interface HelloService {

 public String hello(String name);
 public String sign(String name);
}

RpcService

package rpcClient;

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.Socket;
import java.util.Scanner;

public class RpcClient {

 public static void main(String args[]){
  HelloService helloService = getClient(HelloService.class, "139.224.231.217", 9002);
  //HelloService helloService = getClient(HelloService.class, "127.0.0.1", 9002);
  // 单次请求模式
  /*Scanner input=new Scanner(System.in);
  System.out.println("请输入你的名字:");
  String name = input.next();
  System.out.println(helloService.sign(name));*/

  //多线程版本
  for(int i = 0; i < 12; i++){
   int finalI = i;
   int finalI1 = i;
   new Thread(()->{
    System.out.println( helloService.sign("404name" + finalI1));
   }).start();
  }
 }

 @SuppressWarnings("unchecked")
 public static <T> T getClient(Class<T> clazz, String ip, int port){
  return  (T) Proxy.newProxyInstance(RpcClient.class.getClassLoader(), new Class<?>[]{clazz}, new InvocationHandler() {

   @Override
   public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
    Socket socket = new Socket(ip, port);
    ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
    out.writeUTF(arg1.getName());
    out.writeObject(arg1.getParameterTypes());
    out.writeObject(arg2);
    ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
    return in.readObject();
   }
  });
 }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

404name

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值