背景
最近在进行开发工作的时候,遇到了一个场景:
pc程序需要和安卓设备进行通讯和接口调用。
此时就需要进行远程调用方法。然而大学时代有关于远程过程调用的知识都还给了老师……所以在此进行一个复习,并进行实战演练!
网络远程过程调用
三种方式说明:
- Socket:主要用于网络通信,它允许不同计算机上的进程通过网络进行数据交换。Socket提供了一个端到端的通信机制,无论是在同一台机器上还是跨网络的不同机器上。
- Pipe:主要用于同一台机器上的进程间通信(IPC),它实现了一种简单的数据流机制,允许一个进程的输出直接作为另一个进程的输入。Pipe是单向的,数据只能从一个方向流动。
- RPC:是一种远程过程调用的协议,它允许一个程序通过网络调用另一个地址空间(通常是另一台机器)上的过程(或函数),就像调用本地过程一样。RPC隐藏了网络通信的底层细节,使得远程调用看起来像本地调用一样简单。
相互关系说明:
- socket和pipe:Socket主要用于跨设备场景,当然也可以用于实现同一台机器上的进程间通信,但是对于同一设备的进程通讯,Pipe更为常见和高效。
- Socket和RPC:Socket是RPC实现中常用的底层通信机制之一。在RPC框架中,客户端和服务器之间的网络通信通常是通过Socket来完成的。Socket提供了数据传输的通道,而RPC则在这个通道上构建了一个更高层次的调用接口。
方式一:pipe
原理详解:
- Pipe是一种在Unix和类Unix系统中常用的进程间通信机制。它通过创建一个管道文件来实现数据的单向流动。
- 当一个进程创建了一个管道时,它会得到两个文件描述符:一个用于写入(写端),另一个用于读取(读端)。写入管道的数据会被存储在内核的缓冲区中,直到被另一个进程读取。
- Pipe的通信是同步的,即写进程在写入数据后会被阻塞,直到读进程读取了数据;同样,读进程在读取数据前也会被阻塞,直到写进程写入了数据。
代码示例:
在Windows系统中,Python的multiprocessing
模块提供了与Unix系统类似的管道(Pipe)功能,用于进程间通信(IPC)。
from multiprocessing import Process, Pipe def sender(conn): conn.send("Hello from sender!") conn.close() def receiver(conn): print("Receiving...") while True: try: data = conn.recv() print(f"Received: {data}") except EOFError: print("No more data. Exiting.") break conn.close() if __name__ == '__main__': # 创建一个管道 parent_conn, child_conn = Pipe() # 创建子进程 p1 = Process(target=sender, args=(child_conn,)) p2 = Process(target=receiver, args=(parent_conn,)) # 启动子进程 p1.start() p2.start() # 等待子进程完成 p1.join() p2.join()
注意:在Windows上,如果接收者进程在发送者进程之后退出,可能会导致发送者进程中的管道连接在关闭时出现问题。
方式二:socket
原理详解:
- Socket是计算机网络编程中的一种抽象,它提供了在网络上进行通信的接口。Socket本质上是一种通信的端点,它在网络上标识了一个通信链路的两端,并提供了通信双方所需的接口和功能。
- 在TCP/IP协议栈中,Socket位于传输层和应用层之间,它使用传输层提供的服务(如TCP或UDP)来实现网络通信。
- TCP Socket基于TCP协议,提供可靠的、有序的数据传输服务。它通过三次握手建立连接,确保数据的可靠性和顺序性。
- UDP Socket基于UDP协议,提供简单的数据传输服务,但不保证数据的可靠性和顺序性。它适用于一些实时性要求高、允许一定数据丢失的应用场景。
代码示例:
服务端:
import socket def tcp_server(host='127.0.0.1', port=12345): server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind((host, port)) server_socket.listen(5) # 最多可以挂起5个连接 print(f"Server is listening on {host}:{port}") while True: client_socket, addr = server_socket.accept() print(f"Connected by {addr}") try: while True: data = client_socket.recv(1024) if not data: break print(f"Received: {data.decode()}") client_socket.sendall(data) # Echo back the data finally: client_socket.close() if __name__ == '__main__': tcp_server()
客户端:
import socket def tcp_client(host='127.0.0.1', port=12345): client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect((host, port)) try: while True: message = input("Enter message: ") if message == 'quit': break client_socket.sendall(message.encode()) data = client_socket.recv(1024) print(f"Received: {data.decode()}") finally: client_socket.close() if __name__ == '__main__': tcp_client()
方式三:RPC
原理详解:
- RPC是一种远程过程调用的协议,它允许程序通过网络调用远程地址空间上的过程。RPC隐藏了网络通信的底层细节,使得远程调用看起来像本地调用一样简单。
- RPC的实现通常包括客户端和服务器两部分。客户端负责发起远程调用请求,并接收服务器返回的调用结果;服务器则负责接收客户端的请求,执行相应的过程,并将结果返回给客户端。
- RPC框架通常会在客户端和服务器之间建立一条或多条Socket连接,用于传输远程调用的请求和响应。这些Socket连接可以是持久的(长连接),也可以是临时的(短连接)。
- RPC框架还需要处理一些额外的任务,如参数和结果的序列化与反序列化、网络异常的处理、服务调用的负载均衡等。这些任务通常是由RPC框架本身来完成的,以减轻应用程序的负担。
代码示例:
1. .proto
文件(helloworld.proto
)
这个文件定义了gRPC服务的接口,使用Protocol Buffers语法。
// 使用proto3语法
syntax = "proto3"; // 定义包名,防止命名冲突
package helloworld; // 定义Greeter服务
service Greeter { // 定义一个RPC方法SayHello,它接收HelloRequest并返回HelloReply rpc SayHello (HelloRequest) returns (HelloReply) {}
} // 定义HelloRequest消息,包含一个string类型的name字段
message HelloRequest { string name = 1; // 字段编号为1
} // 定义HelloReply消息,包含一个string类型的message字段
message HelloReply { string message = 1; // 字段编号为1
}
2. Java 服务端实现
这里假设你已经使用protoc
编译器和gRPC Java插件生成了GreeterGrpc.java
和Helloworld.java
等自动生成的代码。
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import helloworld.GreeterGrpc;
import helloworld.GreeterOuterClass; // 实现GreeterGrpc.GreeterImplBase,提供SayHello方法的具体实现
public class HelloWorldServer { static class HelloWorldImpl extends GreeterGrpc.GreeterImplBase { @Override public void sayHello(GreeterOuterClass.HelloRequest req, StreamObserver<GreeterOuterClass.HelloReply> responseObserver) { // 构造回复消息 GreeterOuterClass.HelloReply reply = GreeterOuterClass.HelloReply.newBuilder() .setMessage("Hello " + req.getName()) .build(); // 发送回复并标记RPC调用完成 responseObserver.onNext(reply); responseObserver.onCompleted(); } } public static void main(String[] args) throws Exception { // 在指定端口上创建并启动gRPC服务器 Server server = ServerBuilder.forPort(50051) .addService(new HelloWorldImpl()) .build() .start(); System.out.println("Server started, listening on 50051"); // 等待服务器终止(通常是通过某种方式发送的关闭信号) server.awaitTermination(); }
}
3. Python 客户端实现
这里假设你已经使用protoc
编译器和gRPC Python插件生成了__init__.py
、helloworld_pb2.py
和helloworld_pb2_grpc.py
等自动生成的代码。
import grpc import helloworld_pb2
import helloworld_pb2_grpc def run(): # 创建一个不安全的通道连接到服务器 with grpc.insecure_channel('localhost:50051') as channel: # 创建Greeter服务的存根(stub) stub = helloworld_pb2_grpc.GreeterStub(channel) # 构造请求消息 response = stub.SayHello(helloworld_pb2.HelloRequest(name='you')) # 打印响应消息 print("Greeter client received: " + response.message) if __name__ == '__main__': run()