gRPC实战(二、流式传输 Java、Go实现)

735 阅读7分钟

二、流式传输

我们第一节学习的是简单一元数据传输,也就是在rpc中最常见的一个请求对应一个响应。但往往在实际开发中可能有这样的场景:用户app位置上报、服务端大文件下发等这种持续字节流的情况。Grpc贴心的针对这些情况设置了三种流式传输模式:客户端流、服务端流、客户端服务端双向流传输

1、客户端单向流

现在我们在proto文件中创建一个客户端上报流数据的rpc方法 - reportUserBehavior

syntax = "proto3";//标识 proto版本 建议使用proto3
package userinfoservice;//proto包名 避免命名冲突,也可以作为引入其他proto文件时使用
option java_package = "com.example.userinfoservice" ;//生成的类将带有此包名,不指定则使用package
option cc_generic_services = true;
option go_package = "./pb";
option java_outer_classname = "UserInfoEntity";

message GetUserInfoReq{
  string id = 1;
}
message GetUserInfoRes{
  string id = 1;
  string name = 2;
  int32 age = 3;
}

message ReportUserBehaviorReq{
  string id = 1;
  string behavior = 2;
}

message ReportUserBehaviorRes{
  int32 retCode = 1;
  string retMsg = 2;
  int64 receivedCount = 3;
}

service UserInfoService {
  rpc getUserInfo(GetUserInfoReq) returns (GetUserInfoRes);
  rpc reportUserBehavior(stream ReportUserBehaviorReq) returns (ReportUserBehaviorRes);
}

reportUserBehavior 模拟的是客户端上报用户行为的例子,在日常开发中,这种场景很常见,比如打点环节里可能需要将用户的一系列的行为操作上报。我们使用go和Java分别来实现这个案例:

  • go语言实现

a、通过protoc生成代码并实现客户端代码

package main

import (
	"context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"grpc-in-action/part02-stream/go/client/pb"
	"io"
	"log"
	"time"
)

func main() {
	conn,err:= grpc.Dial("localhost:10081",grpc.WithTransportCredentials(insecure.NewCredentials()))

	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewUserInfoServiceClient(conn)

	ReportUserBehavior(c)//客户端stream
}

func ReportUserBehavior(c pb.UserInfoServiceClient) {
	req:=&pb.ReportUserBehaviorReq{
		Id:       "1",
		Behavior: "起床",
	}

	req1:=&pb.ReportUserBehaviorReq{
		Id:       "1",
		Behavior: "洗漱",
	}

	req2:=&pb.ReportUserBehaviorReq{
		Id:       "1",
		Behavior: "上班",
	}

	context,cancel := context.WithTimeout(context.Background(),time.Second *10)

	defer cancel()
	bhClient ,err := c.ReportUserBehavior(context)
	if err != nil {
		panic(err)
	}

	if err= bhClient.Send(req);err!=nil{
		panic(err)
	}

	if err= bhClient.Send(req);err!=nil{
		panic(err)
	}

	if err= bhClient.Send(req1);err!=nil{
		panic(err)
	}

	if err= bhClient.Send(req2);err!=nil{
		panic(err)
	}
	bhRes, err := bhClient.CloseAndRecv()
	if err != nil {
		log.Fatalf("%v.CloseAndRecv() got error %v, want %v", bhClient, err, nil)
	}
	log.Printf("Update Orders Res : %s", bhRes)

}

b、go服务端支持

package impl

import (
	"context"
	"errors"
	"fmt"
	"grpc-in-action/part02-stream/go/server/pb"
	"io"
	"log"
)

type StreamUserInfoServiceImpl struct {
	UserLocationTmp map[string][]UserLocTmp
	UserInfoData map[string]*UserInfo
	pb.UnimplementedUserInfoServiceServer
}

type UserLocTmp struct {
	Loc string
	Temperature string
}

type UserInfo struct {
	Id string
	Name string
	Age int32
}

