go-micro examples 中stream 代码学习(流 服务及 结合浏览器websocket的使用)

对应 examples/stream 例子:

这个例子, 有 一个 streaming 服务, 和两个 client, 一个是 streaming rpc , 一个是 js websockets  + web services (streaming rpc)。

目录:

 

server  是 服务

client : 是 rpc streaming client 

web : 是 websocket handler (和浏览器js websocket组成 peer) + rpc streaming client

server/proto/stream.proto 内容如下:

syntax = "proto3";

service Streamer {
    rpc Stream(stream Request) returns (stream Response){}
    rpc ServerStream(Request) returns (stream Response) {}
}

message Request {
    int64 count = 1;
}

message Response {
    int64 count = 1;
}

protoc --proto_path=$GOPATH/src:. --micro_out=. --go_out=. stream.proto

server/main.go 代码如下:

package main

import (
	"context"
	"github.com/micro/go-micro"
	"io"
	"log"
	proto "zhaozhiliang.com/stream/server/proto"
)

type Streamer struct{}

//服务 方 的stream
func (e *Streamer) ServerStream(ctx context.Context, req *proto.Request, stream proto.Streamer_ServerStreamStream) error {
	log.Printf("Got msg %v", req.Count)
	for i := 0; i< int(req.Count); i++ {
		if err := stream.Send(&proto.Response{Count: int64(i)}); err != nil {
			return err
		}
	}
	return nil
}


//双向的 stream
func (e *Streamer) Stream(ctx context.Context, stream proto.Streamer_StreamStream) error {
	for {
		req, err := stream.Recv()
		if err == io.EOF {
			return nil
		}

		if err != nil {
			return err
		}
		log.Printf("Got msg %v", req.Count)
		if err := stream.Send(&proto.Response{Count: req.Count}); err != nil {
			return err
		}
	}
}


func main() {
	service := micro.NewService(
		micro.Name("go.micro.srv.stream"),
	)

	service.Init()
	proto.RegisterStreamerHandler(service.Server(), new(Streamer))

	if err := service.Run(); err != nil {
		log.Fatal(err)
	}


}

client/main.go 代码如下:

package main

import (
	"context"
	"fmt"
	"github.com/micro/go-micro"
	proto "zhaozhiliang.com/stream/server/proto"
)

//双向
func bidirectional(cl proto.StreamerService) {
	//创建 流 client
	stream, err := cl.Stream(context.Background())
	if err != nil {
		fmt.Println("err:", err)
		return
	}

	// 双向流,
	//发送  和 接受 messages   10次
	for j := 0; j < 10; j++ {
		if err := stream.Send(&proto.Request{Count: int64(j)}); err != nil {
			fmt.Println("err:", err)
		}

		rsp, err := stream.Recv()
		if err != nil {
			fmt.Println("recv err", err)
			break
		}

		fmt.Printf("Sent msg %v got msg %v\n", j, rsp.Count)
	}

	//关闭 stream
	if err := stream.Close(); err != nil {
		fmt.Println("stream close err:", err)
	}
}

func serverStream(cl proto.StreamerService) {
	//发送请求
	stream, err := cl.ServerStream(context.Background(), &proto.Request{Count: int64(10)})
	if err != nil {
		fmt.Println("err:", err)
		return
	}

	//服务端  来的 stream
	//接受到了 10个 消息
	for j := 0; j < 10; j++{
		rsp, err := stream.Recv()
		if err != nil {
			fmt.Println("recv err", err)
			break
		}
		fmt.Printf("got msg %v\n", rsp.Count)
	}

	//关闭 stream
	if err := stream.Close(); err != nil {
		fmt.Println("stream close err:", err)
	}
}

func main() {
	service := micro.NewService()
	service.Init()

	//创建 client
	cl := proto.NewStreamerService("go.micro.srv.stream", service.Client())

	//双向 流
	bidirectional(cl)

	//服务 单方面 流
	serverStream(cl)

}

web/main.go 代码如下:

package main

import (
	"context"
	"github.com/gorilla/websocket"
	"github.com/micro/go-micro/client"
	"github.com/micro/go-micro/web"
	"io"
	"log"
	"net/http"
	"time"
	proto "zhaozhiliang.com/stream/server/proto"
)

