Creating “Login form”

We use here simple form where user can insert his or her preferred nick name for chat. To keep us focused on WebSocket stuff we don’t add any logic or checks in this point. Let’s add view called InsertUserName.cshtml under Home folder.


<form action="@Url.Action("Index")" method="post">
    <input type="text" placeholder="Insert user name" name="userName" />
    <input type="submit" value="Eńter" />
</form>

There will be another view for chat room and we will come back to it later when web sockets related code is done. Index() methods of home controller look like this.


[HttpGet]
public IActionResult Index()
{
    return View("InsertUserName");
} [HttpPost]
public IActionResult Index(string username)
{
    return View("Index", username);
}

If request method is GET then we show nick name form and if request method is POST we will show chat room view.

WebSockets middleware

Now let’s write ASP.Core middleware for WebSocket. To keep things simple I mixed together custom WebSocket middleware and custom WebSocket connection manager from Radu Matei’s post Creating a WebSockets middleware for ASP .NET Core. I like the work Radu has done but here we will keep things as small as possible. To get better understanding of WebSockets I suggest you to go through Radu’s post.

NB! To use WebSockets in ASP.NET Core project add reference to Microsoft.AspNetCore.WebSockets NuGet package!

In breaf, this is what our WebSocket middleware class does:

  1. Keep concurrent dictionary with connected WebSockets (this is needed for message broadcast)
  2. Read messages from WebSocket and broadcast there to all known WebSockets
  3. Try to keep WebSockets dictionary as clean as possible

Here is the WebSocket middleware class.


public class ChatWebSocketMiddleware
{
    private static ConcurrentDictionary<string, WebSocket> _sockets = new ConcurrentDictionary<string, WebSocket>();     private readonly RequestDelegate _next;     public ChatWebSocketMiddleware(RequestDelegate next)
    {
        _next = next;
    }     public async Task Invoke(HttpContext context)
    {
        if (!context.WebSockets.IsWebSocketRequest)
        {
            await _next.Invoke(context);
            return;
        }         CancellationToken ct = context.RequestAborted;
        WebSocket currentSocket = await context.WebSockets.AcceptWebSocketAsync();
        var socketId = Guid.NewGuid().ToString();         _sockets.TryAdd(socketId, currentSocket);         while (true)
        {
            if (ct.IsCancellationRequested)
            {
                break;
            }             var response = await ReceiveStringAsync(currentSocket, ct);
            if(string.IsNullOrEmpty(response))
            {
                if(currentSocket.State != WebSocketState.Open)
                {
                    break;
                }                 continue;
            }             foreach (var socket in _sockets)
            {
                if(socket.Value.State != WebSocketState.Open)
                {
                    continue;
                }                 await SendStringAsync(socket.Value, response, ct);
            }
        }         WebSocket dummy;
        _sockets.TryRemove(socketId, out dummy);         await currentSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", ct);
        currentSocket.Dispose();
    }     private static Task SendStringAsync(WebSocket socket, string data, CancellationToken ct = default(CancellationToken))
    {
        var buffer = Encoding.UTF8.GetBytes(data);
        var segment = new ArraySegment<byte>(buffer);
        return socket.SendAsync(segment, WebSocketMessageType.Text, true, ct);
    }     private static async Task<string> ReceiveStringAsync(WebSocket socket, CancellationToken ct = default(CancellationToken))
    {
        var buffer = new ArraySegment<byte>(new byte[8192]);
        using (var ms = new MemoryStream())
        {
            WebSocketReceiveResult result;
            do
            {
                ct.ThrowIfCancellationRequested();                 result = await socket.ReceiveAsync(buffer, ct);
                ms.Write(buffer.Array, buffer.Offset, result.Count);
            }
            while (!result.EndOfMessage);             ms.Seek(0, SeekOrigin.Begin);
            if (result.MessageType != WebSocketMessageType.Text)
            {
                return null;
            }             // Encoding UTF8: https://tools.ietf.org/html/rfc6455#section-5.6
            using (var reader = new StreamReader(ms, Encoding.UTF8))
            {
                return await reader.ReadToEndAsync();
            }
        }
    }
}

