.NET 聊天软件,使用 WebSocket 和 H5 开发即时通讯软件,Web 网页聊天系统

前言

        即时通讯系统已成为是我们生活中、工作中不可缺少的工具了,各行各业的软件中也都会集成即通讯模块与用户之间快速沟通,传递信息,从而提高工作效率,用户可以私密地传递消息,它可以在互联网上或网络内的用户之间进行消息传递,而无需等待邮件或其他传统通讯形式的延迟。无论是通过文字、图片、语音,还是视频,即时通讯系统可以提供各种各样的通讯方式,让用户可以更加轻松地与其他人交流。

        当我们的电商系统、MES 系统、ERP 系统、OA 系统需要用到通信模块时,前期为了项目快速上线会就会接入第三方的即时通讯服务,但是使用第三方即时通讯服务是要收费的,会按照消息量、用户量来收取相关费用的,当你系统的用户量上升了,那岂不是一笔很大的开销。使用第三方通讯服务还会担心数据泄密的风险,也没法在自己的系统中去做深度定制功能。所有我们还不如自己开发一个即时通讯模块,便于集成到其他系统中,数据完全自己掌控,保护用户隐私,让用户可以在安全的环境下与其他人交流。接下来我会通过 .net + websocket + h5 来开发一个简单的即时通讯模块。

软件定制开发请联系 QQ:993014309或869119955

技术栈介绍

  • 后端:.NET WebApi、C#、SQLServer2019
  • 前端:Javascript、JQuery、Mui、HTML5
  • 通讯协议:WebSocket

效果图

 

 

 

功能表

 服务端 Webscokt 通信通道

  • 创建一个 ChatApiController WebAPI 控制器,用来处理客户的长连接以及消息接收与分发。
 /// <summary>
 /// 聊天 Web Api 控制器。
 /// </summary>
 [Route("gateway/chat/{action}")]
 public class ChatApiController : BaseApiController
 {

     /// <summary>
     /// 建立聊天连接。
     /// </summary>
     /// <returns> http 响应消息。</returns>
     [HttpGet]
     public HttpResponseMessage Connect()
     {
         // 是一个 WebSocket 请求才去处理。 
         if (HttpContext.Current.IsWebSocketRequest)
         {
             ConversationManage conversation;

             try
             {
                 conversation = new ConversationManage(AccountContext.CurrentAccountId());
             }
             catch
             {
                 // 返回错误提示。
                 return GatewayHelp.RequesArgError();
             }

             // 接受 WebSocket 请求。
             HttpContext.Current.AcceptWebSocketRequest(conversation.Start);
         }

         return Request.CreateResponse(System.Net.HttpStatusCode.SwitchingProtocols);
     }
}
  • 新建一个 ConversationManage 类型,用来管理客户端的会话,做身份验证、心跳检测、消息接收、消息分发、消息存储、消息校验等操作。
/// <summary>
/// 表示一个会话管理对象。
/// </summary>
public class ConversationManage
{

    private static Keisoft.Logger.Log log = new Keisoft.Logger.Log("ChatCore");

    // 默认编码。
    private static System.Text.Encoding _encoding = System.Text.Encoding.UTF8;

    /// <summary>
    /// 帐号 Id。
    /// </summary>
    private long _accountId;

    // 当前会话的 WebSocket。
    private WebSocket _webSocket;

    private Conversation _conversation;

    private HandlerSystemMessage _hsm;
    private HandlerPrivateMessage _hpm;
    private HandlerGroupMessage _hgm;


    /// <summary>
    /// 初始化会话管理对象。
    /// </summary>
    /// <param name="accountId"> 帐号 Id。</param>
    /// <exception cref="ArgumentNullException"> 获取帐号信息失败异常。</exception>
    public ConversationManage(long accountId)
    {
        _accountId = accountId;

        _conversation = new Conversation();

        // 获取推送配置数据。
        var _accountLogic = LogicDependencyResolver.GetService<IAccountLogic>();
        // 获取帐号资料。
        var lram = _accountLogic.GetPushConfig(accountId);

        if (lram.IsSucceed && lram.Value != null)
            // 设置推送配置。
            _conversation.ClientId = lram.Value.ClientId;

        MessageHelp.WriteInfoLog(_accountId, "帐号请求连接");
    }

