背景
最近在进行开发工作的时候,遇到了一个场景:
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()