func (server *StreamUserInfoServiceImpl) GetUserInfo(ctx context.Context, in *pb.GetUserInfoReq) (*pb.GetUserInfoRes, error)  {

	log.Printf("go stream server收到请求")
	//初始化一个map
	if server.UserInfoData == nil{
		server.initUserInfoData()
	}
	data:= server.UserInfoData[in.Id]
	if data == nil{
		return nil,errors.New("该id不存在")
	}
	res := &pb.GetUserInfoRes{
		Id:   data.Id,
		Name: data.Name,
		Age:  data.Age,
	}

	return res,nil
}

func (server *StreamUserInfoServiceImpl) ReportUserBehavior(stream pb.UserInfoService_ReportUserBehaviorServer) error  {

	res:= pb.ReportUserBehaviorRes{
		RetCode: 0,
		RetMsg: "收到收到~",
		ReceivedCount: 0,
	}
	for {
		behaviorData, err := stream.Recv()
		if err == io.EOF {
			// 结束符 客户端停止发送
			return stream.SendAndClose(&res)
		}
		if err != nil {
			return err
		}
		//此处有并发问题 只是简单演示
		res.ReceivedCount+=1
		log.Printf("用户 ID : %s -行为: %s", behaviorData.Id, behaviorData.Behavior)
	}
}

func (server *StreamUserInfoServiceImpl) initUserInfoData (){
	server.UserInfoData = make(map[string]*UserInfo)

	server.UserInfoData["1"] = &UserInfo{
		Id:   "1",
		Name: "小美",
		Age:  18,
	}
	server.UserInfoData["2"] = &UserInfo{
		Id:   "2",
		Name: "小刚",
		Age:  28,
	}
	server.UserInfoData["3"] = &UserInfo{
		Id:   "3",
		Name: "小王",
		Age:  20,
	}
}

func (server *StreamUserInfoServiceImpl) initUserLocationTmp() {
	server.UserLocationTmp = make(map[string][]UserLocTmp)
	var data []UserLocTmp
	for i := 0; i < 10; i++ {
		t := UserLocTmp{
			Loc: "北京",
			Temperature :"26.5",
		}
		data = append(data,t)
	}
	server.UserLocationTmp["1"] = data
	server.UserLocationTmp["2"] = data
	server.UserLocationTmp["3"] = data
}
  • Java语言实现

a、通过Gradle插件生成代码并实现客户端代码

package com.example.userinfoservice;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;

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

public class App {

    private static final Logger logger = Logger.getLogger(App.class.getName());

    public static void main(String[] args) throws InterruptedException {
        ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 10081)
                .usePlaintext()
                .build();

        UserInfoServiceGrpc.UserInfoServiceBlockingStub stub =
                UserInfoServiceGrpc.newBlockingStub(channel);

        GetUserInfo(stub);
        //客户端流
        UserInfoServiceGrpc.UserInfoServiceStub asyncStub = UserInfoServiceGrpc.newStub(channel);
        ReportUserBehavior(asyncStub);