var upgrader = websocket.Upgrader{
	//允许跨域
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

func Stream (cli proto.StreamerService, ws *websocket.Conn) error {
	// 从 websocket 读取 请求,并初始化
	var req proto.Request
	err := ws.ReadJSON(&req)
	if err != nil {
		return err
	}
	log.Printf("Got req.Count %v from websocket", req.Count)

	//即便 我们不再 期望 来自websocket 更多的请求,我们仍需要 去 websocket 读取 内容,为了能获取到 close 信号
	//???
	go func() {
		for {
			if _, _, err := ws.NextReader(); err != nil {
				break
			}
		}
	}()


	log.Printf("Received Request: %v", req)

	//发送请求 给 stream server
	stream, err := cli.ServerStream(context.Background(), &req)
	if err != nil {
		return err
	}
	defer stream.Close()

	// 从 stream server 中读取消息,并且发送响应 给 websocket
	for {
		rsp, err := stream.Recv()
		log.Printf("Got msg %v from services", rsp.Count)
		if err != nil {
			if err != io.EOF {
				return err
			}
			break
		}

		// 写 response  给 websocket
		err = ws.WriteJSON(rsp)
		if err != nil {
			if isExpectedClose(err) {
				log.Println("Expected Close on socket", err)
				break
			} else {
				return err
			}

		}
	}

	return nil
}

func isExpectedClose(err error) bool  {
	if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway){
		log.Println("Unexpected websocket close:", err)
		return false
	}

	return true
}

func main() {
	//新建 web service
	service := web.NewService(
		web.Name("go.micro.web.stream"),
	)
	if err := service.Init(); err != nil {
		log.Fatal("Init", err)
	}

	//新建 RPC client
	rpcClient := client.NewClient(client.RequestTimeout(time.Second * 120))
	cli := proto.NewStreamerService("go.micro.srv.stream", rpcClient)

	//获取 静态文件 html/js
	service.Handle("/", http.FileServer(http.Dir("html")))

	//处理 websocket 连接
	service.HandleFunc("/stream", func( w http.ResponseWriter, r *http.Request) {
		//升级请求到 websocket
		conn, err := upgrader.Upgrade(w, r, nil)
		if err != nil {
			log.Fatal("Upgrade:", err)
		}
		defer conn.Close()

		//处理 websocket 请求
		if err := Stream(cli, conn); err != nil {
			log.Fatal("Echo :", err)
		}
		log.Println("stream complete")
	})

	if err := service.Run(); err != nil {
		log.Fatal("Run: ", err)
	}
}

web/html/index.html 代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Websocket Stream</title>
    <script src="./stream/main.js"></script>
    <style>
        table {
            table-layout: fixed;
        }
        td {
            border: 2px solid green;
        }
        td input {
            width: 100%;
            box-sizing: border-box;
        }
    </style>
</head>
<body>
<h2>Websocket Stream</h2>
<table>
    <tr>
        <td valign="top" width="25%">
            <p>
                <form>
            <p>
                Count: <br>
                <input id="count" name="count" type="number" min="1"/>
            </p>
            <p>
                <button type="button" id="send">Send</button>
                <button type="button" id="cancel">Cancel</button>
            </p>
            <p>
                <button type="button" id="open">Open Connection</button>
            </p>
            </form>
            </p>
        </td>
        <td valign="top" width="75%">
            <div id="output"/>
        </td>
    </tr>
</table>

</body>
</html>

web/html/main.js 代码如下:

var wsUri;
var output;
var count;
var ws;

window.addEventListener("load", function (evt) {
    wsUri = "ws://" + window.location.host + "/stream/stream"
    output = document.getElementById("output");
    count = document.getElementById("count");

    var print = function (message) {
        var d = document.createElement("div");
        d.innerHTML = message;
        output.appendChild(d);
    }


    var parseCount = function (evt) {
        return JSON.parse(evt.data).count
    }

    var newSocket = function () {
        ws = new WebSocket(wsUri);
        ws.onopen = function (evt) {
            print('<span style="color: green;"> Connection Open</span>');
        }
        ws.onclose = function (evt) {
            print('<span style="color: red;"> Connection Closed</span>');
            ws = null;
        }
        ws.onmessage = function (evt) {
            print('<span style="color:blue;">Update:</span>' + parseCount(evt));
        }
        ws.onerror = function (evt) {
            print('<span style="color: red;">Error:</span>' + parseCount(evt));
        }
    };

    newSocket()

    document.getElementById("send").onclick = function (evt) {
        if (!ws) {
            return false
        }

        var msg = {count: parseInt(count.value)}
        req = JSON.stringify(msg)
        print('<span style="color:blue;">Sent request: </span>' + req);
        ws.send(JSON.stringify(msg))
        return false;
    }

    document.getElementById("cancel").onclick = function(evt) {
        if (!ws) {
            return false;
        }
        ws.close();
        print('<span style="color: red;">Request Canceled</span>');
        return false;
    };

    document.getElementById("open").onclick = function(evt) {
        if (!ws) {
            newSocket()
        }
        return false;
    };

})

go.mod 代码如下:

module zhaozhiliang.com/stream

go 1.13

require (
	github.com/golang/protobuf v1.3.2
	github.com/micro/go-micro v1.18.0
)

运行程序:

运行 :

运行服务 : go run/server/main.go

运行micro web (它也可以为 websocket client 提供反向代理):micro web

运行 websocket client : cd web; go run main.go

运行client: go run client/main.go

访问 http://192.168.1.129:8082/stream  并 发送一个 请求。

本例子架构图:

 

 

 

 

 

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值