    /// <summary>
    /// 启动会话。
    /// </summary>
    /// <param name="context"> WebSocket 上下文。</param>
    /// <returns> 会话任务。</returns>
    public async Task Start(AspNetWebSocketContext context)
    {
        MessageHelp.WriteInfoLog(_accountId, "帐号连接成功");
        //var socket = context.WebSocket;

        _webSocket = context.WebSocket;
        _conversation.WebSocket = _webSocket;

        // 保存会话。
        CacheConversation.Save(_accountId, _conversation);

        try
        {
            _hsm = new HandlerSystemMessage(_accountId, _webSocket);
            _hpm = new HandlerPrivateMessage(_accountId, _webSocket);
            _hgm = new HandlerGroupMessage(_accountId, _webSocket);
        }
        catch (Exception ex)
        {
            MessageHelp.WriteInfoLog(_accountId, string.Concat("帐号初始化数据出现异常,异常内容:", ex.ToString()));

            // 返回错误。
            MessageHelp.SendMessageAsync
           (
              _webSocket,
              MessageHelp.ReturnSendError(null, MessageReportState.ArgErrorCode, "禁止连接")

           ).Wait();

            return;
        }

        // 上线发“离线私聊、群聊消息”。
        await _hpm.Online();
        // 上线发送“新朋友申请”。
        await _hsm.OnlinePushNewFriendApply();

        // 持续监听。
        while (true)
        {
            if (_webSocket.State != WebSocketState.Open)
            {
                MessageHelp.WriteInfoLog(_accountId, "会话状态未打开", _webSocket.State);

                // 关闭会话。
                CacheConversation.Close(_accountId);
                // 处理离线操作。
                _hpm.OffLine();

                break;
            }

            // 获取客户端请求的消息。
            var messageBody = await ReceiveMessageAsync();

            if (messageBody == null)
            {

                // 接收请求失败,告知客户端“发送消息失败”。
                var noticeBuffer = MessageHelp.ReturnSendError(null);

                await MessageHelp.SendMessageAsync(_webSocket, noticeBuffer);

                // 继续监听。
                continue;
            }

            if (messageBody.IsClose)
            {
                // 关闭会话。
                await CloseAsync();

                break;
            }

            switch (messageBody.BodyType)
            {
                // 心跳包。
                case MessageBodyType.Heartbeat:
                    // 回复心跳包。
                    await _hsm.ReplyHeartbeat(messageBody.Id, _conversation, _hpm);
                    break;

                // 私聊天。
                case MessageBodyType.PrivateChat:
                    // 服务器接收消息,然后转发给客户端。
                    await _hpm.Process(messageBody);
                    break;

                // 群聊。
                case MessageBodyType.GroupChat:
                    await _hgm.Process(messageBody);
                    break;

                // 消息应答。
                case MessageBodyType.Answer:
                    await _hpm.ProcessAnswer(messageBody);
                    break;
            }

        }

        MessageHelp.WriteInfoLog(_accountId, "本次回会话已结束", _webSocket.State);
    }

