* LCIMClient.cs:
* LCRTMRouter.cs: * LCIMConversation.cs: * LCHeartBeat.cs: * LCConnection.cs: * LCWebSocketClient.cs: * LCIMMessageController.cs: * LCIMSessionController.cs: * LCIMMessage.cs: chore: 完善连接模块
parent
8f81bf245a
commit
95acf35e65
|
@ -4,22 +4,36 @@ using System.Threading.Tasks;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using LeanCloud.Storage;
|
using LeanCloud.Storage;
|
||||||
using LeanCloud.Storage.Internal.Codec;
|
|
||||||
|
|
||||||
namespace LeanCloud.Realtime {
|
namespace LeanCloud.Realtime {
|
||||||
|
/// <summary>
|
||||||
|
/// 普通对话
|
||||||
|
/// </summary>
|
||||||
public class LCIMConversation {
|
public class LCIMConversation {
|
||||||
|
/// <summary>
|
||||||
|
/// 对话 Id
|
||||||
|
/// </summary>
|
||||||
public string Id {
|
public string Id {
|
||||||
get; internal set;
|
get; internal set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否唯一
|
||||||
|
/// </summary>
|
||||||
public bool Unique {
|
public bool Unique {
|
||||||
get; internal set;
|
get; internal set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 唯一 Id
|
||||||
|
/// </summary>
|
||||||
public string UniqueId {
|
public string UniqueId {
|
||||||
get; internal set;
|
get; internal set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 对话名称
|
||||||
|
/// </summary>
|
||||||
public string Name {
|
public string Name {
|
||||||
get {
|
get {
|
||||||
return this["name"] as string;
|
return this["name"] as string;
|
||||||
|
@ -29,42 +43,69 @@ namespace LeanCloud.Realtime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建者 Id
|
||||||
|
/// </summary>
|
||||||
public string CreatorId {
|
public string CreatorId {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 成员 Id
|
||||||
|
/// </summary>
|
||||||
public ReadOnlyCollection<string> MemberIds {
|
public ReadOnlyCollection<string> MemberIds {
|
||||||
get {
|
get {
|
||||||
return new ReadOnlyCollection<string>(ids.ToList());
|
return new ReadOnlyCollection<string>(ids.ToList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 静音成员 Id
|
||||||
|
/// </summary>
|
||||||
public ReadOnlyCollection<string> MutedMemberIds {
|
public ReadOnlyCollection<string> MutedMemberIds {
|
||||||
get {
|
get {
|
||||||
return new ReadOnlyCollection<string>(mutedIds.ToList());
|
return new ReadOnlyCollection<string>(mutedIds.ToList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 未读消息数量
|
||||||
|
/// </summary>
|
||||||
public int Unread {
|
public int Unread {
|
||||||
get; internal set;
|
get; internal set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最新的一条消息
|
||||||
|
/// </summary>
|
||||||
public LCIMMessage LastMessage {
|
public LCIMMessage LastMessage {
|
||||||
get; internal set;
|
get; internal set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建时间
|
||||||
|
/// </summary>
|
||||||
public DateTime CreatedAt {
|
public DateTime CreatedAt {
|
||||||
get; internal set;
|
get; internal set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新时间
|
||||||
|
/// </summary>
|
||||||
public DateTime UpdatedAt {
|
public DateTime UpdatedAt {
|
||||||
get; internal set;
|
get; internal set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最新送达消息时间戳
|
||||||
|
/// </summary>
|
||||||
public long LastDeliveredTimestamp {
|
public long LastDeliveredTimestamp {
|
||||||
get; internal set;
|
get; internal set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最新送达消息时间
|
||||||
|
/// </summary>
|
||||||
public DateTime LastDeliveredAt {
|
public DateTime LastDeliveredAt {
|
||||||
get {
|
get {
|
||||||
DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeMilliseconds(LastDeliveredTimestamp);
|
DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeMilliseconds(LastDeliveredTimestamp);
|
||||||
|
@ -72,10 +113,16 @@ namespace LeanCloud.Realtime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最新已读消息时间戳
|
||||||
|
/// </summary>
|
||||||
public long LastReadTimestamp {
|
public long LastReadTimestamp {
|
||||||
get; internal set;
|
get; internal set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最新已读消息时间
|
||||||
|
/// </summary>
|
||||||
public DateTime LastReadAt {
|
public DateTime LastReadAt {
|
||||||
get {
|
get {
|
||||||
DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeMilliseconds(LastReadTimestamp);
|
DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeMilliseconds(LastReadTimestamp);
|
||||||
|
@ -83,6 +130,11 @@ namespace LeanCloud.Realtime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置/获取对话属性
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
public object this[string key] {
|
public object this[string key] {
|
||||||
get {
|
get {
|
||||||
return customProperties[key];
|
return customProperties[key];
|
||||||
|
@ -92,6 +144,9 @@ namespace LeanCloud.Realtime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否已静音
|
||||||
|
/// </summary>
|
||||||
public bool IsMute {
|
public bool IsMute {
|
||||||
get; private set;
|
get; private set;
|
||||||
}
|
}
|
||||||
|
@ -100,7 +155,7 @@ namespace LeanCloud.Realtime {
|
||||||
get; private set;
|
get; private set;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dictionary<string, object> customProperties;
|
private readonly Dictionary<string, object> customProperties;
|
||||||
|
|
||||||
internal HashSet<string> ids;
|
internal HashSet<string> ids;
|
||||||
|
|
||||||
|
@ -128,7 +183,7 @@ namespace LeanCloud.Realtime {
|
||||||
if (LastMessage == null) {
|
if (LastMessage == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await Client.ConversationController.Read(Id, LastMessage);
|
await Client.MessageController.Read(Id, LastMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -374,7 +429,7 @@ namespace LeanCloud.Realtime {
|
||||||
/// <param name="limit">限制</param>
|
/// <param name="limit">限制</param>
|
||||||
/// <param name="messageType">消息类型</param>
|
/// <param name="messageType">消息类型</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<List<LCIMMessage>> QueryMessages(LCIMMessageQueryEndpoint start = null,
|
public async Task<ReadOnlyCollection<LCIMMessage>> QueryMessages(LCIMMessageQueryEndpoint start = null,
|
||||||
LCIMMessageQueryEndpoint end = null,
|
LCIMMessageQueryEndpoint end = null,
|
||||||
LCIMMessageQueryDirection direction = LCIMMessageQueryDirection.NewToOld,
|
LCIMMessageQueryDirection direction = LCIMMessageQueryDirection.NewToOld,
|
||||||
int limit = 20,
|
int limit = 20,
|
||||||
|
|
|
@ -34,16 +34,31 @@ namespace LeanCloud.Realtime.Internal.Connection {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const int HEART_BEAT_INTERVAL = 5000;
|
private const int HEART_BEAT_INTERVAL = 5000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通知事件
|
||||||
|
/// </summary>
|
||||||
internal Action<GenericCommand> OnNotification;
|
internal Action<GenericCommand> OnNotification;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 断线事件
|
||||||
|
/// </summary>
|
||||||
internal Action OnDisconnect;
|
internal Action OnDisconnect;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开始重连事件
|
||||||
|
/// </summary>
|
||||||
internal Action OnReconnecting;
|
internal Action OnReconnecting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重连成功事件
|
||||||
|
/// </summary>
|
||||||
internal Action OnReconnected;
|
internal Action OnReconnected;
|
||||||
|
|
||||||
internal string id;
|
internal string id;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求回调缓存
|
||||||
|
/// </summary>
|
||||||
private readonly Dictionary<int, TaskCompletionSource<GenericCommand>> responses;
|
private readonly Dictionary<int, TaskCompletionSource<GenericCommand>> responses;
|
||||||
|
|
||||||
private int requestI = 1;
|
private int requestI = 1;
|
||||||
|
@ -61,7 +76,7 @@ namespace LeanCloud.Realtime.Internal.Connection {
|
||||||
router = new LCRTMRouter();
|
router = new LCRTMRouter();
|
||||||
client = new LCWebSocketClient(router, heartBeat) {
|
client = new LCWebSocketClient(router, heartBeat) {
|
||||||
OnMessage = OnClientMessage,
|
OnMessage = OnClientMessage,
|
||||||
OnDisconnect = OnClientDisconnect
|
OnClose = OnClientDisconnect
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,41 +84,63 @@ namespace LeanCloud.Realtime.Internal.Connection {
|
||||||
await client.Connect();
|
await client.Connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重置连接
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
internal async Task Reset() {
|
internal async Task Reset() {
|
||||||
router.Reset();
|
// 关闭就连接
|
||||||
await client.Close();
|
await client.Close();
|
||||||
|
// 重新创建连接组件
|
||||||
heartBeat = new LCHeartBeat(this, HEART_BEAT_INTERVAL, HEART_BEAT_INTERVAL);
|
heartBeat = new LCHeartBeat(this, HEART_BEAT_INTERVAL, HEART_BEAT_INTERVAL);
|
||||||
router = new LCRTMRouter();
|
router = new LCRTMRouter();
|
||||||
client = new LCWebSocketClient(router, heartBeat) {
|
client = new LCWebSocketClient(router, heartBeat) {
|
||||||
OnMessage = OnClientMessage,
|
OnMessage = OnClientMessage,
|
||||||
OnDisconnect = OnClientDisconnect
|
OnClose = OnClientDisconnect
|
||||||
};
|
};
|
||||||
await Reconnect();
|
await Reconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送请求,会在收到应答后返回
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <returns></returns>
|
||||||
internal async Task<GenericCommand> SendRequest(GenericCommand request) {
|
internal async Task<GenericCommand> SendRequest(GenericCommand request) {
|
||||||
TaskCompletionSource<GenericCommand> tcs = new TaskCompletionSource<GenericCommand>();
|
TaskCompletionSource<GenericCommand> tcs = new TaskCompletionSource<GenericCommand>();
|
||||||
request.I = requestI++;
|
request.I = requestI++;
|
||||||
responses.Add(request.I, tcs);
|
responses.Add(request.I, tcs);
|
||||||
LCLogger.Debug($"{id} => {FormatCommand(request)}");
|
try {
|
||||||
byte[] bytes = request.ToByteArray();
|
await SendCommand(request);
|
||||||
Task sendTask = client.Send(bytes);
|
} catch (Exception e) {
|
||||||
if (await Task.WhenAny(sendTask, Task.Delay(SEND_TIMEOUT)) == sendTask) {
|
tcs.TrySetException(e);
|
||||||
try {
|
|
||||||
await sendTask;
|
|
||||||
} catch (Exception e) {
|
|
||||||
tcs.TrySetException(e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tcs.TrySetException(new TimeoutException("Send request"));
|
|
||||||
}
|
}
|
||||||
return await tcs.Task;
|
return await tcs.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送命令
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="command"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task SendCommand(GenericCommand command) {
|
||||||
|
LCLogger.Debug($"{id} => {FormatCommand(command)}");
|
||||||
|
byte[] bytes = command.ToByteArray();
|
||||||
|
Task sendTask = client.Send(bytes);
|
||||||
|
if (await Task.WhenAny(sendTask, Task.Delay(SEND_TIMEOUT)) == sendTask) {
|
||||||
|
await sendTask;
|
||||||
|
} else {
|
||||||
|
throw new TimeoutException("Send request");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关闭连接
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
internal async Task Close() {
|
internal async Task Close() {
|
||||||
OnNotification = null;
|
OnNotification = null;
|
||||||
OnDisconnect = null;
|
OnDisconnect = null;
|
||||||
heartBeat.Stop();
|
|
||||||
await client.Close();
|
await client.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,8 +191,6 @@ namespace LeanCloud.Realtime.Internal.Connection {
|
||||||
try {
|
try {
|
||||||
LCLogger.Debug($"Reconnecting... {reconnectCount}");
|
LCLogger.Debug($"Reconnecting... {reconnectCount}");
|
||||||
await client.Connect();
|
await client.Connect();
|
||||||
client.OnMessage = OnClientMessage;
|
|
||||||
client.OnDisconnect = OnClientDisconnect;
|
|
||||||
break;
|
break;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
reconnectCount++;
|
reconnectCount++;
|
||||||
|
@ -167,11 +202,13 @@ namespace LeanCloud.Realtime.Internal.Connection {
|
||||||
if (reconnectCount < MAX_RECONNECT_TIMES) {
|
if (reconnectCount < MAX_RECONNECT_TIMES) {
|
||||||
// 重连成功
|
// 重连成功
|
||||||
LCLogger.Debug("Reconnected");
|
LCLogger.Debug("Reconnected");
|
||||||
|
client.OnMessage = OnClientMessage;
|
||||||
|
client.OnClose = OnClientDisconnect;
|
||||||
OnReconnected?.Invoke();
|
OnReconnected?.Invoke();
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
// 重置 Router,继续尝试重连
|
// 重置 Router,继续尝试重连
|
||||||
router.Reset();
|
router = new LCRTMRouter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ using LeanCloud.Realtime.Protocol;
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal.Connection {
|
namespace LeanCloud.Realtime.Internal.Connection {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 心跳控制器
|
/// 心跳控制器,由于 .Net Standard 2.0 不支持发送 ping frame,所以需要发送逻辑心跳
|
||||||
/// 1. 每次接收到消息后开始监听,如果在 pingInterval 时间内没有再次接收到消息,则发送 ping 请求;
|
/// 1. 每次接收到消息后开始监听,如果在 pingInterval 时间内没有再次接收到消息,则发送 ping 请求;
|
||||||
/// 2. 发送后等待 pongInterval 时间,如果在此时间内接收到了任何消息,则取消并重新开始监听 1;
|
/// 2. 发送后等待 pongInterval 时间,如果在此时间内接收到了任何消息,则取消并重新开始监听 1;
|
||||||
/// 3. 如果没收到消息,则认为超时并回调,连接层接收回调后放弃当前连接,以断线逻辑处理
|
/// 3. 如果没收到消息,则认为超时并回调,连接层接收回调后放弃当前连接,以断线逻辑处理
|
||||||
|
|
|
@ -223,6 +223,7 @@ namespace LeanCloud.Realtime.Internal.Controller {
|
||||||
message.IsTransient = direct.Transient;
|
message.IsTransient = direct.Transient;
|
||||||
// 通知服务端已接收
|
// 通知服务端已接收
|
||||||
if (!message.IsTransient) {
|
if (!message.IsTransient) {
|
||||||
|
// 只有非暂态消息才需要发送 ack
|
||||||
_ = Ack(message.ConversationId, message.Id);
|
_ = Ack(message.ConversationId, message.Id);
|
||||||
}
|
}
|
||||||
// 获取对话
|
// 获取对话
|
||||||
|
|
|
@ -18,9 +18,9 @@ namespace LeanCloud.Realtime.Internal.Controller {
|
||||||
/// 打开会话
|
/// 打开会话
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
internal async Task Open(bool reconnect) {
|
internal async Task Open(bool force) {
|
||||||
SessionCommand session = NewSessionCommand();
|
SessionCommand session = NewSessionCommand();
|
||||||
session.R = reconnect;
|
session.R = !force;
|
||||||
GenericCommand request = NewCommand(CommandType.Session, OpType.Open);
|
GenericCommand request = NewCommand(CommandType.Session, OpType.Open);
|
||||||
request.SessionMessage = session;
|
request.SessionMessage = session;
|
||||||
GenericCommand response = await Client.Connection.SendRequest(request);
|
GenericCommand response = await Client.Connection.SendRequest(request);
|
||||||
|
@ -78,7 +78,9 @@ namespace LeanCloud.Realtime.Internal.Controller {
|
||||||
SessionCommand session = new SessionCommand();
|
SessionCommand session = new SessionCommand();
|
||||||
if (Client.Tag != null) {
|
if (Client.Tag != null) {
|
||||||
session.Tag = Client.Tag;
|
session.Tag = Client.Tag;
|
||||||
session.DeviceId = Guid.NewGuid().ToString();
|
}
|
||||||
|
if (Client.DeviceId != null) {
|
||||||
|
session.DeviceId = Client.DeviceId;
|
||||||
}
|
}
|
||||||
if (Client.SignatureFactory != null) {
|
if (Client.SignatureFactory != null) {
|
||||||
LCIMSignature signature = Client.SignatureFactory.CreateConnectSignature(Client.Id);
|
LCIMSignature signature = Client.SignatureFactory.CreateConnectSignature(Client.Id);
|
||||||
|
|
|
@ -28,13 +28,6 @@ namespace LeanCloud.Realtime.Internal.Router {
|
||||||
return rtmServer;
|
return rtmServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重置服务器地址缓存
|
|
||||||
/// </summary>
|
|
||||||
internal void Reset() {
|
|
||||||
rtmServer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task<LCRTMServer> Fetch() {
|
async Task<LCRTMServer> Fetch() {
|
||||||
string server = await LCApplication.AppRouter.GetRealtimeServer();
|
string server = await LCApplication.AppRouter.GetRealtimeServer();
|
||||||
string url = $"{server}/v1/route?appId={LCApplication.AppId}&secure=1";
|
string url = $"{server}/v1/route?appId={LCApplication.AppId}&secure=1";
|
||||||
|
|
|
@ -7,21 +7,31 @@ using LeanCloud.Realtime.Internal.Connection;
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal.WebSocket {
|
namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// WebSocket 客户端,只与通信协议相关
|
/// WebSocket 客户端,负责底层连接和事件,只与通信协议相关
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class LCWebSocketClient {
|
internal class LCWebSocketClient {
|
||||||
// .net standard 2.0 好像在拼合 Frame 时有 bug,所以将这个值调整大一些
|
// .net standard 2.0 好像在拼合 Frame 时有 bug,所以将这个值调整大一些
|
||||||
private const int RECV_BUFFER_SIZE = 1024 * 5;
|
private const int RECV_BUFFER_SIZE = 1024 * 5;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关闭超时
|
||||||
|
/// </summary>
|
||||||
private const int CLOSE_TIMEOUT = 5000;
|
private const int CLOSE_TIMEOUT = 5000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 连接超时
|
||||||
|
/// </summary>
|
||||||
private const int CONNECT_TIMEOUT = 10000;
|
private const int CONNECT_TIMEOUT = 10000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息事件
|
||||||
|
/// </summary>
|
||||||
internal Action<byte[]> OnMessage;
|
internal Action<byte[]> OnMessage;
|
||||||
|
|
||||||
internal Action OnDisconnect;
|
/// <summary>
|
||||||
|
/// 连接关闭
|
||||||
internal Action OnReconnect;
|
/// </summary>
|
||||||
|
internal Action OnClose;
|
||||||
|
|
||||||
private ClientWebSocket ws;
|
private ClientWebSocket ws;
|
||||||
|
|
||||||
|
@ -34,6 +44,10 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
this.heartBeat = heartBeat;
|
this.heartBeat = heartBeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 连接
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
internal async Task Connect() {
|
internal async Task Connect() {
|
||||||
try {
|
try {
|
||||||
LCRTMServer rtmServer = await router.GetServer();
|
LCRTMServer rtmServer = await router.GetServer();
|
||||||
|
@ -53,6 +67,11 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
_ = StartReceive();
|
_ = StartReceive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 连接指定 ws 服务器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server"></param>
|
||||||
|
/// <returns></returns>
|
||||||
private async Task Connect(string server) {
|
private async Task Connect(string server) {
|
||||||
LCLogger.Debug($"Connecting WebSocket: {server}");
|
LCLogger.Debug($"Connecting WebSocket: {server}");
|
||||||
Task timeoutTask = Task.Delay(CONNECT_TIMEOUT);
|
Task timeoutTask = Task.Delay(CONNECT_TIMEOUT);
|
||||||
|
@ -66,11 +85,14 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 主动关闭连接
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
internal async Task Close() {
|
internal async Task Close() {
|
||||||
LCLogger.Debug("Closing WebSocket");
|
LCLogger.Debug("Closing WebSocket");
|
||||||
OnMessage = null;
|
OnMessage = null;
|
||||||
OnDisconnect = null;
|
OnClose = null;
|
||||||
OnReconnect = null;
|
|
||||||
heartBeat.Stop();
|
heartBeat.Stop();
|
||||||
try {
|
try {
|
||||||
// 发送关闭帧可能会很久,所以增加超时
|
// 发送关闭帧可能会很久,所以增加超时
|
||||||
|
@ -87,6 +109,11 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送数据
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data"></param>
|
||||||
|
/// <returns></returns>
|
||||||
internal async Task Send(byte[] data) {
|
internal async Task Send(byte[] data) {
|
||||||
ArraySegment<byte> bytes = new ArraySegment<byte>(data);
|
ArraySegment<byte> bytes = new ArraySegment<byte>(data);
|
||||||
if (ws.State == WebSocketState.Open) {
|
if (ws.State == WebSocketState.Open) {
|
||||||
|
@ -103,28 +130,30 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 接收数据
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
private async Task StartReceive() {
|
private async Task StartReceive() {
|
||||||
byte[] buffer = new byte[RECV_BUFFER_SIZE];
|
byte[] buffer = new byte[RECV_BUFFER_SIZE];
|
||||||
try {
|
try {
|
||||||
while (ws.State == WebSocketState.Open) {
|
while (ws.State == WebSocketState.Open) {
|
||||||
WebSocketReceiveResult result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), default);
|
WebSocketReceiveResult result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), default);
|
||||||
if (result.MessageType == WebSocketMessageType.Close) {
|
if (result.MessageType == WebSocketMessageType.Close) {
|
||||||
// 由服务端发起关闭
|
|
||||||
LCLogger.Debug($"Receive Closed: {result.CloseStatus}");
|
LCLogger.Debug($"Receive Closed: {result.CloseStatus}");
|
||||||
LCLogger.Debug($"ws state: {ws.State}");
|
|
||||||
// 这里有可能是客户端主动关闭,也有可能是服务端主动关闭
|
|
||||||
if (ws.State == WebSocketState.CloseReceived) {
|
if (ws.State == WebSocketState.CloseReceived) {
|
||||||
// 如果是服务端主动关闭,则挥手关闭,并认为是断线
|
// 如果是服务端主动关闭,则挥手关闭,并认为是断线
|
||||||
try {
|
try {
|
||||||
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, default);
|
Task closeTask = ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, default);
|
||||||
|
await Task.WhenAny(closeTask, Task.Delay(CLOSE_TIMEOUT));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LCLogger.Error(e);
|
LCLogger.Error(e);
|
||||||
} finally {
|
} finally {
|
||||||
OnDisconnect?.Invoke();
|
HandleExceptionClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (result.MessageType == WebSocketMessageType.Binary) {
|
} else if (result.MessageType == WebSocketMessageType.Binary) {
|
||||||
_ = heartBeat.Update(HandleClose);
|
_ = heartBeat.Update(HandleExceptionClose);
|
||||||
// 拼合 WebSocket Message
|
// 拼合 WebSocket Message
|
||||||
int length = result.Count;
|
int length = result.Count;
|
||||||
byte[] data = new byte[length];
|
byte[] data = new byte[length];
|
||||||
|
@ -137,17 +166,19 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 客户端网络异常
|
// 客户端网络异常
|
||||||
LCLogger.Error(e);
|
LCLogger.Error(e);
|
||||||
OnDisconnect?.Invoke();
|
OnClose?.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleClose() {
|
private void HandleExceptionClose() {
|
||||||
try {
|
try {
|
||||||
heartBeat.Stop();
|
heartBeat.Stop();
|
||||||
ws.Abort();
|
ws.Abort();
|
||||||
ws.Dispose();
|
ws.Dispose();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LCLogger.Error(e);
|
LCLogger.Error(e);
|
||||||
|
} finally {
|
||||||
|
OnClose?.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,39 @@ namespace LeanCloud.Realtime {
|
||||||
get; private set;
|
get; private set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string DeviceId {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
#region 事件
|
#region 事件
|
||||||
|
|
||||||
|
#region 连接状态事件
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端连接断开
|
||||||
|
/// </summary>
|
||||||
|
public Action OnPaused {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端连接恢复正常
|
||||||
|
/// </summary>
|
||||||
|
public Action OnResume {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前客户端被服务端强行下线
|
||||||
|
/// </summary>
|
||||||
|
public Action<int, string> OnClose {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 对话事件
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当前用户被加入某个对话的黑名单
|
/// 当前用户被加入某个对话的黑名单
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -49,55 +80,6 @@ namespace LeanCloud.Realtime {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Action<LCIMConversation, string> OnUnmuted;
|
public Action<LCIMConversation, string> OnUnmuted;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 客户端连接断开
|
|
||||||
/// </summary>
|
|
||||||
public Action OnPaused {
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 客户端连接恢复正常
|
|
||||||
/// </summary>
|
|
||||||
public Action OnResume {
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 当前客户端被服务端强行下线
|
|
||||||
/// </summary>
|
|
||||||
public Action<int, string> OnClose {
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 客户端连接断开
|
|
||||||
/// </summary>
|
|
||||||
public Action OnDisconnect {
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 客户端正在重连
|
|
||||||
/// </summary>
|
|
||||||
public Action OnReconnecting {
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 客户端重连成功
|
|
||||||
/// </summary>
|
|
||||||
public Action OnReconnected {
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 用户在其他客户端登录,当前客户端被服务端强行下线
|
|
||||||
/// </summary>
|
|
||||||
public Action<string> OnConflict {
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 该对话信息被更新
|
/// 该对话信息被更新
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -164,6 +146,10 @@ namespace LeanCloud.Realtime {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Action<LCIMConversation, string, string, string> OnMemberInfoUpdated;
|
public Action<LCIMConversation, string, string, string> OnMemberInfoUpdated;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 消息事件
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当前用户收到消息
|
/// 当前用户收到消息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -207,18 +193,23 @@ namespace LeanCloud.Realtime {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// 最近分发消息更新
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Action OnLastDeliveredAtUpdated {
|
public Action OnLastDeliveredAtUpdated {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最近已读消息更新
|
||||||
|
/// </summary>
|
||||||
public Action OnLastReadAtUpdated {
|
public Action OnLastReadAtUpdated {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
internal ILCIMSignatureFactory SignatureFactory {
|
internal ILCIMSignatureFactory SignatureFactory {
|
||||||
get; private set;
|
get; private set;
|
||||||
}
|
}
|
||||||
|
@ -255,9 +246,11 @@ namespace LeanCloud.Realtime {
|
||||||
|
|
||||||
public LCIMClient(string clientId,
|
public LCIMClient(string clientId,
|
||||||
string tag = null,
|
string tag = null,
|
||||||
|
string deviceId = null,
|
||||||
ILCIMSignatureFactory signatureFactory = null) {
|
ILCIMSignatureFactory signatureFactory = null) {
|
||||||
Id = clientId;
|
Id = clientId;
|
||||||
Tag = tag;
|
Tag = tag;
|
||||||
|
DeviceId = deviceId;
|
||||||
SignatureFactory = signatureFactory;
|
SignatureFactory = signatureFactory;
|
||||||
|
|
||||||
ConversationDict = new Dictionary<string, LCIMConversation>();
|
ConversationDict = new Dictionary<string, LCIMConversation>();
|
||||||
|
@ -278,13 +271,14 @@ namespace LeanCloud.Realtime {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 连接
|
/// 登录
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="force">是否强制登录</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task Open(bool reconnect = false) {
|
public async Task Open(bool force = true) {
|
||||||
await Connection.Connect();
|
await Connection.Connect();
|
||||||
// 打开 Session
|
// 打开 Session
|
||||||
await SessionController.Open(reconnect);
|
await SessionController.Open(force);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -442,7 +436,7 @@ namespace LeanCloud.Realtime {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnConnectionDisconnect() {
|
private void OnConnectionDisconnect() {
|
||||||
OnDisconnect?.Invoke();
|
OnPaused?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnConnectionReconnect() {
|
private void OnConnectionReconnect() {
|
||||||
|
@ -454,7 +448,7 @@ namespace LeanCloud.Realtime {
|
||||||
// 打开 Session
|
// 打开 Session
|
||||||
await SessionController.Reopen();
|
await SessionController.Reopen();
|
||||||
// 回调用户
|
// 回调用户
|
||||||
OnReconnected?.Invoke();
|
OnResume?.Invoke();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LCLogger.Error(e);
|
LCLogger.Error(e);
|
||||||
await Connection.Close();
|
await Connection.Close();
|
||||||
|
|
|
@ -78,6 +78,10 @@ namespace LeanCloud.Realtime {
|
||||||
get; internal set;
|
get; internal set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsTransient {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
internal LCIMMessage() {
|
internal LCIMMessage() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue