RPC 网络编程

RPC:Remote Procedure Call(远程过程调用)    

一.定义:

     是进程间的通信方式(Socket通信),是一种技术的思想,而不是规范。
     核心:通讯、序列化

二.流程梳理  

  client    -    server    
    (TestRPCClient)
    1 客户端通过socket请求服务端,并通过字符串形式(或者Class)将请求的接口发送给服务端
        动态代理:发送接口名、方法名、方法参数、方法参数类型
    (TestRPCServer)
    2 服务端将可以提供的接口注册到服务中心(通过map保存 key[接口名]——value[接口实现类])
    3 服务端接收到客户端的请求后,通过请求的接口名 在服务中心的map中寻找对应的接口实现类
    解析Client发送的:接口名、方法名、方法参数、方法参数类型
    通过reflect执行该方法,并将返回值传给客户端
        Method method = serviceClass.getMethod(methodName, parameterTypes);
        Object result = method.invoke(serviceClass.newInstance(), arguements);
    4 客户端接收服务器处理的结果

三.代码实现

  1  server服务端

 (1)声明业务接口

public interface HelloService {
    public Object sayHi(String name);
}

(2)实现业务接口

public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHi(String name) {
        String str = "hi:"+name;
        System.out.println(str);
        return str;
    }
}

(3)声明服务接口

//服务中心
public interface MyServer {

    public void start() throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException;
    public void stop();
    //注册服务
    public void register(Class service,Class serviceImpl);
}

(4)实现服务接口

(一)单线程实现
查看代码
public class ServerCenter_Singlethread implements MyServer {
    //map:服务端的所有可供客户端访问的接口,都注册到该map中。接口名字“HelloService” value:HelloService的实现
    private static HashMap<String,Class> serverRegister = new HashMap();
    private static int port;

    public ServerCenter_Singlethread(int port){
        this.port = port;
    }

    //开启服务端服务
    @Override
    public void start() throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        ObjectInputStream input = null;
        ObjectOutputStream output = null;
        try{
            ServerSocket server = new ServerSocket();//ip,port
            server.bind(new InetSocketAddress(9999));//绑定端口-

            Socket socket = server.accept();//等待客户端连接..

            //接收到客户端连接及请求,处理该请求..
            input = new ObjectInputStream(socket.getInputStream());
            //因为ObjectInputStream对发送数据的顺序严格要求,因此需要参照发送的顺序逐个接收。
            //接收:接口名、方法名 | 方法参数、方法参数的类型
            String serviceName = input.readUTF();
            String methodName = input.readUTF();
            Class[] parameterTypes = (Class[])input.readObject();//方法的参数类型
            Object[] arguements = (Object[]) input.readObject();//方法参数名

            //根据客户请求,到map中找到阈值对应的接口
            Class serviceClass = serverRegister.get(serviceName);//HelloService
            Method method = serviceClass.getMethod(methodName, parameterTypes);
            //执行该方法
            Object result = method.invoke(serviceClass.newInstance(), arguements);

            //向客户端将方法执行完毕的返回值传给客户端
            output = new ObjectOutputStream(socket.getOutputStream());
            output.writeObject(result);
        } catch(Exception e){
            e.printStackTrace();
        } finally {
            try{
                if(output != null) output.close();
                if(input != null) input.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }

    }

    @Override
    public void stop() {

    }

    @Override
    public void register(Class service,Class serviceImpl) {
        serverRegister.put(service.getName(),serviceImpl);
    }
}
(二)多线程实现(常用)
查看代码
public class ServerCenter implements MyServer {
    //map:服务端的所有可供客户端访问的接口,都注册到该map中。接口名字“HelloService” value:HelloService的实现
    private static HashMap<String,Class> serverRegister = new HashMap();
    private static int port;
    //连接池(并发多线程):存在多个连接对象,每个连接对象都可以处理一个客户请求
    /*
        获取处理器个数(生产连接池对象):Runtime.getRuntime().availableProcessors());
     */
    private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    private static boolean isRunning;

    public ServerCenter(int port){
        this.port = port;
    }