    /// <summary>
    /// 接收消息。
    /// </summary>
    /// <returns> 消息主体。</returns>
    private async Task<MessageBody> ReceiveMessageAsync()
    {
        var buffer = new ArraySegment<byte>(new byte[2048]);

        WebSocketReceiveResult result = null;

        try
        {
            result = await _webSocket.ReceiveAsync(buffer, CancellationToken.None);
        }
        catch (Exception ex)
        {
            //MessageHelp.WriteInfoLog(_accountId, string.Concat("接收发送异常,异常内容:", ex.ToString()));

            // 关闭。
            return new MessageBody { IsClose = true };
        }

        if (result.MessageType == WebSocketMessageType.Close)
        {
            // 关闭。
            return new MessageBody { IsClose = true };
        }

        // 获取消息内容。
        string message = _encoding.GetString(buffer.Array, 0, result.Count);

        MessageBody body;
        // 尝试将 JSON 字符串转换成对象。
        if (MessageHelp.TryToMessageBody(message, out body) && body != null)
        {
            body.Content = System.Net.WebUtility.HtmlEncode(body.Content);

            return body;
        }

        MessageHelp.WriteInfoLog(_accountId, string.Concat("发送 MessageBody 异常,MessageBody 内容:", message));

        return null;
    }

    /// <summary>
    /// 关闭会话。
    /// </summary>
    /// <returns> 异步任务。</returns>
    private async Task CloseAsync()
    {
        MessageHelp.WriteInfoLog(_accountId, "请求关闭连接", _webSocket.State);

        // 关闭会话。
        CacheConversation.Close(_accountId);
        // 处理离线操作。
        _hpm.OffLine();

        try
        {

            if (_webSocket.State == WebSocketState.CloseReceived)
                // 关闭 WebSocket。
                await _webSocket.CloseAsync(WebSocketCloseStatus.Empty, string.Empty, CancellationToken.None);
        }
        catch (Exception ex)
        {
            MessageHelp.WriteInfoLog(_accountId, string.Concat("关闭连接出现异常,异常内容:", ex.ToString()));
        }

        MessageHelp.WriteInfoLog(_accountId, "关闭连接完毕", _webSocket.State);
    }
}

客户端 Webscokt 通信通道

  • 初始化 WebSocket 连接。
 // 连接。
 function connect() {

     try {

         if ("WebSocket" in window)
             // 初始化一个 WebSocket 对象。
             webSocket = new WebSocket(_host);

         else if ("MozWebSocket" in window)
             // 初始化一个 WebSocket 对象。
             webSocket = new MozWebSocket(_host);

         // 绑定事件。
         event();

     } catch (e) {

         console.log(e);
         // 重连。
         reconnect();
     }

 }
  • 由于各种网络原因,在创建 WebSocket 连接之后,我们还有处理 WebSocket 重连的问题。
 /*
  * 重新连接。
 */
 function reconnect() {

     if (lockReconnect) {
         return;
     }

     showConnecting(true);

     lockReconnect = true;

     // intervalMillisecond 毫秒后重新连接。
     setTimeout(function () {

         connect();
         // 允许继续重连。
         lockReconnect = false;

     }, intervalMillisecond);

 }
  • 为了能够之后与服务器保持长时的间通信,所以还需要做心跳检测。
/*
 * 心跳检测。
*/
var heartbeatCheck = {

    // 3分钟发一次心跳。
    //interval: 180000,
    interval: 3000,
    // 本地定时对象。
    localTimeOut: null,
    // 服务器定时对象。
    serverTimeOut: null,

    // 重置。
    reset: function () {

        clearTimeout(this.localTimeOut);
        clearTimeout(this.serverTimeOut);

        return this;
    },

    // 开启心跳。        
    start: function () {

        var self = this;

        this.localTimeOut = setTimeout(function () {

            console.log("ping");

            var data = { id: system.guid().newGuid(), bodyType: messageBodyType.heartbeat, content: "..." };

            // 发送一个心跳,服务器收到后,返回一个心跳消息,在 onmessage 中能收到服务器的心跳就说明连接正常。
            webSocket.send(JSON.stringify(data));

            // 如果超过一定时间还未调用 heartbeatCheck.reset().start() 说明服务器主动断开。
            self.serverTimeOut = setTimeout(function () {

                console.log("服务器断开");
                //webSocket.close();
                // 重新连接。
                reconnect();

            }, self.interval);

        }, this.interval);

    }

};
  • 创建一个本地消息队列,实现消息断网后重发。
