Golang微服务框架kratos实现SignalR服务

简介: SignalR 在可用的情况下使用新的 WebSocket 传输,并在必要时回退到旧传输。 虽然当然可以直接使用 WebSocket 编写应用,但使用 SignalR 意味着需要实现的许多额外功能已经为你完成。 最重要的是,这意味着你可以编写应用代码以利用 WebSocket,而无需担心为旧客户端创建单独的代码路径。 SignalR 还可以避免担心 WebSocket 的更新,因为 SignalR 已更新以支持基础传输中的更改,从而为应用程序提供跨 WebSocket 版本的一致接口。

Golang微服务框架kratos实现SignalR服务

基于 SignalR 可以实现客户端和服务器之间进行即时通信。

适合 SignalR 的应用场景:

需要从服务器进行高频率更新的应用。 示例包括游戏、社交网络、投票、拍卖、地图和 GPS 应用。
仪表板和监视应用。
协作应用。 协作应用的示例包括白板应用和团队会议软件。
需要通知的应用。 社交网络、电子邮件、聊天、游戏、旅行警报和很多其他应用都需使用通知。

SignalR 自动选择服务器和客户端能力范围内的最佳传输方法,如WebSockets、Server-Sent Events、长轮询。Hub 是一种高级管道,允许客户端和服务器相互调用方法。 SignalR 自动处理跨计算机边界的调度,并允许客户端调用服务器上的方法,反之亦然。SignalR 提供两个内置协议:基于 JSON 的文本协议和基于 MessagePack 的二进制协议。

什么是 SignalR?

ASP.NET SignalR 是一个面向 ASP.NET 开发人员的库,可简化向应用程序添加实时 Web 功能的过程。 实时 Web 功能是让服务器代码在可用时立即将内容推送到连接的客户端,而不是让服务器等待客户端请求新数据。

SignalR 可用于向 ASP.NET 应用程序添加任何类型的“实时”Web 功能。 虽然聊天通常用作示例,但你可以执行更多操作。 每当用户刷新网页以查看新数据,或页面实现 长时间轮询 以检索新数据时,它都是使用 SignalR 的候选项。 示例包括仪表板和监视应用程序、协作应用程序 (,例如同时编辑文档) 、作业进度更新和实时表单。

SignalR 还支持需要服务器进行高频率更新的全新 Web 应用程序类型,例如实时游戏。

SignalR 提供了一个简单的 API,用于创建服务器到客户端远程过程调用, (RPC) 调用客户端浏览器 (和其他客户端平台中的 JavaScript 函数,) 从服务器端 .NET 代码。 SignalR 还包括用于连接管理的 API (例如,连接和断开连接事件) ,以及分组连接。

SignalR 和 WebSocket

SignalR 在可用的情况下使用新的 WebSocket 传输,并在必要时回退到旧传输。 虽然当然可以直接使用 WebSocket 编写应用,但使用 SignalR 意味着需要实现的许多额外功能已经为你完成。 最重要的是,这意味着你可以编写应用代码以利用 WebSocket,而无需担心为旧客户端创建单独的代码路径。 SignalR 还可以避免担心 WebSocket 的更新,因为 SignalR 已更新以支持基础传输中的更改,从而为应用程序提供跨 WebSocket 版本的一致接口。

Kratos服务端

首先安装库:

go get -u github.com/tx7do/kratos-transport/transport/signalr

然后实现一个简单的服务端:

package main

import (
    "context"
    "fmt"
    "os"
    "os/signal"
    "strings"
    "syscall"
    "time"

    signalR "github.com/philippseith/signalr"
    "github.com/tx7do/kratos-transport/transport/signalr"
)

type chat struct {
   
    signalR.Hub
}

func (c *chat) OnConnected(connectionID string) {
   
    fmt.Printf("%s connected\n", connectionID)
    c.Groups().AddToGroup("group", connectionID)
}

func (c *chat) OnDisconnected(connectionID string) {
   
    fmt.Printf("%s disconnected\n", connectionID)
    c.Groups().RemoveFromGroup("group", connectionID)
}

