【分布式系统】RPC实验

Posted by Cww97 on 2017-12-05

版权声明:本文为博主原创文章,未经博主允许不得转载。原文所在http://blog.csdn.net/cww97 https://blog.csdn.net/cww97/article/details/78725811
# RPC实验

标签(空格分隔): 分布式系统


华东师范大学 计算机科学与软件工程学院

林佳乐 10152510208

陈伟文 10152510217

徐紫琦 10152510119

RPC的定义及内容

RPC即远程过程调用,允许计算机程序通过网络在共享网络中另一台计算机上运行某个过程而不需要了解底层网络架构,因此本地和远程的子程序代码具有很高的相似度。1RPC采用客户机-服务器模式,常见实现方式为请求-应答模式的信息传递系统。RPC是进程间交互的一种,不同的进程拥有不同的地址空间,如果在同一个主机上,参与RPC的进程即使拥有同一个物理地址空间也依然拥有不同的虚拟地址空间,而若参与RPC的进程在不同主机上运行,其物理地址空间和虚拟地址空间均不同。一次RPC由客户机发起,客户机提交一条请求信息到已知的远程服务器要求运行某一特定过程。远程服务器处理请求,客户机此时一般被阻塞(除非客户机发送请求为异步请求),直至服务器返回输出。

image.png-131.3kB
image.png-131.3kB

RPC具有位置透明性,即调用的过程尽管大致相同却非完全相同,因此本地调用和远程调用是有显著区别的。远程调用通常显著地表现出更慢的速度和更低的可靠度,且可能会因为未知的网络问题突然失败。因此明确辨别远程调用和本地调用是必要的,在不知道过程是否正确运行的情况下建立必要的错误处理机制也是必要的。

RPC的历史

最早的把网络操作当做远程系统调用的想法在ARPANET的早期文档中就有提及。1978年,Per Brinch Hansen提出了分布式进程的概念以描述基于由进程间过程调用组成的外部请求的分布式计算手段以应对由具有分布式存储的计算机网络控制的实时应用在监测同步活动时对长期的严格时间同步提出的高要求和可靠性提出的高要求。2 1981年,Bruce Jay Nelson提出了RPC这个名词,讲述了RPC为高级语言提供基于网络的交互具有的优越性,并描述了一个提供远程过程调用的程序包的实现及设计者的设计思路与选择。3

Server端介绍

Server端存储一个 StringService 类,保存string的两个方法, concatcompare

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type StringService struct {
}

func (s *StringService) Concat(args *Args, result *Result) error {
result.Value = args.A + args.B
result.Status = "Concat"
return nil
}

func (s *StringService) Compare(args *Args, result *Result) error {
result.Value = args.B
result.Status = "Compare"
if strings.Compare(args.A,args.B) > 0 {
result.Value = args.A
}
return nil
}

Server端主函数

1
2
3
4
5
6
7
8
9
10
11
func main() {
var ms = new(common.StringService)
rpc.Register(ms)
rpc.HandleHTTP() //将Rpc绑定到HTTP协议上。
fmt.Println("启动服务...")
err := http.ListenAndServe(":1234", nil)
if err != nil {
fmt.Println(err.Error())
}
fmt.Println("服务已停止!")
}

StringService 绑定到rpc上,再讲rpc绑定在http协议上,同时将其绑定在1234端口,那么server端已经开始运行。

Server端和Client端通信的数据结构

1
2
3
4
5
6
7
type Args struct {
A, B string
}
type Result struct {
Value string
Status string
}

Args 表示参数
Result 存储调用的函数名字和运行结果

Client端介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
var args = common.Args{"hello", "world"}
var result1 = common.Result{}
var result2 = common.Result{}
var client, err = rpc.DialHTTP("tcp", "192.168.43.26:1234")
if err != nil {
fmt.Println("连接RPC服务失败:", err)
}
err = client.Call("StringService.Concat", args, &result1)
err = client.Call("StringService.Compare",args,&result2)
if err != nil {
fmt.Println("调用失败:", err)
}
fmt.Println("使用方法: ",result1.Status," 调用结果:", result1.Value)
fmt.Println("使用方法: ",result2.Status," 调用结果:", result2.Value)

}

将hello和world两个参数绑定到通信所用的参数的数据结构中,并设置两个空的Result,然后就可以用以tcp的方式访问服务器,由于实验是在本地局域网的多台机器上进行的,所以服务器是server端所在的IPV4地址,然后就可以分别调用两个函数。

WIRESHARK抓包
采用192.168.43.26的机器为server端,192.168.43.157为client端
Server端运行 server.go , 然后用wireshark监听tcp.port ==1234
Client端运行 clinet.go
抓包结果如下:

image.png-134.4kB
image.png-134.4kB

很明显,NO97—99是TCP的三次握手阶段
NO100

image.png-54.9kB
image.png-54.9kB

发送连接RPC的请求
NO101

image.png-63.3kB
image.png-63.3kB

Server端返回表示rpc连接成功
NO102为ACK,表示连接成功
NO103 正式开始RPC连接的执行任务

image.png-109.5kB
image.png-109.5kB

可以在包中发现调用的函数名字:StringService.Concat ,参数A为hello,参数B为world
NO104

image.png-127.7kB
image.png-127.7kB

为server端调用concat函数之后的返回值,可以发现value为helloworld,status为concat
No105为ack表示接收到
No106为发起调用campare的函数的请求,由于args已经传送过来,所以不需要再起传送

image.png-72.3kB
image.png-72.3kB

No107为compare的返回值

image.png-63.4kB
image.png-63.4kB

Compare返回的是较大的string,所以value为world,status为Divide(由于代码修改之后未保存)
NO108为ack,表示接收到数据

NO109-112为四次握手释放。

参考文献

[1] Arpaci-Dusseau, Remzi H., Arpaci-Dusseau, Andrea C.: Introduction to Distributed Systems. Book. 2014
[2] Brinch Hansen, Per: Distributed processes: a concurrent programming concept, Communications of the ACM. 1978
[3] Bruce Jay Nelson: Implementing Remote Procedure Calls. Xerox Palo Alto Research Center. PhD thesis. 1981