// 提供消息队列,实现消息断网后重发。
var messagesQueue = {

    _key: "_",

    // 最大发送次数 3 次。
    _maxRetryFrequency: 3,

    // 存储消息。
    depository: {},

    /*
     * 入队。
     * @id {String} 消息 Id。
     * @data {Object} 发送的消息数据。
    */
    enqueue: function (id, data) {

        var record = {

            id: id,
            // 发送的数据。
            data: data,
            // 防止重发。
            lock: false,
            // 重复次数。
            retryFrequency: 0
        };

        this.depository[this._key + id] = record;
    },

    /*
     * 出队。
     * id {String} 消息 Id。
    */
    dequeue: function (id) {

        delete this.depository[this._key + id];
    },

    reset: function () {

        for (var key in this.depository) {

            this.depository[key].lock = false;
        }

        return this;
    },

    trySend: function () {

        for (var key in this.depository) {

            var record = this.depository[key];

            if (record == null || record.lock)
                continue;

            if (record.retryFrequency >= this._maxRetryFrequency) {

                // 超过最大次数,直接移除。在 getUnsentMessage 去删除。
                //delete this.depository[key];

                // 通知该消息发送失败。
                if (_onEvent.onMessageSendFail)
                    _onEvent.onMessageSendFail(record.id);

                continue;
            }

            // 检查是否已经关闭。
            if (webSocket == null || webSocket.readyState == webSocket.CLOSED)
                break;


            // 再次检测。
            if (record.lock)
                continue;

            // 标记已发送。
            record.lock = true;
            record.retryFrequency++;

            // 向服务器发送。
            webSocket.send(record.data);
        }
    },

    /*
     * 消息 Id。
     * @return {Object} 未发出的消息。
    */
    getUnsentMessage: function (id) {

        var record = this.depository[this._key + id];

        if (record == null)
            return null;

        // 获取消息数据。
        var data = record.data;

        // 检查是否需要删除。
        if (record.retryFrequency >= this._maxRetryFrequency)
            delete this.depository[key];

        // 
        console.log("getUnsentMessage");
        console.log(data);

        return data;
    }

};

总结

        以上就是 .net + websocket + h5 来开发一个简单的即时通讯系统的简单介绍了,整个项目代码太多了无法全部写到文章中。需要 .net 聊天系统项目代码可以到我的 GitHub 去看看或者通过微信公众号私信我。

微信公众号:KeisoftCN