func (c *chat) Broadcast(message string) {
   
    // Broadcast to all clients
    c.Clients().Group("group").Send("receive", message)
}

func (c *chat) Echo(message string) {
   
    c.Clients().Caller().Send("receive", message)
}

func (c *chat) Panic() {
   
    panic("Don't panic!")
}

func (c *chat) RequestAsync(message string) <-chan map[string]string {
   
    r := make(chan map[string]string)
    go func() {
   
        defer close(r)
        time.Sleep(4 * time.Second)
        m := make(map[string]string)
        m["ToUpper"] = strings.ToUpper(message)
        m["ToLower"] = strings.ToLower(message)
        m["len"] = fmt.Sprint(len(message))
        r <- m
    }()
    return r
}

func (c *chat) RequestTuple(message string) (string, string, int) {
   
    return strings.ToUpper(message), strings.ToLower(message), len(message)
}

func (c *chat) DateStream() <-chan string {
   
    r := make(chan string)
    go func() {
   
        defer close(r)
        for i := 0; i < 50; i++ {
   
            r <- fmt.Sprint(time.Now().Clock())
            time.Sleep(time.Second)
        }
    }()
    return r
}

func (c *chat) UploadStream(upload1 <-chan int, factor float64, upload2 <-chan float64) {
   
    ok1 := true
    ok2 := true
    u1 := 0
    u2 := 0.0
    c.Echo(fmt.Sprintf("f: %v", factor))
    for {
   
        select {
   
        case u1, ok1 = <-upload1:
            if ok1 {
   
                c.Echo(fmt.Sprintf("u1: %v", u1))
            } else if !ok2 {
   
                c.Echo("Finished")
                return
            }
        case u2, ok2 = <-upload2:
            if ok2 {
   
                c.Echo(fmt.Sprintf("u2: %v", u2))
            } else if !ok1 {
   
                c.Echo("Finished")
                return
            }
        }
    }
}

func (c *chat) Abort() {
   
    fmt.Println("Abort")
    c.Hub.Abort()
}

func main() {
   
    interrupt := make(chan os.Signal, 1)
    signal.Notify(interrupt, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)

    ctx := context.Background()

    srv := signalr.NewServer(
        signalr.WithAddress(":8100"),
        signalr.WithCodec("json"),
        signalr.WithHub(&chat{
   }),
    )

    srv.MapHTTP("/chat")

    if err := srv.Start(ctx); err != nil {
   
        panic(err)
    }

    defer func() {
   
        if err := srv.Stop(ctx); err != nil {
   
            t.Errorf("expected nil got %v", err)
        }
    }()

    <-interrupt
}

JS客户端

<!DOCTYPE html>
<html lang="en">

<head>
    <title>SignalR Client</title>
    <meta charset='utf-8'>
    <meta http-equiv='X-UA-Compatible' content='IE=edge'>
    <meta name='viewport' content='width=device-width, initial-scale=1'>
</head>

<body>
<input type="text" id="message"/>
<input type="button" value="Broadcast" id="broadcast"/>
<input type="button" value="Echo" id="echo"/>
<input type="button" value="Panic" id="panic"/>
<input type="button" value="RequestTuple" id="requesttuple"/>
<input type="button" value="RequestAsync" id="requestasync"/>
<input type="button" value="Stream" id="stream"/>
<input type="button" value="Stop Stream" id="stopstream"/>
<input type="button" value="Upstream" id="upstream"/>
<input type="button" value="Stop/Start Client Connection" id="stop"/>
<input type="button" value="Abort Server Connection" id="abort"/>
<ul id="messages">
</ul>

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/7.0.7/signalr.js"></script>

