asp.net core 实现websocket通信

项目中需要向网页上主动推送信息并弹层提示用户,目前有两种实现方法

  1. ajax轮询
  2. http 1.1 长连接
  3. websocket主动推送

网页主要用在手机端。

  • 第一种不断发请求浪费流量且不稳定
  • 第二种对服务器资源占用较大,针对每一次请求都要异步阻塞维持
  • 第三种websocket,客户端与服务器只需第一次通过带有websocket标识的Http请求建立连接,不需要每次解析http请求及用户信息,连接维持交由web服务器,handler也无需每次解析请求状态。

三者的优缺点及原理讲解主要是看的这两篇文章"看完让你彻底搞懂Websocket原理",WebSocket原理及技术简介

最终选择了websocket,以下是asp.net core 测试实现方式。


  1. .net core 提供了websocketserver的组件,需要在nuget上引用Microsoft.AspNetCore.WebSockets.Server
    在project.json 文件中添加引用,然后点击restore引用,如图

image

  1. 在项目中添加一个websocktHandler的处理类,处理接收到请求后的逻辑
  2. 在startup.cs 中注册 websocket 服务,并将处理回调注册到流程中,当请求为"/ws"走websockt处理handler
  3. websocket 制定了标识位来标识当前请求,根据该标识处理不同的逻辑

opcode的值: 0x1代表此帧为文本数据帧, 0x2代表此帧为二进制数据帧, 0x8为控制帧中的连接关闭帧(close frame), 0x9为控制帧中的Ping帧, 0xA(十进制的10)为控制帧中的Pong帧。

  1. 接收请求后将当前请求的socket以键值对方式存入内存中,以便给指定用户发送信息
  2. 根据接收到的数据内容,按分隔符分割处理,获取到【发送人】【数据】【接收人】
  3. 分隔符在配置文件中配置

.net core 添加自定义配置,需要引用Microsoft.Extensions.Options.ConfigurationExtensions,引用方式同上。

8.startup.cs 中注册,新建AppSettings类,并映射

 services.AddOptions();
 services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));

以上便是websocket服务端的处理流程,最终效果如下

image

下面是代码

WebSocketHandler.cs



using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Options;

namespace WebApplication
{
    public class SocketHandler
    {

        static Dictionary<string, WebSocket> userDic = new Dictionary<string, WebSocket>();
        SocketHandler(WebSocket socket)
        {
            this.socket = socket;
            this.BufferSize = AppSettingsServices.AppSettings.WebSocketConfig.MaxLenth;
        }

        int BufferSize;
        WebSocket socket;
        async Task EchoLoop()
        {
            var buffer = new byte[BufferSize];
            var seg = new ArraySegment<byte>(buffer);
            while (this.socket.State == WebSocketState.Open)
            {

                var incoming = await this.socket.ReceiveAsync(seg, CancellationToken.None);

                if (incoming.MessageType == WebSocketMessageType.Close)
                {
                    if(userDic.Values.Contains(this.socket))
                    {
                        userDic.Remove(userDic.FirstOrDefault(m=>m.Value==this.socket).Key);
                    }
                    await socket.CloseAsync(WebSocketCloseStatus.NormalClosure,
                    String.Empty, CancellationToken.None);

                }
                else
                {
                    // string receviceString = System.Text.Encoding.UTF8.GetString(buffer);
                    string receviceString = System.Text.Encoding.UTF8.GetString(buffer.Where(b => b != 0).ToArray());
                    if (string.IsNullOrEmpty(receviceString)) { return; }
                    string separator = AppSettingsServices.AppSettings.WebSocketConfig.Separator;
                    var msg = receviceString.Split(new[] { separator }, StringSplitOptions.None);
                    string toname = string.Empty;
                    string data = string.Empty;
                    string fromname = string.Empty;
                 
                    if (msg.Length > 2)
                    {
                        toname = msg[0];
                        data = msg[1];
                        fromname = msg[2];
                    }
                    string tomsg = data;
                    List<WebSocket> toSocketList = new List<WebSocket>();
                    if (!string.IsNullOrEmpty(toname))
                    {
                        if (!userDic.ContainsKey(fromname))
                        {
                            userDic.Add(fromname, this.socket);
                        }
                        if (userDic.ContainsKey(toname))
                        {
                            toSocketList.Add(userDic[toname]);
                        }
                        else
                        {
                            toSocketList.Add(userDic[fromname]);
                            tomsg = "用户不在线";
                        }
                    }
                    else
                    {
                        toSocketList.AddRange(userDic.Values.ToList());
                    }
                    byte[] toBuffer = System.Text.Encoding.UTF8.GetBytes(tomsg);
                    var outgoing = new ArraySegment<byte>(toBuffer);
                    foreach(WebSocket s in toSocketList){
                      await  s.SendAsync(outgoing, WebSocketMessageType.Text, true, CancellationToken.None);
                    }
                    // await this.socket.SendAsync(outgoing, WebSocketMessageType.Text, true, CancellationToken.None);
                }
            }
        }
        static async Task Acceptor(HttpContext hc, Func<Task> n)
        {
            if (!hc.WebSockets.IsWebSocketRequest)
                return;
            var socket = await hc.WebSockets.AcceptWebSocketAsync();

            var h = new SocketHandler(socket);
            await h.EchoLoop();
        }
        /// <summary>
        /// branches the request pipeline for this SocketHandler usage
        /// </summary>
        /// <param name="app"></param>
        public static void Map(IApplicationBuilder app)
        {

            app.UseWebSockets();
            app.Use(SocketHandler.Acceptor);

        }
    }

}