Before using middleware we have to introduce it to request pipeline in Startup class of web application. We do it in configure method before initializing MVC.


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

NB! It’s very important to add WebSockets before MVC. WebSockets middleware checks in the beginning if request is for WebSocket. If it is WebSocket request then middleware starts handling it. If MVC is added to pipeline before then MVC will handle all the requests and our WebSocket middleware is never used.

Chat room view

Now let’s add chat room view. We will use Index view of home controller for chat room. Chat room view initializes WebSocket connection and starts listening to it. If user writes something to chat box and presses Enter then message is sent to server over WebSocket and WebSocket middleware will broadcast it to all WebSocket clients it knows.


@model string
@{
    ViewData["Title"] = "Home Page";
}
<style>
    body {margin:0px; padding:0px;}
    .msg {
        position: absolute;
        top: ;
        bottom: 30px;
        border: 1px solid green;
        margin-bottom: auto;
        display:block;
        overflow: scroll;
        width:100%;
        white-space:nowrap;
    }
</style>
<div class="msg">
    <div style="position:absolute; bottom:;" id="msgs"></div>
</div> <div style="position:absolute;height:20px;bottom:10px;left:; display:block;width:100%">
    <input type="text" style="max-width:unset;width:100%;max-width:100%" id="MessageField" placeholder="type message and press enter" />
</div> @section Scripts {
    <script>
    $(function () {
        var userName = '@Model';         var protocol = location.protocol === "https:" ? "wss:" : "ws:";
        var wsUri = protocol + "//" + window.location.host;
        var socket = new WebSocket(wsUri);
        socket.onopen = e => {
            console.log("socket opened", e);
        };         socket.onclose = function (e) {
            console.log("socket closed", e);
        };         socket.onmessage = function (e) {
            console.log(e);
            $('#msgs').append(e.data + '<br />');
        };         socket.onerror = function (e) {
            console.error(e.data);
        };         $('#MessageField').keypress(function (e) {
            if (e.which != 13) {
                return;
            }             e.preventDefault();             var message = userName + ": " + $('#MessageField').val();
            socket.send(message);
            $('#MessageField').val('');
        });
    });
    </script>
}

Now let’s build application and run it.

WebSocket chat room in action

The screenshot below shows how our chat room looks like. It’s extremely primitive and simple but it works. There’s room enough for improvements but this is the fun I leave to all my dear readers.

Wrapping up

Using WebSockets in ASP.NET Core is simple. Without any additional libraries we can use the one by Microsoft. We had to write custom middleware class for WebSocket communication and in our case the class came pretty small. We used concurrent dictionary as a WebSockets cache and this enabled us to broadcast messages over sockets. Our solution is very primitive and simple, there is a lot of room for improvements but as a proof of concept it works well.

