17、实时通信技术:WebSocket与云消息服务的应用

实时通信技术:WebSocket与云消息服务的应用

1. WebSocket协议概述

在当今的互联网应用中,实时交互功能变得越来越重要。比如在Facebook上,当有人对我们的帖子进行评论时会弹出通知;在Twitter上,当有人转发我们的推文时,页面会动态更新。虽然并非所有这些功能都明确通过WebSocket实现,但几年前大部分是这样,而且现在很多功能仍通过WebSocket或在开发者看来类似WebSocket的技术来支持。

WebSocket协议大约在2008年出现,它定义了浏览器和服务器之间建立持久、双向套接字连接的方式。这种连接允许在浏览器中运行的Web应用程序向服务器发送数据,同时服务器也能在无需应用程序“轮询”(定期检查更新)的情况下向客户端发送数据。

在底层,浏览器会向服务器请求连接升级。握手完成后,浏览器和服务器会切换到一个单独的二进制TCP连接进行双向通信。一个请求连接升级的HTTP请求示例如下:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

WebSocket的应用场景非常广泛,从社交媒体网站的弹出通知推送,到实时更新的流式仪表板和监控控制台,甚至使用HTML、图形和CSS开发交互式多人游戏。

2. 部署模型及其挑战

在传统的部署模型中,我们需要启动一个服务器(物理或虚拟),安装托管产品(如IIS Web服务器或WebSphere等J2EE容器),然后部署应用程序。如果应用程序具有可扩展性且能在服务器群中运行,我们需要为服务器群或集群中的每个服务器重复此过程。

当用户连接到网站上打开WebSocket连接的页面时,该连接会与处理初始请求的服务器保持打开状态,直到用户刷新页面或点击其他链接。不过,使用代理和防火墙时可能会出现一些问题。

如果所有服务器都运行在AWS的EC2实例上,基于云的基础设施会使虚拟机随时可能被重新定位、销毁和重建。这虽然有利于应用程序实现近乎无限的扩展,但也意味着“实时”的WebSocket连接可能会在毫无预兆的情况下中断或变得陈旧无响应。此外,与单个服务器建立始终开启的TCP连接会影响应用程序的扩展能力。根据应用程序代码处理的请求和数据量,管理这些连接和数据交换可能会成为一个麻烦的负担。

解决这些问题的常见方法是将WebSocket的使用外部化,即将WebSocket连接和数据传输的管理工作卸载到应用程序代码之外的组件。另一个有助于扩展的解决方案是完全避免使用WebSocket,转而使用基于HTTP的消息系统。简单来说,与其让应用程序自行管理WebSocket,不如让专业的云消息服务提供商来处理。

3. 云消息服务提供商的选择

为了使应用程序具备实时功能,我们希望微服务能够向客户端推送数据,同时应用程序也能通过相同或类似的消息管道将消息发送到后端。为了让微服务保持云原生特性,并能够在云中自由扩展和迁移,我们需要选择一个消息服务提供商来处理部分实时功能。

提供消息服务的公司众多,且数量还在不断增加。以下是一些提供云消息服务的公司:
| 公司名称 | 服务内容 |
| ---- | ---- |
| Apigee | API网关和实时消息传递 |
| PubNub | 实时消息传递和状态管理 |
| Pusher | 实时消息传递和状态管理 |
| Kaazing | 实时消息传递 |
| Mashery | API网关和实时消息传递 |
| Google | Google Cloud Messaging |
| ASP.NET SignalR | 托管在Azure中的实时消息传递 |
| Amazon | 简单通知服务 |

选择消息服务提供商的标准完全取决于自身需求、应用程序类型、预算、预期流量以及是否包含移动设备或物联网组件等因素。无论选择哪种机制,都应该花些时间将代码与具体的服务提供商隔离开来,以便在需要时能够轻松更换,而不会对代码产生过大影响。使用反腐败层(ACL)是一个不错的选择。

在本文中,我们选择了PubNub作为示例,原因包括其简单的SDK、优秀的文档、丰富的公共示例,以及无需提供信用卡信息即可用于演示目的。

4. 构建接近度监控器

我们之前构建了一个由多个微服务组成的应用程序,用于检测队友何时进入彼此的范围内。当系统检测到两个相邻的队友时,会将 ProximityDetectedEvent 事件发送到队列中。现在,我们要构建一个监控器,以便在后端系统检测到这些接近度事件时实时更新。

4.1 创建接近度监控器服务