<script>
    let subscription;
    const connection = new signalR.HubConnectionBuilder()
        .withUrl('http://localhost:8100/chat')
        .withAutomaticReconnect()
        .build();

    document.getElementById('broadcast').addEventListener('click', () => {
    
        let val = document.getElementById('message').value;
        if (val) {
    
            connection.invoke('broadcast', val);
        }
    });
    document.getElementById('echo').addEventListener('click', () => {
    
        let val = document.getElementById('message').value;
        if (val) {
    
            connection.invoke('echo', val);
        }
    });
    document.getElementById('panic').addEventListener('click', () => {
    
        connection.invoke('panic').catch((err) => {
    
            let li = document.createElement('li');
            li.innerText = err;
            document.getElementById('messages').appendChild(li);
        });
    });

    document.getElementById('requestasync').addEventListener('click', () => {
    
        let val = document.getElementById('message').value;
        if (val) {
    
            connection.invoke('requestasync', val).then(val => {
    
                let li = document.createElement('li');
                li.innerText = 'received finally ' + JSON.stringify(val);
                document.getElementById('messages').appendChild(li);
            })
        }
    });

    document.getElementById('requesttuple').addEventListener('click', () => {
    
        let val = document.getElementById('message').value;
        if (val) {
    
            connection.invoke('requesttuple', val).then(val => {
    
                let li = document.createElement('li');
                li.innerText = 'received ' + JSON.stringify(val);
                document.getElementById('messages').appendChild(li);
            })
        }
    });
    document.getElementById('stream').addEventListener('click', () => {
    
        subscription = connection.stream('datestream').subscribe({
    
            next: (item) => {
    
                let li = document.createElement('li');
                li.innerText = 'item ' + item;
                document.getElementById('messages').appendChild(li);
            },
            complete: () => {
    
                let li = document.createElement('li');
                li.innerText = 'complete';
                document.getElementById('messages').appendChild(li);
            }
        })
    });
    document.getElementById('stopstream').addEventListener('click', () => {
    
        if (subscription) {
    
            subscription.dispose()
        }
    });
    document.getElementById('upstream').addEventListener('click', () => {
    
        const subject1 = new signalR.Subject();
        const subject2 = new signalR.Subject();
        connection.send("uploadstream", subject1, 3, subject2);
        let iteration1 = 0;
        const intervalHandle1 = setInterval(() => {
    
            iteration1++;
            subject1.next(iteration1);
            if (iteration1 === 5) {
    
                clearInterval(intervalHandle1);
                subject1.complete();
            }
        }, 500);
        let iteration2 = 0;
        const intervalHandle2 = setInterval(() => {
    
            iteration2++;
            subject2.next(iteration2);
            if (iteration2 === 10) {
    
                clearInterval(intervalHandle2);
                subject2.complete();
            }
        }, 100);
    });
    document.getElementById('stop').addEventListener('click', () => {
    
        connection.stop().then(() => {
    
            connection.start();
        });
    });
    document.getElementById('abort').addEventListener('click', () => {
    
        connection.send('abort')
    });

    connection.on('receive', message => {
    
        let li = document.createElement('li');
        li.innerText = 'sent ' + message;
        document.getElementById('messages').appendChild(li);
    });

    connection.onclose(error => {
    
        console.assert(connection.state === signalR.HubConnectionState.Disconnected);
        console.log('Connection closed due to error. Try refreshing this page to restart the connection', error);
    });
    connection.onreconnecting(error => {
    
        console.assert(connection.state === signalR.HubConnectionState.Reconnecting);
        console.log('Connection lost due to error. Reconnecting.', error);
    });
    connection.onreconnected(connectionId => {
    
        console.assert(connection.state === signalR.HubConnectionState.Connected);
        console.log('Connection reestablished. Connected with connectionId', connectionId);
    });

    async function start() {
    
        try {
    
            await connection.start();
            console.assert(connection.state === signalR.HubConnectionState.Connected);
            console.log('SignalR connection established');
        } catch (err) {
    
            console.assert(connection.state === signalR.HubConnectionState.Disconnected);
            console.error('SignalR Connection Error: ', err);
            setTimeout(() => this.start(), 5000);
        }
    }

    start();

</script>
</body>

