计算机网络报告-week11

计算机网络报告-week11

实验目的

  • 深入掌握RPC编程
  • 深入理解运输层协议

实验任务

  • 利用gRPC实现跨语言调用
  • 完成课本195页编程作业:实现一个可靠传输协议

任务1:利用gRPC实现跨语言调用

利用gRPC框架,采用两种不同语言(比如C++和Java或Go和Java)实现客户端和服务端。

服务端提供getStuNo()函数,该函数以自己的姓名为参数,返回自己的学号。客户端以自己的姓名为参数远程调用该函数,返回拿到自己的学号。

将实现思路和关键代码以及实验截图写到实验报告中。

这里,我使用Java和Python 两种语言来实现 grpc 的客户端与服务端。既可以是 Java client + Python server也可以是Java server + Python client

Java客户端+python服务端:

Java客户端

这次Java实现grpc,我是在week10的task2的基础上修改得来的,因此环境的搭建这里按下不表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package helloworld;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;

import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

public class HelloWorldClient {
// 首先创建一个final类型的不能被改变的参数用来获取类名
private static final Logger logger = Logger.getLogger(HelloWorldClient.class.getName());
// 这边也同时定义两个
private final ManagedChannel channel;
private final GreeterGrpc.GreeterBlockingStub blockingStub;

/**
* Construct client connecting to HelloWorld server at {@code host:port}.
*/
//接下来是定义一个方法,用来
public HelloWorldClient(String host, int port) {

channel = ManagedChannelBuilder.forAddress(host, port)
.usePlaintext(true)
.build();
blockingStub = GreeterGrpc.newBlockingStub(channel);
}

public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}

/**
greet函数是建立连接之后客户端的一些操作,比如说先模拟一个logger在尝试访问服务端。
然后向服务端发送请求,调用sayHello()
最后打印返回信息
*/
public void greet(String name) {
logger.info("Will try to greet " + name + " ...");
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply response;
try {
response = blockingStub.sayHello(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
logger.info("Greeting: " + response.getMessage());
}

/**
启动client的main函数,首先建立一个和端口50051的连接,然后开启greet()函数。
*/
public static void main(String[] args) throws Exception {
HelloWorldClient client = new HelloWorldClient("localhost", 50051);
try {

String user = "徐啟航";
if (args.length > 0) {
user = args[0];
}
client.greet(user);
} finally {
client.shutdown();
}
}
}

python服务端:

在讲解python服务端之前,我们首先来搭建python的grpc环境:

首先我们要下载几个包:

安装grpc:pip install grcpio

安装编译工具:pip install grpcio-tools

然后在项目中新建helloworld 文件夹用于存放proto文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
syntax = "proto3";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}

注意,这里写的package helloworld要和java中的proto文件一致。

接下来我们要在helloworld文件夹中编译proto文件:

1
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ./helloworld.proto

编译完成后,helloworld文件夹下会出现helloworld_pb2.py以及helloworld_pb2_grpc.py两个文件.我们要修改一下helloworld_pb2_grpc.py中的依赖,否则在运行时会出错:

1
2
3
import grpc
#原来是 import helloworld_pb2 as helloworld__pb2
from helloworld import helloworld_pb2 as helloworld__pb2

接下来开始写服务端的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#! /usr/bin/env python
# -*- coding: utf-8 -*-

import time

import grpc
from concurrent import futures
from helloworld import helloworld_pb2, helloworld_pb2_grpc

_ONE_DAY_IN_SECONDS = 60 * 60 * 24

#首先定义处理请求的函数,打印请求者信息之后,返回他想要的信息
class Greeter(helloworld_pb2_grpc.GreeterServicer):

def SayHello(self, request, context):
print("%s 发来请求~" % request.name)
return helloworld_pb2.HelloReply(message='Your ID is 10195501423, %s!' % request.name)

# 这是启动服务器的代码,规定了在那个端口开启服务以及错误处理等,这里使用了一个线程池来处理并发任务
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
server.add_insecure_port('[::]:50051')
server.start()
try:
while True:
time.sleep(_ONE_DAY_IN_SECONDS)
except KeyboardInterrupt:
server.stop(0)


if __name__ == '__main__':
serve()