    //开启服务端服务
    @Override
    public void start() {
        ServerSocket server = null;
        try {
            server = new ServerSocket();//ip,port
            server.bind(new InetSocketAddress(port));//绑定端口-
        } catch (IOException e) {
            e.printStackTrace();
        }

        isRunning = true;
        System.out.println("start server..服务器已启动!");
        //一旦开启,执行多次
        while(true){
            //100: 1 1 1 1 .. 1 -- 如果让多个客户端请求并发执行 -- 多线程

            //客户端每请求一次连接(发出一次请求),服务端从连接池中获取一个线程对象去处理
            Socket socket = null;
            try{
                socket = server.accept();//等待客户端连接..
            }catch (Exception e){
                e.printStackTrace();
            }
            //启动线程处理客户请求
            executor.execute(new ServiceTask(socket));
            System.out.println("已接收到新的用户用户请求!");
        }

    }

    //关闭服务
    @Override
    public void stop() {
        isRunning = false;
        executor.shutdown();
    }

    @Override
    public void register(Class service,Class serviceImpl) {
        serverRegister.put(service.getName(),serviceImpl);
    }

    //定义内部类实现线程任务
    private static class ServiceTask implements Runnable{

        private Socket socket;

        public ServiceTask(){

        }

        //线程与外部的对象桥梁
        public ServiceTask(Socket socket){
            this.socket = socket;
        }
        //线程所做的事情(并发任务) doThing()
        @Override
        public void run() {
            doThing(socket);
        }
    }