接近度监控器示例包含几个不同的组件。首先,我们需要消费从队列中获取的 ProximityDetectedEvent 事件。然后,提取该事件的原始信息,并调用团队服务获取团队和成员的友好名称等信息。最后,将增强后的数据通过实时消息系统(这里使用PubNub)发送出去。

以下是接近度监控器的核心代码 ProximityDetectedEventProcessor.cs

using System;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StatlerWaldorfCorp.ProximityMonitor.Queues;
using StatlerWaldorfCorp.ProximityMonitor.Realtime;
using StatlerWaldorfCorp.ProximityMonitor.TeamService;

namespace StatlerWaldorfCorp.ProximityMonitor.Events
{
    public class ProximityDetectedEventProcessor : IEventProcessor
    {
        private ILogger logger;
        private IRealtimePublisher publisher;
        private IEventSubscriber subscriber;
        private PubnubOptions pubnubOptions;

        public ProximityDetectedEventProcessor(
            ILogger<ProximityDetectedEventProcessor> logger,
            IRealtimePublisher publisher,
            IEventSubscriber subscriber,
            ITeamServiceClient teamClient,
            IOptions<PubnubOptions> pubnubOptions)
        {
            this.logger = logger;
            this.pubnubOptions = pubnubOptions.Value;
            this.publisher = publisher;
            this.subscriber = subscriber;
            logger.LogInformation("Created Proximity Event Processor.");

            subscriber.ProximityDetectedEventReceived += (pde) => {
                Team t = teamClient.GetTeam(pde.TeamID);
                Member sourceMember = teamClient.GetMember(pde.TeamID, pde.SourceMemberID);
                Member targetMember = teamClient.GetMember(pde.TeamID, pde.TargetMemberID);
                ProximityDetectedRealtimeEvent outEvent = new ProximityDetectedRealtimeEvent
                {
                    TargetMemberID = pde.TargetMemberID,
                    SourceMemberID = pde.SourceMemberID,
                    DetectionTime = pde.DetectionTime,
                    SourceMemberLocation = pde.SourceMemberLocation,
                    TargetMemberLocation = pde.TargetMemberLocation,
                    MemberDistance = pde.MemberDistance,
                    TeamID = pde.TeamID,
                    TeamName = t.Name,
                    SourceMemberName = $"{sourceMember.FirstName} {sourceMember.LastName}",
                    TargetMemberName = $"{targetMember.FirstName} {targetMember.LastName}"
                };
                publisher.Publish(
                    this.pubnubOptions.ProximityEventChannel,
                    outEvent.toJson());
            };
        }

        public void Start()
        {
            subscriber.Subscribe();
        }

        public void Stop()
        {
            subscriber.Unsubscribe();
        }
    }
}

在这段代码中,构造函数注入了多个依赖项:
- Logger :用于记录日志。
- Real - time event publisher :用于在指定通道上发布字符串消息。
- Event subscriber :监听队列(RabbitMQ)中的 ProximityDetectedEvent 消息。
- Team service client :用于查询团队服务获取团队和成员的详细信息。
- PubNub options :包含消息发布通道等信息。

4.2 创建实时发布器类

为了更好地管理代码,未来可以创建一个小类,负责从每个接收到的 ProximityDetectedEvent 创建 ProximityDetectedRealtimeEvent 的新实例。这不仅具有反腐败功能,还能获取团队成员的友好信息。

以下是使用PubNub API实现 IRealtimePublisher 接口的代码 PubnubRealtimePublisher.cs

using Microsoft.Extensions.Logging;
using PubnubApi;

namespace StatlerWaldorfCorp.ProximityMonitor.Realtime
{
    public class PubnubRealtimePublisher : IRealtimePublisher
    {
        private ILogger logger;
        private Pubnub pubnubClient;

        public PubnubRealtimePublisher(
            ILogger<PubnubRealtimePublisher> logger,
            Pubnub pubnubClient)
        {
            logger.LogInformation(
                "Realtime Publisher (Pubnub) Created.");
            this.logger = logger;
            this.pubnubClient = pubnubClient;
        }

        public void Validate()
        {
            pubnubClient.Time()
               .Async(new PNTimeResultExt(
                    (result, status) => {
                        if (status.Error) {
                            logger.LogError(
                                $"Unable to connect to Pubnub {status.ErrorData.Information}");
                            throw status.ErrorData.Throwable;
                        } else {
                            logger.LogInformation("Pubnub connection established.");
                        }
                    }
                ));
        }

