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只需关闭收发器