    //具体的服务内容:接收客户端请求-处理请求-返回结果
    public static void doThing(Socket socket){

        ObjectInputStream input = null;
        ObjectOutputStream output = null;
        try{
            //接收到客户端连接及请求,处理该请求..
            input = new ObjectInputStream(socket.getInputStream());
            //因为ObjectInputStream对发送数据的顺序严格要求,因此需要参照发送的顺序逐个接收。
            //接收:接口名、方法名 | 方法参数、方法参数的类型
            String serviceName = input.readUTF();
            String methodName = input.readUTF();
            Class[] parameterTypes = (Class[])input.readObject();//方法的参数类型
            Object[] arguements = (Object[]) input.readObject();//方法参数名

            //根据客户请求,到map中找到阈值对应的接口
            Class serviceClass = serverRegister.get(serviceName);//HelloService
            Method method = serviceClass.getMethod(methodName, parameterTypes);//方法名、方法参数类型
            //执行该方法
            Object result = method.invoke(serviceClass.newInstance(), arguements);

            //向客户端将方法执行完毕的返回值传给客户端
            output = new ObjectOutputStream(socket.getOutputStream());
            output.writeObject(result);
        } catch(Exception e){
            e.printStackTrace();
        } finally {
            try{
                if(output != null) output.close();
                if(input != null) input.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

  2  client客户端

查看代码
public class MyClient {
    /*
        InetSocketAddress 包括:ip、port
        serviceName:请求的接口名
        return:返回动态代理对象
     */
    //获取代表服务端接口的动态代理对象(HelloService)
    public static <T> T getRemoteProxyObj(Class service, InetSocketAddress addr){
        /*
            三个参数:
            1 ClassLoader类加载器:需要代理哪个类(例如HelloService接口),就需要将HelloService的类加载器 传入第一个参数
            2 Class<?>[] 需要代理的对象,具备哪些方法 -- 接口
            3 InvocationHandler
         */
        return (T)Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service}, new InvocationHandler() {

            /*
                proxy:代理的对象HelloService
                method:哪个方法(sayHello())
                args:该方法的参数列表
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                ObjectOutputStream output = null;
                ObjectInputStream input = null;
                try{
                    //客户端向服务端发送请求:请求某一个具体的接口
                    Socket socket = new Socket();
                    //InetSocketAddress:ip、port
                    socket.connect(addr);
                    socket.getOutputStream();//发送[序列化流(对象流)](客户端->服务端 OutputStream)
                    output = new ObjectOutputStream(socket.getOutputStream());
                    //发送:接口名、方法名 | 方法参数、方法参数的类型
                    output.writeUTF(service.getName());//发送:接口名
                    output.writeUTF(method.getName());//发送:方法名
                    output.writeObject(method.getParameterTypes());//方法参数的类型
                    output.writeObject(args);//方法参数

                    //等待服务端处理..

                    //接收服务端处理后的返回值
                    input = new ObjectInputStream(socket.getInputStream());
                    return input.readObject();//客户端-服务端-客户端
                }catch (Exception e){
                    e.printStackTrace();
                    return null;
                } finally {
                    try{
                        if(input != null) input.close();
                        if(output != null) output.close();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }

            }
        });
    }
}

  3  测试使用

(1)启动服务并注册接口,同时监听客户端连接

public class TestRPCServer {
    public static void main(String[] args) {
        //服务中心
        ServerCenter server = new ServerCenter(9999);
        //注册接口
        server.register(HelloService.class, HelloServiceImpl.class);
        server.start();
    }
}

(2)客户端启动,根据接口名的类加载器以及IP地址端口号,获取代表服务端接口的动态代理对象(HelloService)

public class TestRPCClient {
    public static void main(String[] args) throws ClassNotFoundException {
        HelloService service = MyClient.getRemoteProxyObj(Class.forName("rpc.server.dao.HelloService"),new InetSocketAddress("127.0.0.1",9999));
        Object obj = service.sayHi("张三");
        System.out.println(obj);
    }
}

四、Socket网络编程

Socket(套接字):基于TCP协议的网络通信,可以提供双向安全连接的网络通讯。基于数据流(字节流)
   客户端:Socket发送数据,OutputStream;接收数据,InputStream

   服务端:ServerSocket     InputStream          OutputStream

1、TCP/IP,基于TCP的socket传输(对象流)

定义:面向连接的、可靠的(【速度慢】不丢失、不重复、有序)、基于字节流的传输通讯协议(设备、协议、联系【网线】)

应用:字节流、字节数组、对象流、缓冲流

实体对象:

public class Person implements Serializable {
    private int pid;
    private String name;
    private int age;
}

(1)客户端代码:

#获取服务ip和端口
socket = new Socket("127.0.0.1",8888);
OutputStream out = socket.getOutputStream();//发送数据

objectInputStream = new ObjectOutputStream(out);//输出流装饰成对象流
objectInputStream.writeObject(new Person(1001,"zs",32););//客户端发送对象
socket.shutdownOutput();//等同于 flush()、close(),不可省
                              
//接收服务端返回的字符信息
InputStream in = socket.getInputStream();
bufferedReader = new BufferedReader(new InputStreamReader(in));
String feedbackinfo = null;
StringBuffer buffer = new StringBuffer("");
while((feedbackinfo = bufferedReader.readLine()) != null){
    buffer.append(feedbackinfo);
}
System.out.println("客户端接收:"+buffer);

//关闭流
if(objectInputStream != null) objectInputStream.close();
if(in != null) in.close();
if(out != null) out.close();
if(socket != null) socket.close();

(2)服务端代码:

ServerSocket serverSocket = new ServerSocket(8888);//绑定端口(8888)
Socket socket = serverSocket.accpet()监听客户端访问

//没检测到客户端就分配线程进行交互
ServerThread serverThread = new ServerThread(socket);
serverThread.start();

run{
    InputStream in = socket.getInputStream();//获取输入流
    ObjectInputStream ois = new ObjectInputStream(in);//装饰成对象流
    Person per = (Person)ois.readObject();//获取客户端传来的对象
    socket.shutdownInput();//flush

    //反馈客户端
    OutputStream out = socket.getOutputStream();
    out.write("已收到...".getBytes());
    socket.shutdownOutput();
    
    //关闭流
    if(out != null) out.close();
    if(ois != null) ois.close();
    if(in != null) in.close();
    if(socket != null) socket.close();
}

2、UDP

定义:无连接的协议、不可靠的【速度快】。在传输数据之前,客户端和服务端之间不建立和维护连接。提供不可靠的数据传输。

应用:DatagramSocket 收发器

(1)发送端 Send

InetAddress ip = InetAddress.getByName("127.0.0.1");//获取ip地址
DatagramPacket dp = new DatagramPacket("hello server".getBytes(),msg.length(),ip,9999);
DatagramSocket ds = new DatagramSocket();//收发器
ds.send(dp);
//udp只需关闭收发器
ds.close();

(2)接收端 Receive

byte[] data = new byte[64];//缓存数组
DatagramPacket  dp = new DatagramPacket(data,data.length);//用缓存数组接收发送的数据
DatagramSocket ds = new DatagramSocket(9999);//根据端口获取接收器
ds.receive(dp);
String receiveData = new String(dp.getData(),0,data.length);//显示接收的数据
    System.out.println("显示发送方信息:"+dp.getAddress().getHostAddress());
    System.out.println("接收到数据:"+receiveData);
ds.close();//udp只需关闭收发器
posted @ 2022-04-22 10:08  小吴dnd  阅读(168)  评论(0编辑  收藏  举报