        channel.shutdown();
    }

    private static void ReportUserBehavior(UserInfoServiceGrpc.UserInfoServiceStub stub) {
        CountDownLatch finishLatch = new CountDownLatch(1);
        StreamObserver<UserInfoEntity.ReportUserBehaviorRes> responseObserver = new StreamObserver<UserInfoEntity.ReportUserBehaviorRes>() {
            @Override
            public void onNext(UserInfoEntity.ReportUserBehaviorRes value) {
                logger.info("Report User Behavior RetCode: " + value.getRetCode() + " RetMsg: " + value.getRetMsg());
            }
            @Override
            public void onError(Throwable t) {
            }
            @Override
            public void onCompleted() {
                logger.info("此次流处理完成");
                finishLatch.countDown();
            }
        };
        StreamObserver<UserInfoEntity.ReportUserBehaviorReq> reportUserBehaviorReqStreamObserver = stub.reportUserBehavior(responseObserver);
        reportUserBehaviorReqStreamObserver.onNext(UserInfoEntity.ReportUserBehaviorReq.newBuilder().setId("1")
                .setBehavior("下班").build());
        reportUserBehaviorReqStreamObserver.onNext(UserInfoEntity.ReportUserBehaviorReq.newBuilder().setId("1")
                .setBehavior("回家").build());
        reportUserBehaviorReqStreamObserver.onNext(UserInfoEntity.ReportUserBehaviorReq.newBuilder().setId("1")
                .setBehavior("做饭").build());

        if (finishLatch.getCount() == 0) {
            logger.warning("RPC completed or errored before we finished sending.");
            return;
        }
        reportUserBehaviorReqStreamObserver.onCompleted();
        try {
            if (!finishLatch.await(10, TimeUnit.SECONDS)) {
                logger.warning("FAILED : Process orders cannot finish within 10 seconds");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    private static void GetUserInfo(UserInfoServiceGrpc.UserInfoServiceBlockingStub stub) {

        UserInfoEntity.GetUserInfoRes res = stub.getUserInfo(
                UserInfoEntity.GetUserInfoReq.newBuilder()
                        .setId("1")
                        .build());
        logger.info("用户id: " + res.getId() + "\t用户姓名:"+ res.getName()
                +"\t用户年龄:"+ res.getAge());
    }
}

b、服务端支持

package com.example.userinfoservice.impl;

import com.example.userinfoservice.UserInfoEntity;
import com.example.userinfoservice.UserInfoServiceGrpc;
import com.example.userinfoservice.entity.UserInfoData;
import com.google.common.collect.Maps;
import io.grpc.stub.StreamObserver;
import org.apache.commons.lang3.StringUtils;

import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;

public class UserInfoServiceImpl extends UserInfoServiceGrpc.UserInfoServiceImplBase {

    private HashMap<String, UserInfoData> userinfoHashMap = Maps.newHashMap();

    @Override
    public void getUserInfo(UserInfoEntity.GetUserInfoReq request, StreamObserver<UserInfoEntity.GetUserInfoRes> responseObserver) {
        System.out.println("java服务收到请求");
        if (StringUtils.isEmpty(request.getId())){
            responseObserver.onError(new Exception("id不能为空"));
            responseObserver.onCompleted();
            return;
        }

        if (userinfoHashMap.isEmpty()){
            initUserInfoHashMap();
        }

        UserInfoData userInfoData = userinfoHashMap.get(request.getId());
        if (userInfoData == null){
            responseObserver.onError(new Exception("id不能为空"));
            responseObserver.onCompleted();
            return;
        }
        responseObserver.onNext(UserInfoEntity.GetUserInfoRes.newBuilder()
                .setId(userInfoData.getId())
                .setName(userInfoData.getName())
                .setAge(userInfoData.getAge())
                .build());
        responseObserver.onCompleted();
    }

    //客户端流
    @Override
    public StreamObserver<UserInfoEntity.ReportUserBehaviorReq> reportUserBehavior(StreamObserver<UserInfoEntity.ReportUserBehaviorRes> responseObserver) {
        AtomicInteger count = new AtomicInteger(0);
        UserInfoEntity.ReportUserBehaviorRes.Builder res = UserInfoEntity.ReportUserBehaviorRes.newBuilder()
                .setRetCode(0)
                .setRetMsg("收到~~");

        StreamObserver<UserInfoEntity.ReportUserBehaviorReq> reportUserBehaviorReqStreamObserver = new StreamObserver<UserInfoEntity.ReportUserBehaviorReq>() {
            @Override
            public void onNext(UserInfoEntity.ReportUserBehaviorReq value) {
                System.out.println(value.getId() + "\t" + value.getBehavior());
                count.incrementAndGet();
            }

            @Override
            public void onError(Throwable t) {
            }

            @Override
            public void onCompleted() {
                res.setReceivedCount(count.get());
                responseObserver.onNext(res.build());

                responseObserver.onCompleted();
            }
        };
        return reportUserBehaviorReqStreamObserver;
    }

    private void initUserInfoHashMap() {
        userinfoHashMap.put("1",new UserInfoData("1","小美",24));
        userinfoHashMap.put("2",new UserInfoData("2","小强",28));
        userinfoHashMap.put("3",new UserInfoData("3","小刚",30));
    }
}

2、服务端单向流

在proto文件中创建一个服务端下发流数据的rpc方法 - supplyUserChangeInfo

syntax = "proto3";//标识 proto版本 建议使用proto3
package userinfoservice;//proto包名 避免命名冲突,也可以作为引入其他proto文件时使用
option java_package = "com.example.userinfoservice" ;//生成的类将带有此包名,不指定则使用package
option cc_generic_services = true;
option go_package = "./pb";
option java_outer_classname = "UserInfoEntity";

message GetUserInfoReq{
  string id = 1;
}
message GetUserInfoRes{
  string id = 1;
  string name = 2;
  int32 age = 3;
}

message ReportUserBehaviorReq{
  string id = 1;
  string behavior = 2;
}

message ReportUserBehaviorRes{
  int32 retCode = 1;
  string retMsg = 2;
  int64 receivedCount = 3;
}

message SupplyUserChangeInfoReq{
  string id = 1;
}

message SupplyUserChangeInfoRes{
  string id = 1;
  string loc = 2;
  string temperature = 3;
}

service UserInfoService {
  rpc getUserInfo(GetUserInfoReq) returns (GetUserInfoRes);
  rpc reportUserBehavior(stream ReportUserBehaviorReq) returns (ReportUserBehaviorRes);
//服务端单向流
  rpc supplyUserChangeInfo(SupplyUserChangeInfoReq) returns (stream SupplyUserChangeInfoRes);
}
  • Java语言实现

a、服务端实现接口对外服务

package com.example.userinfoservice.impl;

import com.example.userinfoservice.UserInfoEntity;
import com.example.userinfoservice.UserInfoServiceGrpc;
import com.example.userinfoservice.entity.UserInfoData;
import com.example.userinfoservice.entity.UserLocTmp;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import io.grpc.stub.StreamObserver;
import org.apache.commons.lang3.StringUtils;

import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class UserInfoServiceImpl extends UserInfoServiceGrpc.UserInfoServiceImplBase {

    private HashMap<String, UserInfoData> userinfoHashMap = Maps.newHashMap();

    private HashMap<String, List<UserLocTmp>> userLocTmpHashMap = Maps.newHashMap();

    @Override
    public void getUserInfo(UserInfoEntity.GetUserInfoReq request, StreamObserver<UserInfoEntity.GetUserInfoRes> responseObserver) {
        System.out.println("java服务收到请求");
        if (StringUtils.isEmpty(request.getId())){
            responseObserver.onError(new Exception("id不能为空"));
            responseObserver.onCompleted();
            return;
        }

        if (userinfoHashMap.isEmpty()){
            initUserInfoHashMap();
        }

        UserInfoData userInfoData = userinfoHashMap.get(request.getId());
        if (userInfoData == null){
            responseObserver.onError(new Exception("id不能为空"));
            responseObserver.onCompleted();
            return;
        }
        responseObserver.onNext(UserInfoEntity.GetUserInfoRes.newBuilder()
                .setId(userInfoData.getId())
                .setName(userInfoData.getName())
                .setAge(userInfoData.getAge())
                .build());
        responseObserver.onCompleted();
    }

    //客户端流
    @Override
    public StreamObserver<UserInfoEntity.ReportUserBehaviorReq> reportUserBehavior(StreamObserver<UserInfoEntity.ReportUserBehaviorRes> responseObserver) {
        AtomicInteger count = new AtomicInteger(0);
        UserInfoEntity.ReportUserBehaviorRes.Builder res = UserInfoEntity.ReportUserBehaviorRes.newBuilder()
                .setRetCode(0)
                .setRetMsg("收到~~");

        StreamObserver<UserInfoEntity.ReportUserBehaviorReq> reportUserBehaviorReqStreamObserver = new StreamObserver<UserInfoEntity.ReportUserBehaviorReq>() {
            @Override
            public void onNext(UserInfoEntity.ReportUserBehaviorReq value) {
                System.out.println(value.getId() + "\t" + value.getBehavior());
                count.incrementAndGet();
            }

            @Override
            public void onError(Throwable t) {
            }

            @Override
            public void onCompleted() {
                res.setReceivedCount(count.get());
                responseObserver.onNext(res.build());

                responseObserver.onCompleted();
            }
        };
        return reportUserBehaviorReqStreamObserver;
    }

    //服务端流
    @Override
    public void supplyUserChangeInfo(UserInfoEntity.SupplyUserChangeInfoReq request, StreamObserver<UserInfoEntity.SupplyUserChangeInfoRes> responseObserver) {
        String id = request.getId();
        if (userLocTmpHashMap.isEmpty()){
            initUserLocTmpHashMap();
        }
        List<UserLocTmp> userLocTmps = userLocTmpHashMap.get(id);
        for (UserLocTmp userLocTmp : userLocTmps) {
            responseObserver.onNext(UserInfoEntity.SupplyUserChangeInfoRes.newBuilder().setId(id)
            .setLoc(userLocTmp.getLoc())
            .setTemperature(userLocTmp.getTemperature())
            .build());
        }
        responseObserver.onCompleted();
    }

    private void initUserInfoHashMap() {
        userinfoHashMap.put("1",new UserInfoData("1","小美",24));
        userinfoHashMap.put("2",new UserInfoData("2","小强",28));
        userinfoHashMap.put("3",new UserInfoData("3","小刚",30));
    }
    private void initUserLocTmpHashMap() {
        UserLocTmp data = new UserLocTmp("上海", "24.5度");
        List<UserLocTmp> datas = Lists.newArrayList(data);
        for (int i = 0; i < 5; i++) {
            datas.add(data);
        }
        userLocTmpHashMap.put("1",datas);
        userLocTmpHashMap.put("2",datas);
        userLocTmpHashMap.put("3",datas);
    }

}

b、客户端接受服务端下发数据

private static void SupplyUserChangeInfo(UserInfoServiceGrpc.UserInfoServiceStub asyncStub) {
        CountDownLatch finishLatch = new CountDownLatch(1);
        StreamObserver<UserInfoEntity.SupplyUserChangeInfoRes> resStreamObserver = new StreamObserver<UserInfoEntity.SupplyUserChangeInfoRes>() {
            @Override
            public void onNext(UserInfoEntity.SupplyUserChangeInfoRes value) {
                logger.info("id:"+value.getId()+"\tloc:"+value.getLoc()+"\ttemp:"+value.getTemperature());
            }
            @Override
            public void onError(Throwable t) {
            }
            @Override
            public void onCompleted() {
                logger.info("服务端流关闭");
                finishLatch.countDown();
            }
        };
        asyncStub.supplyUserChangeInfo(UserInfoEntity.SupplyUserChangeInfoReq.newBuilder().setId("1").build(),resStreamObserver);
        try {
            if (!finishLatch.await(10, TimeUnit.SECONDS)) {
                logger.warning("FAILED : Process orders cannot finish within 10 seconds");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
  • GO语言实现

a、服务端支持

func (server *StreamUserInfoServiceImpl) SupplyUserChangeInfo(req *pb.SupplyUserChangeInfoReq,
	res pb.UserInfoService_SupplyUserChangeInfoServer) error{
	//获取用户的数据
	if server.UserLocationTmp == nil{
		server.initUserLocationTmp()
	}

	id := req.Id
	if len(id) == 0 {
		return errors.New("id不能为空")
	}
	//获取该用户的历史移动轨迹
	locTmps := server.UserLocationTmp[id]
	for i, tmp := range locTmps {
		log.Printf("i:%d,data:%v",i,tmp)
		err := res.Send(&pb.SupplyUserChangeInfoRes{
			Id:id,
			Loc: tmp.Loc,
			Temperature: tmp.Temperature,
		})
		if err != nil {
			return fmt.Errorf("error sending message to stream : %v", err)
		}
	}
	return nil
}

b、客户端接收数据

func SupplyUserChangeInfo(c pb.UserInfoServiceClient) {
	req:= &pb.SupplyUserChangeInfoReq{
		Id:"1",
	}
	res,err:=c.SupplyUserChangeInfo(context.Background(),req)
	if err != nil {
		log.Fatalf("err:%v",err)
		return
	}
	for{
		data,err:=res.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			panic(err)
		}
		log.Printf("data:%v",data)
	}
}

3、客户端服务端双向流

在proto文件中创建一个服务端下发流数据的rpc方法 - supplyUserChangeInfo

syntax = "proto3";//标识 proto版本 建议使用proto3
package userinfoservice;//proto包名 避免命名冲突,也可以作为引入其他proto文件时使用
option java_package = "com.example.userinfoservice" ;//生成的类将带有此包名,不指定则使用package
option cc_generic_services = true;
option go_package = "./pb";
option java_outer_classname = "UserInfoEntity";

message GetUserInfoReq{
  string id = 1;
}
message GetUserInfoRes{
  string id = 1;
  string name = 2;
  int32 age = 3;
}

message ReportUserBehaviorReq{
  string id = 1;
  string behavior = 2;
}

message ReportUserBehaviorRes{
  int32 retCode = 1;
  string retMsg = 2;
  int64 receivedCount = 3;
}

message SupplyUserChangeInfoReq{
  string id = 1;
}

message SupplyUserChangeInfoRes{
  string id = 1;
  string loc = 2;
  string temperature = 3;
}

message ExchangeUserInfoReq{
  string id = 1;
  double lng = 2;
  double lat = 3;
}

message ExchangeUserInfoRes{
  string id = 1;
  string loc = 2;
}

service UserInfoService {
  rpc getUserInfo(GetUserInfoReq) returns (GetUserInfoRes);
  rpc reportUserBehavior(stream ReportUserBehaviorReq) returns (ReportUserBehaviorRes);
  rpc supplyUserChangeInfo(SupplyUserChangeInfoReq) returns (stream SupplyUserChangeInfoRes);
//双向流  
rpc exchangeUserInfo(stream ExchangeUserInfoReq) returns (stream ExchangeUserInfoRes);
}
  • Java语言实现

a、服务端实现此方法

//客户端服务端流
    @Override
    public StreamObserver<UserInfoEntity.ExchangeUserInfoReq> exchangeUserInfo(StreamObserver<UserInfoEntity.ExchangeUserInfoRes> responseObserver) {
        responseObserver.onNext(UserInfoEntity.ExchangeUserInfoRes.newBuilder()
                .setId("1")
                .setLoc("上海")
                .build());
        responseObserver.onNext(UserInfoEntity.ExchangeUserInfoRes.newBuilder()
                .setId("1")
                .setLoc("上海东站")
                .build());
        responseObserver.onNext(UserInfoEntity.ExchangeUserInfoRes.newBuilder()
                .setId("1")
                .setLoc("上海西站")
                .build());

        responseObserver.onNext(UserInfoEntity.ExchangeUserInfoRes.newBuilder()
                .setId("1")
                .setLoc("上海南站")
                .build());
        return new StreamObserver<UserInfoEntity.ExchangeUserInfoReq>() {
            @Override
            public void onNext(UserInfoEntity.ExchangeUserInfoReq value) {
                System.out.println("id:"+value.getId()+"\tlat:"+value.getLat()+"\tlng:"+value.getLng());
            }

            @Override
            public void onError(Throwable t) {

            }

            @Override
            public void onCompleted() {
                responseObserver.onCompleted();
            }
        };
    }

b、客户端接受流并上报流数据

private static void ExchangeUserInfo(UserInfoServiceGrpc.UserInfoServiceStub asyncStub) {
        CountDownLatch finishLatch = new CountDownLatch(1);
        StreamObserver<UserInfoEntity.ExchangeUserInfoRes> resStreamObserver = new StreamObserver<UserInfoEntity.ExchangeUserInfoRes>() {
            @Override
            public void onNext(UserInfoEntity.ExchangeUserInfoRes value) {
                logger.info("res,id:"+value.getId()+"\tloc:"+value.getLoc());
            }

            @Override
            public void onError(Throwable t) {

            }

            @Override
            public void onCompleted() {
                logger.info("服务端流关闭 交换数据结束");
                finishLatch.countDown();
            }
        };
        StreamObserver<UserInfoEntity.ExchangeUserInfoReq> exchangeUserInfoReqStreamObserver = asyncStub.exchangeUserInfo(resStreamObserver);
        exchangeUserInfoReqStreamObserver.onNext(UserInfoEntity.ExchangeUserInfoReq.newBuilder().setId("1")
                .setLat(12.12)
                .setLng(23.23)
                .build());
        exchangeUserInfoReqStreamObserver.onNext(UserInfoEntity.ExchangeUserInfoReq.newBuilder().setId("1")
                .setLat(22.12)
                .setLng(33.23)
                .build());
        exchangeUserInfoReqStreamObserver.onNext(UserInfoEntity.ExchangeUserInfoReq.newBuilder().setId("1")
                .setLat(32.12)
                .setLng(43.23)
                .build());
        exchangeUserInfoReqStreamObserver.onNext(UserInfoEntity.ExchangeUserInfoReq.newBuilder().setId("1")
                .setLat(42.12)
                .setLng(53.23)
                .build());
        exchangeUserInfoReqStreamObserver.onCompleted();

        try {
            if (!finishLatch.await(10, TimeUnit.SECONDS)) {
                logger.warning("FAILED : Process orders cannot finish within 10 seconds");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
  • Go语言实现

a、服务端实现

func (server *StreamUserInfoServiceImpl) ExchangeUserInfo(ex pb.UserInfoService_ExchangeUserInfoServer) error {
	for  {
		recv, err := ex.Recv()
		if err == io.EOF{
			err := ex.Send(&pb.ExchangeUserInfoRes{
				Id:  "结束汇报",
				Loc: "结束汇报",
			})
			if err != nil {
				return err
			}
			return nil
		}
		if err != nil {
			return err
		}
		log.Printf("用户Id:%s, lat:%v,lng:%v",recv.Id,recv.Lat,recv.Lng)
		err = ex.Send(&pb.ExchangeUserInfoRes{
			Id:  recv.Id,
			Loc: "收到,您当前的位置是北京",
		})
		if err != nil {
			return err
		}
	}
}

b、客户端实现

func ExchangeUserInfo(c pb.UserInfoServiceClient) {
	info, err := c.ExchangeUserInfo(context.Background())
	if err != nil {
		panic(err)
	}
	for i := 0; i < 5; i++ {
		req := &pb.ExchangeUserInfoReq{
			Id:  "1",
			Lng: 12.12,
			Lat: 32.32,
		}
		err = info.Send(req)
		if err != nil {
			panic(err)
		}
	}
	lisChanClose := make(chan bool)

	go listenExchangeUserInfo(info,lisChanClose)

	if err := info.CloseSend(); err != nil {
		log.Fatal(err)
	}

	 lisChanClose<-true

}