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

前言

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

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

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

技术栈介绍

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

效果图

 .NET 聊天软件,使用 WebSocket 和 H5 开发即时通讯软件,Web 网页聊天系统_第1张图片.NET 聊天软件,使用 WebSocket 和 H5 开发即时通讯软件,Web 网页聊天系统_第2张图片

 .NET 聊天软件,使用 WebSocket 和 H5 开发即时通讯软件,Web 网页聊天系统_第3张图片.NET 聊天软件,使用 WebSocket 和 H5 开发即时通讯软件,Web 网页聊天系统_第4张图片

 .NET 聊天软件,使用 WebSocket 和 H5 开发即时通讯软件,Web 网页聊天系统_第5张图片.NET 聊天软件,使用 WebSocket 和 H5 开发即时通讯软件,Web 网页聊天系统_第6张图片

功能表

.NET 聊天软件,使用 WebSocket 和 H5 开发即时通讯软件,Web 网页聊天系统_第7张图片

 服务端 Webscokt 通信通道

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

     /// 
     /// 建立聊天连接。
     /// 
     ///  http 响应消息。
     [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 类型,用来管理客户端的会话,做身份验证、心跳检测、消息接收、消息分发、消息存储、消息校验等操作。
/// 
/// 表示一个会话管理对象。
/// 
public class ConversationManage
{

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

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

    /// 
    /// 帐号 Id。
    /// 
    private long _accountId;

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

    private Conversation _conversation;

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


    /// 
    /// 初始化会话管理对象。
    /// 
    ///  帐号 Id。
    ///  获取帐号信息失败异常。
    public ConversationManage(long accountId)
    {
        _accountId = accountId;

        _conversation = new Conversation();

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

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

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

    /// 
    /// 启动会话。
    /// 
    ///  WebSocket 上下文。
    ///  会话任务。
    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);
    }

    /// 
    /// 接收消息。
    /// 
    ///  消息主体。
    private async Task ReceiveMessageAsync()
    {
        var buffer = new ArraySegment(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;
    }

    /// 
    /// 关闭会话。
    /// 
    ///  异步任务。
    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/

你可能感兴趣的:(websocket,网络协议,网络,.net,html5)