运行结果如下图所示:

Java 服务端和 python客户端

python客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import grpc
from helloworld import helloworld_pb2, helloworld_pb2_grpc


def run():
# 客户端向50051端口发送请求,并打印返回结果
with grpc.insecure_channel('localhost:50051') as channel:
stub = helloworld_pb2_grpc.GreeterStub(channel)
response = stub.SayHello(helloworld_pb2.HelloRequest(name='徐啟航'))
print("Greeter client received: " + response.message)


if __name__ == '__main__':
run()

Java服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package helloworld;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.logging.Logger;

/**
* Server that manages startup/shutdown of a {@code Greeter} server.
*/


public class HelloWorldServer {
private static final Logger logger = Logger.getLogger(HelloWorldServer.class.getName());

/* The port on which the server should run */
private int port = 50051;
private Server server;

private void start() throws IOException {
server = ServerBuilder.forPort(port)
.addService( new GreeterImpl())
.build()
.start();
logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
System.err.println("*** shutting down gRPC server since JVM is shutting down");
HelloWorldServer.this.stop();
System.err.println("*** server shut down");
}
});
}

private void stop() {
if (server != null) {
server.shutdown();
}
}

/**
*这个函数是用来阻止java server自动终止的
*/
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}

//启动sever的main函数
public static void main(String[] args) throws IOException, InterruptedException {
final HelloWorldServer server = new HelloWorldServer();
server.start();
server.blockUntilShutdown();
}

static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
/** 原子Integer */
// public AtomicInteger count = new AtomicInteger(0);
// 处理请求的函数,返回学号
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()+"Your ID is 10195501423").build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
}

结果如下图所示,虽然python客户端收到了来自java服务端的反馈信息,但是服务端却报错退出了。还没有找到原因。

任务2:实现一个可靠传输协议

在这个编程作业实验中,你将要编写发送和接收运输层的代码,以实现一个简单的可靠数据运输协议。这个实验有两个版本,即比特交替协议版本和GBN版本。

任务:基于比特交替协议以及GBN实现模拟实现客户端和服务端的可靠的数据传输协议。

实现要求

  • 应用层消息(message)数据结构:
1
2
3
struct msg {
char data[20];
};
  • 传输层消息(packet)数据结构:
1
2
3
4
5
6
struct pkt {
int seqnum;
int acknum;
int checksum;
char payload[20];
};

Hint:传输层消息中的payload字段即为应用层消息中的data字段,传输层消息中的其他字段为辅助字段(用于实现可靠传输,其中checksum部分自己选取合适的算法实现)。

1). 需要实现的部分关键方法:

A_output(message):A处理应用层发送的message,参数是要发送给B的数据。你要实现的协议就是要保证message的可靠传输;

A_input(packet):A处理B发送过来的传输层packet;

A_timeinterrupt():A的定时器,用于超时重传;

A_init():用于A的一些初始化工作;

B_input(packet):B处理A发送过来的传输层packet;

B_init():用于B的一些初始化工作。

2). 部分需要实现的软件接口:

starttimer(calling_entity,increment):开始计时,calling_entity代表调用实体,这里为A或B;

stoptimer(calling_entity):停止calling_entity计时;

tolayer3(calling_entity,packet):calling_entity将消息转发到传输层;

tolayer5(calling_entity,message):calling_entity将消息转发到应用层。

3) 模拟的网络环境

  • 消息有序接收:有多条message乱序发送,接收端需要保证按序接收;
  • 消息丢失:设置消息丢失率,即消息有一定的概率会被丢失,但要保证接收端能够完整接收;
  • 应用层平均延时:记录应用层从发出message请求到接收响应的平均延时。

基于Alternating-Bit-Protocol(ABP,rdt3.0)实现

ABP协议具体内容可参照课本P140-P142

图 2 ABP协议流程

基于Go-Back-N实现

图 3 窗口长度为4个分组的GBN协议运行流程

要求:窗口大小固定为8,如果A从应用层接受的包在窗口外面,直接简单的舍弃;不用太过考虑缓冲区溢出的问题

Hint:先实现ABP协议,在ABP协议的基础上实现GBN协议。

-------------本文结束,感谢您的阅读-------------