ASP.NET Core Building chat room using WebSocket的更多相关文章

  1. Real-time chart using ASP.NET Core and WebSocket

    Solution in glance The following diagram illustrates our solution where IoT device reports readings ...

  2. WebSocket In ASP.NET Core(一)

    .NET-Core Series Server in ASP.NET-Core DI in ASP.NET-Core Routing in ASP.NET-Core Error Handling in ...

  3. 关于ASP.NET Core WebSocket实现集群的思考

    前言 提到WebSocket相信大家都听说过,它的初衷是为了解决客户端浏览器与服务端进行双向通信,是在单个TCP连接上进行全双工通讯的协议.在没有WebSocket之前只能通过浏览器到服务端的请求应答 ...

  4. WebSocket in ASP.NET Core

    一.WebSocket WebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算) 首先HTTP有1.1和1.0 ...

  5. 网络游戏开发-服务器(01)Asp.Net Core中的websocket,并封装一个简单的中间件

    先拉开MSDN的文档,大致读一遍 (https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/websockets) WebSocket 是一 ...

  6. 快速搭建CentOS+ASP.NET Core环境支持WebSocket

    环境:CentOS 7.x,.net core 2 以下.net core 2安装操作为官方方法.如果你使用Docker,那么更简单了,只需要docker pull microsoft/dotnet就 ...

  7. 在Asp.net Core中使用中间件来管理websocket

    介绍 ASP.NET Core SignalR是一个有用的库,可以简化Web应用程序中实时通信的管理.但是,我宁愿使用WebSockets,因为我想要更灵活,并且与任何WebSocket客户端兼容. ...

  8. ASP.NET Core 集成 WebSocket

    1. 环境 AspNetCore Web 2.0 (MVC) Windows 10 IIS 10 Express/IIS VS 2017 2.如何配置 在已有的或者新创建的 AspNet Core M ...

  9. ASP.NET Core 中的 WebSocket 支持(转自MSDN)

    本文介绍 ASP.NET Core 中 WebSocket 的入门方法. WebSocket (RFC 6455) 是一个协议,支持通过 TCP 连接建立持久的双向信道. 它用于从快速实时通信中获益的 ...

随机推荐

  1. centos 7 安装 MySQL 5.6

    由于Centos7 默认数据库是mariabd,所以通过rpm安装MySQL需要卸载原有的Mariabd,再下载所有的依赖包比较麻烦且容易出错.通过yum的方式安装.yum其实是rpm的一个升级将所有 ...

  2. IDE安装Lombok插件提高开发效率

    Lombok官方api:https://projectlombok.org/features/index.html 使用lombok之后,省去了许多没必要的get,set,toString,equal ...

  3. python程序爬虫总是崩溃

    写的一个爬虫程序,主要用到以下库.但是伴随着代码增多,功能增多.经常性的程序崩溃现象,逐渐显现. pyqt5_5.8.2,requests.get,selenium+chorme,threading. ...

  4. UIWindow 官方文档解析

    UIWindow定义了一个window对象,其用于管理和协调一个app在设备屏幕上的显示.除非一个app能在外部设备上显示内容,一般就只有一个window. window的主要功能:1)提供一个区域来 ...

  5. AT1219 歴史の研究

    附带权值的类区间众数问题?不是很好策啊 发现题目没有强制在线,而且也只有询问操作,那么可以考虑莫队 但是这里的莫队有一个很显著的特征,插入的时候很好维护答案,但是删除的时候不好回退 那么有没有什么办法 ...

  6. 生产者消费者 ProducerConsumer

    生产者消费者是常见的同步问题.一个队列,头部生产数据,尾部消费数据,队列的长度为固定值.当生产的速度大于消费的速度时,队列逐渐会填满,这时就会阻塞住.当尾部消费了数据之后,生产者就可以继续生产了. 生 ...

  7. 记录一次.Net框架Bug发现和提交过程:.Net Framework和.Net Core均受影响

    SmtpClient一处代码编写错误导致异步发送邮件时DeliveryFormat配置项无法正确工作,异步操作已经完全不受我们设置属性控制了,UTF-8内容(如中文)转不转码完全看对方邮件服务器心情! ...

  8. Windows Community Toolkit 3.0 - Gaze Interaction

    概述 Gaze Input & Tracking - 也就是视觉输入和跟踪,是一种和鼠标/触摸屏输入非常不一样的交互方式,利用人类眼球的识别和眼球方向角度的跟踪,来判断人眼的目标和意图,从而非 ...

  9. python2.x版本与python3.x版本的区别以及运算符

    python2.x中: 重复代码,语言不统一,不支持中文 py2中除法获取的都是整形 py2中有long(长整形) print 可以加括号也可以不加括号 range 在py2中打印的结果是列表 py2 ...

  10. docker安装并修改Nginx镜像

    1.安装nginx镜像,命令:docker pull nginx 2.创建nginx容器,并启动,命令:docker run --name webserver -d -p 192.168.51.227 ...