macroecho · GitHubhttps://github.com/macroecho/

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,下面是一个简单的 .NET Core 6 WebSocket 聊天应用程序示例,包含了群聊和私聊功能: 1. 创建 ASP.NET Core 6 Web 应用程序 首先,我们需要创建一个 ASP.NET Core 6 Web 应用程序。可以使用 Visual Studio 或者 Visual Studio Code 来创建。 2. 安装 Microsoft.AspNetCore.WebSockets 包 在项目中安装 Microsoft.AspNetCore.WebSockets 包,使用以下命令: ``` dotnet add package Microsoft.AspNetCore.WebSockets ``` 3. 创建 WebSocket 控制器 创建一个名为 ChatController 的控制器,并添加以下代码: ```csharp using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.WebSockets; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; namespace WebSocketChat.Controllers { [Route("api/[controller]")] [ApiController] public class ChatController : ControllerBase { private readonly WebSocketManager _webSocketManager; public ChatController(WebSocketManager webSocketManager) { _webSocketManager = webSocketManager; } [HttpGet("{username}")] public async Task Get(string username, CancellationToken cancellationToken) { if (HttpContext.WebSockets.IsWebSocketRequest) { WebSocket webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); var socketConnection = new SocketConnection(username, webSocket); _webSocketManager.AddSocket(socketConnection); await socketConnection.ReceiveAsync(_webSocketManager, cancellationToken); } else { HttpContext.Response.StatusCode = 400; } } } } ``` 这个控制器包含了 WebSocket 连接的处理逻辑。它从 HTTP 上下文中获取 WebSocket 对象,然后创建一个 SocketConnection 对象来处理连接。接下来,将 SocketConnection 对象添加到 WebSocketManager 中,以便以后可以使用。 4. 创建 WebSocketManager 和 SocketConnection 类 创建一个名为 WebSocketManager 的类,表示所有 WebSocket 连接的集合。还需要创建名为 SocketConnection 的类,表示单个 WebSocket 连接。 ```csharp using System.Collections.Concurrent; using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace WebSocketChat { public class WebSocketManager { private ConcurrentDictionary<string, SocketConnection> _sockets = new ConcurrentDictionary<string, SocketConnection>(); public void AddSocket(SocketConnection socket) { _sockets.TryAdd(socket.Username, socket); } public void RemoveSocket(string username) { _sockets.TryRemove(username, out _); } public async Task SendMessageAsync(string username, string message) { if (_sockets.TryGetValue(username, out var socket)) { await socket.SendAsync(message); } } public async Task BroadcastAsync(string message) { foreach (var socket in _sockets.Values) { await socket.SendAsync(message); } } } public class SocketConnection { public string Username { get; } public WebSocket WebSocket { get; } public SocketConnection(string username, WebSocket webSocket) { Username = username; WebSocket = webSocket; } public async Task ReceiveAsync(WebSocketManager webSocketManager, CancellationToken cancellationToken) { var buffer = new byte[1024 * 4]; while (WebSocket.State == WebSocketState.Open) { var result = await WebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken); if (result.MessageType == WebSocketMessageType.Text) { var message = Encoding.UTF8.GetString(buffer, 0, result.Count); var index = message.IndexOf(':'); if (index != -1) { var toUser = message.Substring(0, index); var content = message.Substring(index + 1); await webSocketManager.SendMessageAsync(toUser, $"{Username}: {content}"); } else { await webSocketManager.BroadcastAsync($"{Username}: {message}"); } } else if (result.MessageType == WebSocketMessageType.Close) { await WebSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, cancellationToken); webSocketManager.RemoveSocket(Username); } } } public async Task SendAsync(string message) { var bytes = Encoding.UTF8.GetBytes(message); var buffer = new ArraySegment<byte>(bytes); await WebSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None); } } } ``` WebSocketManager 类维护了所有 WebSocket 连接的集合,并提供了一些方法来发送消息。SocketConnection 类表示单个 WebSocket 连接,它维护了连接的用户名和 WebSocket 对象。 5. 创建聊天页面 最后,我们需要创建一个聊天页面,让用户可以在其中输入聊天消息。可以使用 HTML 和 JavaScript 来创建这个页面。 ```html <!DOCTYPE html> <html> <head> <title>WebSocket Chat</title> </head> <body> <h1>WebSocket Chat</h1> <div> <input type="text" id="username" placeholder="Enter your username"> <button onclick="connect()">Connect</button> </div> <div> <input type="text" id="toUser" placeholder="Enter recipient (optional)"> <input type="text" id="message" placeholder="Enter message"> <button onclick="send()">Send</button> </div> <div> <ul id="messages"></ul> </div> <script> var socket; function connect() { var username = document.getElementById("username").value; var url = "ws://" + window.location.host + "/api/chat/" + username; socket = new WebSocket(url); socket.onmessage = function (event) { var messages = document.getElementById("messages"); var li = document.createElement("li"); li.textContent = event.data; messages.appendChild(li); }; } function send() { var toUser = document.getElementById("toUser").value; var message = document.getElementById("message").value; if (toUser) { message = toUser + ":" + message; } socket.send(message); } </script> </body> </html> ``` 这个页面包含了一个表单,让用户输入他们的用户名和聊天消息。当用户单击 Connect 按钮时,将会创建一个 WebSocket 连接。当用户单击 Send 按钮时,将会向服务器发送聊天消息。 以上就是一个简单的 .NET Core 6 WebSocket 聊天应用程序示例,包含了群聊和私聊功能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值