        public void Publish(string channelName, string message)
        {
            pubnubClient.Publish()
               .Channel(channelName)
               .Message(message)
               .Async(new PNPublishResultExt(
                    (result, status) => {
                        if (status.Error) {
                            logger.LogError(
                                $"Failed to publish on channel {channelName}: {status.ErrorData.Information}");
                        } else {
                            logger.LogInformation(
                                $"Published message on channel {channelName}, {status.AffectedChannels.Count} affected channels, code: {status.StatusCode}");
                        }
                    }
                ));
        }
    }
}

这段代码是对PubNub SDK的简单封装,通过验证连接和发布消息的方法实现了实时消息的发布。

5. 依赖注入与工厂类

为了将PubNub API的实例注入到 PubnubRealtimePublisher 类中,我们需要使用ASP.NET Core的依赖注入功能。以下是 Startup.cs 文件中的相关代码:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StatlerWaldorfCorp.ProximityMonitor.Queues;
using StatlerWaldorfCorp.ProximityMonitor.Realtime;
using RabbitMQ.Client.Events;
using StatlerWaldorfCorp.ProximityMonitor.Events;
using Microsoft.Extensions.Options;
using RabbitMQ.Client;
using StatlerWaldorfCorp.ProximityMonitor.TeamService;

namespace StatlerWaldorfCorp.ProximityMonitor
{
    public class Startup
    {
        public Startup(IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole();
            loggerFactory.AddDebug();

            var builder = new ConfigurationBuilder()
               .SetBasePath(env.ContentRootPath)
               .AddJsonFile("appsettings.json", optional: false, reloadOnChange: false)
               .AddEnvironmentVariables();
            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddOptions();
            services.Configure<QueueOptions>(Configuration.GetSection("QueueOptions"));
            services.Configure<PubnubOptions>(Configuration.GetSection("PubnubOptions"));
            services.Configure<TeamServiceOptions>(Configuration.GetSection("teamservice"));
            services.Configure<AMQPOptions>(Configuration.GetSection("amqp"));
            services.AddTransient(typeof(IConnectionFactory), typeof(AMQPConnectionFactory));
            services.AddTransient(typeof(EventingBasicConsumer), typeof(RabbitMQEventingConsumer));
            services.AddSingleton(typeof(IEventSubscriber), typeof(RabbitMQEventSubscriber));
            services.AddSingleton(typeof(IEventProcessor), typeof(ProximityDetectedEventProcessor));
            services.AddTransient(typeof(ITeamServiceClient), typeof(HttpTeamServiceClient));
            services.AddRealtimeService();
            services.AddSingleton(typeof(IRealtimePublisher), typeof(PubnubRealtimePublisher));
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IEventProcessor eventProcessor, IOptions<PubnubOptions> pubnubOptions, IRealtimePublisher realtimePublisher)
        {
            realtimePublisher.Validate();
            realtimePublisher.Publish(
                pubnubOptions.Value.StartupChannel,
                "{'hello': 'world'}");
            eventProcessor.Start();
            app.UseMvc();
        }
    }
}

AddRealtimeService 是一个静态扩展方法,用于简化服务提供商对 IRealtimePublisher 实现的注入。为了确保所有配置(包括秘密API密钥)都能被注入,我们需要注册一个工厂类。以下是 PubnubFactory.cs 的代码:

using Microsoft.Extensions.Options;
using PubnubApi;
using Microsoft.Extensions.Logging;

namespace StatlerWaldorfCorp.ProximityMonitor.Realtime
{
    public class PubnubFactory
    {
        private PNConfiguration pnConfiguration;
        private ILogger logger;

        public PubnubFactory(IOptions<PubnubOptions> pubnubOptions, ILogger<PubnubFactory> logger)
        {
            this.logger = logger;
            pnConfiguration = new PNConfiguration();
            pnConfiguration.PublishKey = pubnubOptions.Value.PublishKey;
            pnConfiguration.SubscribeKey = pubnubOptions.Value.SubscribeKey;
            pnConfiguration.Secure = false;
        }

        public Pubnub CreateInstance()
        {
            return new Pubnub(pnConfiguration);
        }
    }
}

这个工厂类根据 PubnubOptions 创建 Pubnub 类的实例。通过 RealtimeServiceCollectionExtensions.cs 中的静态扩展方法将工厂类插入到依赖注入机制中:

using System;
using Microsoft.Extensions.DependencyInjection;
using PubnubApi;

namespace StatlerWaldorfCorp.ProximityMonitor.Realtime
{
    public static class RealtimeServiceCollectionExtensions
    {
        public static IServiceCollection AddRealtimeService(this IServiceCollection services)
        {
            services.AddTransient<PubnubFactory>();
            return AddInternal(services, p => p.GetRequiredService<PubnubFactory>(), ServiceLifetime.Singleton);
        }

        private static IServiceCollection AddInternal(this IServiceCollection collection, Func<IServiceProvider, PubnubFactory> factoryProvider, ServiceLifetime lifetime)
        {
            Func<IServiceProvider, object> factoryFunc = provider =>
            {
                var factory = factoryProvider(provider);
                return factory.CreateInstance();
            };

            var descriptor = new ServiceDescriptor(
                typeof(Pubnub),
                factoryFunc, lifetime);
            collection.Add(descriptor);
            return collection;
        }
    }
}

通过这种方式,任何需要 Pubnub 对象实例的对象都将通过我们注册的工厂函数来获取。

6. 测试与验证

为了验证整个系统是否正常工作,我们可以手动将 ProximityDetectedEvent 的JSON字符串放入 proximitydetected 队列中。如果接近度监控服务正在运行并订阅了该队列,且团队服务也在运行并包含所有必要的数据,那么接近度监控器将获取该事件,增强数据,并通过PubNub发送实时事件。我们可以使用PubNub调试控制台查看处理结果。

同时,我们可以复制并修改GitHub仓库中的脚本文件,使用示例数据填充团队服务,并使用包含测试接近度事件的示例JSON文件进行测试,而无需启动之前的代码。

7. 创建实时接近度监控器UI

虽然我们已经有了一个能够处理接近度事件并将其发送到实时消息系统的微服务,但还没有对实时消息进行有意义的处理。我们可以利用这些消息在地图UI上移动图钉,动态更新表格或图表,或者在Web UI中创建小提示或弹出通知。根据消息服务提供商的不同,还可以将这些消息自动转换为推送通知并发送到团队成员的移动设备上。

为了简化操作,我们使用一个简单的HTML页面,无需任何图形和服务器支持。以下是 realtimetest.html 的代码:

<html>
<head>
    <title>RT page sample</title>
    <script src="https://cdn.pubnub.com/sdk/javascript/pubnub.4.4.0.js"></script>
    <script>
        var pubnub = new PubNub({
            subscribeKey: "yoursubkey",
            publishKey: "yourprivatekey",
            ssl: true
        });
        pubnub.addListener({
            message: function(m) {
                var channelName = m.channel;
                var channelGroup = m.subscription;
                var pubTT = m.timetoken;
                var msg = JSON.parse(m.message);
                console.log("New Message!!", msg);
                var newDiv = document.createElement('div')
                var newStr = "** (" + msg.TeamName + ") " + msg.SourceMemberName + " moved within " + msg.MemberDistance + "km of " + msg.TargetMemberName;
                newDiv.innerHTML = newStr
                var oldDiv = document.getElementById('chatLog')
                oldDiv.appendChild(newDiv)
            },
            presence: function(p) {
            },
            status: function(s) {
            }
        });
        console.log("Subscribing..");
        pubnub.subscribe({
            channels: ['proximityevents']
        });
    </script>
</head>
<body>
    <h1>Proximity Monitor</h1>
    <p>Proximity Events listed below.</p>
    <div id="chatLog"></div>
</body>
</html>

这个HTML页面通过JavaScript监听 proximityevents 通道的消息,并将接收到的消息动态添加到 chatLog div元素中。值得注意的是,这个文件无需托管在服务器上,在任何浏览器中打开即可运行。大多数云消息服务提供商(如Amazon、Azure和Google)都提供易于使用的SDK和丰富的文档示例,方便实现后端服务、Web浏览器用户、移动设备和其他集成点之间的实时通信。

综上所述,通过WebSocket协议和云消息服务提供商,我们可以为应用程序添加强大的实时功能。选择合适的服务提供商和正确的实现方式,能够帮助我们应对部署和扩展过程中的挑战,为用户提供更好的实时交互体验。

实时通信技术:WebSocket与云消息服务的应用(续)

8. 实时通信技术的优势与价值