AppSettings.cs

namespace WebApplication
{
    public class AppSettings
    {
        public WebSocketConfig WebSocketConfig { get; set; }

    }
    public class WebSocketConfig
    {
        //单次发送最大值
        public int MaxLenth { get; set; }
        //to和data的分割符号
        public string Separator { get; set; }
    }
}

AppSettingsServices.cs

using Microsoft.Extensions.Options;
namespace WebApplication
{
    public class AppSettingsServices
    {
        public static  AppSettings AppSettings;
        public static void SetAppSettings(IOptions<AppSettings>  v)
        {
          AppSettings= v.Value;
        }

    }

}

Startup.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using WebApplication;
using WebApplication.Data;
using WebApplication.Models;
using WebApplication.Services;

namespace WebApplication
{
    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

            if (env.IsDevelopment())
            {
                // For more details on using the user secret store see https://go.microsoft.com/fwlink/?LinkID=532709
                builder.AddUserSecrets();
            }

            builder.AddEnvironmentVariables();
            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));

            services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

            services.AddMvc();

            // Add application services.
            services.AddTransient<IEmailSender, AuthMessageSender>();
            services.AddTransient<ISmsSender, AuthMessageSender>();

            services.AddOptions();
            services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));


        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IOptions<AppSettings> appsettings)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();

            app.UseIdentity();

            // Add external authentication middleware below. To configure them please see https://go.microsoft.com/fwlink/?LinkID=532715

            app.UseMvc(routes =>
            {
                
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });

            AppSettingsServices.SetAppSettings(appsettings);
            app.Map("/ws", SocketHandler.Map);

        }
        // public async void a (IApplicationBuilder b){
        //       b.UseWebSockets();
        //     b.Use(new SocketHandler.Acceptor);
        // }
    }
}

客户端JS代码

 //w:向谁发,d 发送内容,f 谁发的
    function sendData(w, d, f) {
        initWebSocket();
        if (webSocket.OPEN && webSocket.readyState == 1) {
            var s = "<custom_separator>";
            webSocket.send(w + s + d + s + f);
        }
        if (webSocket.readyState == 2 || webSocket.readyState == 3) {
            $("#div_receive").append("WebSocket closed");
        }
    }
    function initWebSocket() {
        var url = "ws://localhost:5000/ws";
        if (!webSocket) {
            webSocket = new WebSocket(url);
            //Open connection  handler.
            webSocket.onopen = function () {
                $("#div_receive").append("WebSocket opened" + "<br>");

            };

            //Message data handler.
            webSocket.onmessage = function (e) {
                $("#div_receive").append(e.data + "<br>");
            };

            //Close event handler.
            webSocket.onclose = function () {
                $("#div_receive").append("WebSocket closed." + "<br>");
            };

            //Error event handler.
            webSocket.onerror = function (e) {
                $("#div_receive").append(e.message + "<br>");
            }
        }
    }
posted @ 2017-01-20 16:03  鹤空  阅读(2400)  评论(0编辑  收藏  举报