</html>

参考资料 (Reference)

目录
相关文章
|
1月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
107 3
|
1月前
|
Cloud Native Java API
聊聊从单体到微服务架构服务演化过程
本文介绍了从单体应用到微服务再到云原生架构的演进过程。单体应用虽易于搭建和部署,但难以局部更新;面向服务架构(SOA)通过模块化和服务总线提升了组件复用性和分布式部署能力;微服务则进一步实现了服务的独立开发与部署,提高了灵活性;云原生架构则利用容器化、微服务和自动化工具,实现了应用在动态环境中的弹性扩展与高效管理。这一演进体现了软件架构向着更灵活、更高效的方向发展。
|
26天前
|
Kubernetes 负载均衡 Docker
构建高效后端服务:微服务架构的探索与实践
【10月更文挑战第20天】 在数字化时代,后端服务的构建对于任何在线业务的成功至关重要。本文将深入探讨微服务架构的概念、优势以及如何在实际项目中有效实施。我们将从微服务的基本理念出发,逐步解析其在提高系统可维护性、扩展性和敏捷性方面的作用。通过实际案例分析,揭示微服务架构在不同场景下的应用策略和最佳实践。无论你是后端开发新手还是经验丰富的工程师,本文都将为你提供宝贵的见解和实用的指导。
|
25天前
|
监控 API 持续交付
构建高效后端服务:微服务架构的深度探索
【10月更文挑战第20天】 在数字化时代,后端服务的构建对于支撑复杂的业务逻辑和海量数据处理至关重要。本文深入探讨了微服务架构的核心理念、实施策略以及面临的挑战,旨在为开发者提供一套构建高效、可扩展后端服务的方法论。通过案例分析,揭示微服务如何帮助企业应对快速变化的业务需求,同时保持系统的稳定性和灵活性。
46 9
|
27天前
|
监控 安全 Java
构建高效后端服务:微服务架构深度解析与最佳实践###
【10月更文挑战第19天】 在数字化转型加速的今天,企业对后端服务的响应速度、可扩展性和灵活性提出了更高要求。本文探讨了微服务架构作为解决方案,通过分析传统单体架构面临的挑战,深入剖析微服务的核心优势、关键组件及设计原则。我们将从实际案例入手,揭示成功实施微服务的策略与常见陷阱,为开发者和企业提供可操作的指导建议。本文目的是帮助读者理解如何利用微服务架构提升后端服务的整体效能,实现业务快速迭代与创新。 ###
60 2
|
1月前
|
消息中间件 Kafka 数据库
微服务架构中,如何确保服务之间的数据一致性?
微服务架构中,如何确保服务之间的数据一致性?
|
1月前
|
Dubbo Java 应用服务中间件
Dubbo学习圣经:从入门到精通 Dubbo3.0 + SpringCloud Alibaba 微服务基础框架
尼恩团队的15大技术圣经,旨在帮助开发者系统化、体系化地掌握核心技术,提升技术实力,从而在面试和工作中脱颖而出。本文介绍了如何使用Dubbo3.0与Spring Cloud Gateway进行整合,解决传统Dubbo架构缺乏HTTP入口的问题,实现高性能的微服务网关。
|
28天前
|
运维 Kubernetes 开发者
构建高效后端服务:微服务架构与容器化技术的结合
【10月更文挑战第18天】 在数字化转型的浪潮中,企业对后端服务的要求日益提高,追求更高的效率、更强的可伸缩性和更易于维护的系统。本文将探讨微服务架构与容器化技术如何结合,以构建一个既灵活又高效的后端服务体系。通过分析当前后端服务面临的挑战,介绍微服务和容器化的基本概念,以及它们如何相互配合来优化后端服务的性能和管理。本文旨在为开发者提供一种实现后端服务现代化的方法,从而帮助企业在竞争激烈的市场中脱颖而出。
26 0
|
12天前
|
设计模式 Java API
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
56 6
|
12天前
|
设计模式 Java API
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
29 1