实时通信技术在现代应用开发中具有显著的优势和价值,以下是一些主要方面:
- 增强用户体验 :实时更新和交互能够让用户及时获取信息,提高用户参与度和满意度。例如,社交媒体的实时通知、实时聊天等功能,让用户能够第一时间了解到感兴趣的动态。
- 提高业务效率 :在企业应用中,实时数据的传递可以加快决策过程,提高工作效率。比如,实时监控系统可以及时发现问题并采取措施,减少损失。
- 支持创新应用 :实时通信技术为各种创新应用提供了可能,如实时协作工具、多人在线游戏、物联网应用等。这些应用能够满足用户多样化的需求,推动行业的发展。

9. 实时通信技术的发展趋势

随着技术的不断进步,实时通信技术也在不断发展和演变。以下是一些可能的发展趋势:
- 与人工智能的融合 :将实时通信技术与人工智能相结合,可以实现更加智能的交互和决策。例如,智能客服可以实时响应用户的问题,并提供个性化的解决方案。
- 物联网的普及 :物联网的发展将使得更多的设备需要实时通信。实时通信技术将在物联网领域发挥重要作用,实现设备之间的实时数据传输和协同工作。
- 5G技术的推动 :5G技术的高速、低延迟特性将进一步提升实时通信的性能。这将为实时视频、虚拟现实等对实时性要求较高的应用提供更好的支持。

10. 实时通信技术的挑战与应对策略

尽管实时通信技术具有诸多优势,但也面临一些挑战。以下是一些常见的挑战及应对策略:
| 挑战 | 应对策略 |
| ---- | ---- |
| 网络稳定性 | 采用冗余网络、优化网络配置,以提高网络的可靠性。同时,使用断线重连机制,确保在网络中断后能够尽快恢复连接。 |
| 数据安全 | 采用加密技术对数据进行加密传输,防止数据泄露。同时,加强身份验证和访问控制,确保只有授权用户能够访问数据。 |
| 系统性能 | 优化系统架构,采用分布式系统和缓存技术,提高系统的处理能力。同时,进行性能测试和优化,确保系统在高并发情况下能够稳定运行。 |

11. 实时通信技术的应用案例分析

为了更好地理解实时通信技术的应用,以下是一些实际的应用案例分析:
- 社交媒体平台 :社交媒体平台广泛应用实时通信技术,如Facebook的实时通知、Twitter的实时推文更新等。这些功能让用户能够及时了解到朋友和关注者的动态,提高了用户的参与度和粘性。
- 金融交易系统 :金融交易系统对实时性要求极高,实时通信技术可以确保交易信息的及时传递和处理。例如,股票交易系统可以实时更新股票价格和交易信息,让投资者能够及时做出决策。
- 在线教育平台 :在线教育平台利用实时通信技术实现了实时互动教学。教师和学生可以通过视频会议、实时聊天等方式进行交流和互动,提高了教学效果。

12. 实时通信技术的实践建议

在实际应用中,为了更好地利用实时通信技术,以下是一些实践建议:
- 选择合适的技术和服务提供商 :根据应用的需求和特点,选择合适的实时通信技术和服务提供商。考虑技术的性能、稳定性、安全性以及服务提供商的支持和服务质量。
- 进行充分的测试和优化 :在上线之前,进行充分的测试和优化,确保系统的性能和稳定性。测试包括功能测试、性能测试、安全测试等。
- 关注技术发展动态 :实时通信技术发展迅速,关注技术发展动态,及时引入新的技术和方法,以提升应用的竞争力。

13. 总结与展望

实时通信技术在现代应用开发中具有重要的地位和作用。通过WebSocket协议和云消息服务提供商,我们可以为应用程序添加强大的实时功能。在选择服务提供商和实现方式时,需要考虑应用的需求、性能、扩展性和安全性等因素。同时,我们也需要关注实时通信技术的发展趋势,不断探索和创新,以满足用户日益增长的需求。

未来,随着技术的不断进步,实时通信技术将在更多的领域得到应用,为人们的生活和工作带来更多的便利和创新。我们期待着实时通信技术能够创造出更加美好的未来。

以下是实时通信系统的简单流程图:

graph LR
    A[用户操作] --> B[发送请求]
    B --> C{服务器处理}
    C -->|成功| D[实时消息推送]
    C -->|失败| E[错误处理]
    D --> F[客户端接收消息]
    F --> G[更新UI]

这个流程图展示了实时通信系统的基本工作流程,从用户操作开始,经过服务器处理,最终将实时消息推送给客户端并更新UI。通过这样的流程,用户可以获得实时的交互体验。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值