即时通讯系统已成为是我们生活中、工作中不可缺少的工具了,各行各业的软件中也都会集成即通讯模块与用户之间快速沟通,传递信息,从而提高工作效率,用户可以私密地传递消息,它可以在互联网上或网络内的用户之间进行消息传递,而无需等待邮件或其他传统通讯形式的延迟。无论是通过文字、图片、语音,还是视频,即时通讯系统可以提供各种各样的通讯方式,让用户可以更加轻松地与其他人交流。
当我们的电商系统、MES 系统、ERP 系统、OA 系统需要用到通信模块时,前期为了项目快速上线会就会接入第三方的即时通讯服务,但是使用第三方即时通讯服务是要收费的,会按照消息量、用户量来收取相关费用的,当你系统的用户量上升了,那岂不是一笔很大的开销。使用第三方通讯服务还会担心数据泄密的风险,也没法在自己的系统中去做深度定制功能。所有我们还不如自己开发一个即时通讯模块,便于集成到其他系统中,数据完全自己掌控,保护用户隐私,让用户可以在安全的环境下与其他人交流。接下来我会通过 .net + websocket + h5 来开发一个简单的即时通讯模块。
软件定制开发请联系 QQ:993014309或869119955
///
/// 聊天 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);
}
}
///
/// 表示一个会话管理对象。
///
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);
}
}
// 连接。
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();
}
}
/*
* 重新连接。
*/
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/