diff --git "a/XFE\345\220\204\347\261\273\346\213\223\345\261\225/CyberComm.cs" "b/XFE\345\220\204\347\261\273\346\213\223\345\261\225/CyberComm.cs" index 9bbb56e..957365a 100644 --- "a/XFE\345\220\204\347\261\273\346\213\223\345\261\225/CyberComm.cs" +++ "b/XFE\345\220\204\347\261\273\346\213\223\345\261\225/CyberComm.cs" @@ -1,18 +1,12 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; -using System.IO; using System.Linq; using System.Net; using System.Net.WebSockets; using System.Text; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using XFE各类拓展.ArrayExtension; -using XFE各类拓展.BufferExtension; -using XFE各类拓展.FormatExtension; -using XFE各类拓展.TaskExtension; namespace XFE各类拓展.CyberComm { @@ -594,7 +588,7 @@ internal CyberCommServerEventArgsImpl(WebSocket webSocket, XFECyberCommException /// public class CyberCommGroup { - private readonly List webSockets = new List(); + private readonly List cyberCommList = new List(); /// /// 组ID /// @@ -602,18 +596,18 @@ public class CyberCommGroup /// /// 添加客户端 /// - /// 客户端 - public void Add(WebSocket webSocket) + /// 客户端 + public void Add(CyberCommServerEventArgs e) { - webSockets.Add(webSocket); + cyberCommList.Add(e); } /// - /// 获取指定的客户端 + /// 移除指定的客户端 /// /// 客户端 public void Remove(WebSocket webSocket) { - webSockets.Remove(webSocket); + cyberCommList.Remove(cyberCommList.Find(x => x.CurrentWebSocket == webSocket)); } /// /// 移除指定索引的客户端 @@ -621,14 +615,14 @@ public void Remove(WebSocket webSocket) /// 客户单索引 public void RemoveAt(int index) { - webSockets.RemoveAt(index); + cyberCommList.RemoveAt(index); } /// /// 清空列表 /// public void Clear() { - webSockets.Clear(); + cyberCommList.Clear(); } /// /// 群组客户端数量 @@ -637,19 +631,19 @@ public int Count { get { - return webSockets.Count; + return cyberCommList.Count; } } /// /// 索引器 /// - /// 索引 + /// 查找 /// 客户端 - public WebSocket this[int index] + public CyberCommServerEventArgs this[Predicate findFunc] { get { - return webSockets[index]; + return cyberCommList.Find(findFunc); } } /// @@ -658,27 +652,29 @@ public WebSocket this[int index] /// 群发文本消息 public async Task SendGroupTextMessage(string message) { - byte[] sendBuffer = Encoding.UTF8.GetBytes(message); - foreach (WebSocket webSocket in webSockets) + List tasks = new List(); + foreach (CyberCommServerEventArgs cyberCommServerEventArgs in cyberCommList) { - await webSocket.SendAsync(new ArraySegment(sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None); + tasks.Add(cyberCommServerEventArgs.ReplyMessage(message)); } + await Task.WhenAll(tasks); } /// - /// 向除了指定的WS客户端外的客户端发送群组文本消息 + /// 向指定的WS客户端发送群组文本消息 /// /// 群发文本消息 - /// 除了指定的WS客户端外的客户端 - public async Task SendGroupTextMessageExcept(string message, WebSocket exceptWebSocket) + /// 指定的WS客户端 + public async Task SendGroupTextMessage(string message, Func findFunc) { - byte[] sendBuffer = Encoding.UTF8.GetBytes(message); - foreach (WebSocket webSocket in webSockets) + List tasks = new List(); + foreach (CyberCommServerEventArgs cyberCommServerEventArgs in cyberCommList) { - if (webSocket != exceptWebSocket) + if (findFunc.Invoke(cyberCommServerEventArgs)) { - await webSocket.SendAsync(new ArraySegment(sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None); + tasks.Add(cyberCommServerEventArgs.ReplyMessage(message)); } } + await Task.WhenAll(tasks); } /// /// 发送群组二进制消息 @@ -686,25 +682,29 @@ public async Task SendGroupTextMessageExcept(string message, WebSocket exceptWeb /// 群发二进制消息 public async Task SendGroupBinaryMessage(byte[] bytes) { - foreach (WebSocket webSocket in webSockets) + List tasks = new List(); + foreach (CyberCommServerEventArgs cyberCommServerEventArgs in cyberCommList) { - await webSocket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Binary, true, CancellationToken.None); + tasks.Add(cyberCommServerEventArgs.ReplyBinaryMessage(bytes)); } + await Task.WhenAll(tasks); } /// - /// 向除了指定的WS客户端外的客户端发送群组二进制消息 + /// 向指定的WS客户端发送群组二进制消息 /// /// 群发二进制消息 - /// 除了指定的WS客户端外的客户端 - public async Task SendGroupBinaryMessageExcept(byte[] bytes, WebSocket exceptWebSocket) + /// 指定的WS客户端 + public async Task SendGroupBinaryMessage(byte[] bytes, Func findFunc) { - foreach (WebSocket webSocket in webSockets) + List tasks = new List(); + foreach (CyberCommServerEventArgs cyberCommServerEventArgs in cyberCommList) { - if (webSocket != exceptWebSocket) + if (findFunc.Invoke(cyberCommServerEventArgs)) { - await webSocket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Binary, true, CancellationToken.None); + tasks.Add(cyberCommServerEventArgs.ReplyBinaryMessage(bytes)); } } + await Task.WhenAll(tasks); } /// /// 客户端群组 @@ -718,11 +718,35 @@ public CyberCommGroup(string GroupId) /// 客户端群组 /// /// 群组ID - /// 客户端群组 - public CyberCommGroup(string GroupId, List webSockets) + /// 客户端群组 + public CyberCommGroup(string GroupId, List cyberCommList) { this.GroupId = GroupId; - this.webSockets = webSockets; + this.cyberCommList = cyberCommList; + } + } + /// + /// 代签名的WebSocket + /// + public class SignedWebSocket + { + /// + /// 签名 + /// + public string Signature { get; set; } + /// + /// 服务器 + /// + public WebSocket WebSocket { get; set; } + /// + /// 签名WebSocket + /// + /// 签名 + /// WebSocket + public SignedWebSocket(string signature, WebSocket webSocket) + { + Signature = signature; + WebSocket = webSocket; } } /// @@ -850,1133 +874,4 @@ public CyberCommGroupController() commGroups = new List(); } } - namespace XCCNetWork - { - /// - /// XFE网络通信二进制返回消息类型 - /// - public enum XCCBinaryMessageType - { - /// - /// 文本消息 - /// - Text, - /// - /// 二进制消息 - /// - Binary, - /// - /// 图片消息 - /// - Image, - /// - /// 音频消息 - /// - Audio, - /// - /// 实时音频 - /// - AudioBuffer, - /// - /// 视频消息 - /// - Video - } - /// - /// XFE网络通信明文返回消息类型 - /// - public enum XCCTextMessageType - { - /// - /// 文本消息 - /// - Text, - /// - /// 图片消息 - /// - Image, - /// - /// 音频消息 - /// - Audio, - /// - /// 视频消息 - /// - Video - } - class XCCNetWorkBase - { - /// - /// 明文消息接收时触发 - /// - public EventHandler textMessageReceived; - /// - /// 二进制消息接收时触发 - /// - public EventHandler binaryMessageReceived; - /// - /// 异常消息接收时触发 - /// - public EventHandler exceptionMessageReceived; - /// - /// 连接关闭时触发 - /// - public EventHandler connectionClosed; - /// - /// 连接成功时触发 - /// - public EventHandler connected; - } - /// - /// XCC网络通讯 - /// - public class XCCNetWork - { - private readonly XCCNetWorkBase xCCNetWorkBase; - /// - /// XCC当前群组 - /// - public List Groups { get; set; } - /// - /// 明文消息接收时触发 - /// - public event EventHandler TextMessageReceived - { - add - { - xCCNetWorkBase.textMessageReceived += value; - } - remove - { - xCCNetWorkBase.textMessageReceived -= value; - } - } - /// - /// 二进制消息接收时触发 - /// - public event EventHandler BinaryMessageReceived - { - add - { - xCCNetWorkBase.binaryMessageReceived += value; - } - remove - { - xCCNetWorkBase.binaryMessageReceived -= value; - } - } - /// - /// 异常消息接收时触发 - /// - public event EventHandler ExceptionMessageReceived - { - add - { - xCCNetWorkBase.exceptionMessageReceived += value; - } - remove - { - xCCNetWorkBase.exceptionMessageReceived -= value; - } - } - /// - /// 连接关闭时触发 - /// - public event EventHandler ConnectionClosed - { - add - { - xCCNetWorkBase.connectionClosed += value; - } - remove - { - xCCNetWorkBase.connectionClosed -= value; - } - } - /// - /// 连接成功时触发 - /// - public event EventHandler Connected - { - add - { - xCCNetWorkBase.connected += value; - } - remove - { - xCCNetWorkBase.connected -= value; - } - } - /// - /// 创建XCC群组会话 - /// - /// 群组名称 - /// 发送者 - /// - public XCCGroup CreateGroup(string groupId, string sender) - { - var group = new XCCGroupImpl(groupId, sender, xCCNetWorkBase); - Groups.Add(group); - return group; - } - /// - /// XCC网络通信会话 - /// - public XCCNetWork() - { - xCCNetWorkBase = new XCCNetWorkBase(); - Groups = new List(); - } - } - /// - /// XCC群组 - /// - public abstract class XCCGroup - { - private event EndTaskTrigger UpdateTaskTrigger; - private readonly XCCNetWorkBase workBase; - private int reconnectTimes = -1; - #region 公有属性 - /// - /// 群组ID - /// - public string GroupId { get; } - /// - /// 发送者 - /// - public string Sender { get; } - /// - /// WebSocket客户端 - /// - public ClientWebSocket ClientWebSocket { get; private set; } - /// - /// 是否已连接 - /// - public bool IsConnected { get; private set; } = false; - #endregion - #region 公有方法 - /// - /// 启动XCC会话 - /// - /// 是否自动重连 - /// 最大重连次数,-1则为无限次 - /// 重连尝试延迟 - /// - public async Task StartXCC(bool autoReconnect = true, int reconnectMaxTimes = -1, int reconnectTryDelay = 100) - { - XCCReconnect: - ClientWebSocket = new ClientWebSocket(); - Uri serverUri = new Uri("ws://xcc.api.xfegzs.com"); - var base64GroupId = Convert.ToBase64String(Encoding.UTF8.GetBytes(GroupId)); - var base64SenderId = Convert.ToBase64String(Encoding.UTF8.GetBytes(Sender)); - ClientWebSocket.Options.SetRequestHeader("Group", base64GroupId); - ClientWebSocket.Options.SetRequestHeader("Sender", base64SenderId); - reconnectTimes++; - try - { - await ClientWebSocket.ConnectAsync(serverUri, CancellationToken.None); - } - catch (Exception ex) - { - if (IsConnected == true) - { - workBase.connectionClosed?.Invoke(this, new XCCConnectionClosedEventArgsImpl(this, ClientWebSocket, false)); - } - IsConnected = false; - if (autoReconnect) - { - if (reconnectTimes <= reconnectMaxTimes || reconnectMaxTimes == -1) - { - Thread.Sleep(reconnectTryDelay); - goto XCCReconnect; - } - } - else - { - workBase.exceptionMessageReceived?.Invoke(this, new XCCExceptionMessageReceivedEventArgsImpl(this, ClientWebSocket, null, null, new XFECyberCommException("与XCC网络通讯服务器建立连接时发生异常", ex))); - return; - } - } - reconnectTimes = 0; - workBase.connected?.Invoke(this, new XCCConnectedEventArgsImpl(this, ClientWebSocket)); - IsConnected = true; - while (ClientWebSocket.State == WebSocketState.Open) - { - try - { - byte[] receiveBuffer = new byte[1024]; - WebSocketReceiveResult receiveResult = await ClientWebSocket.ReceiveAsync(new ArraySegment(receiveBuffer), CancellationToken.None); - var bufferList = new List(); - bufferList.AddRange(receiveBuffer.Take(receiveResult.Count)); - //ReceiveCompletedMessageByUsingWhile - while (!receiveResult.EndOfMessage) - { - receiveResult = await ClientWebSocket.ReceiveAsync(new ArraySegment(receiveBuffer), CancellationToken.None); - bufferList.AddRange(receiveBuffer.Take(receiveResult.Count)); - } - var receivedBinaryBuffer = bufferList.ToArray(); - if (receiveResult.MessageType == WebSocketMessageType.Text) - { - try - { - var receivedMessage = Encoding.UTF8.GetString(receivedBinaryBuffer); - var isHistory = receivedMessage.IndexOf("[XCCGetHistory]") == 0; - if (isHistory) - { - receivedMessage = receivedMessage.Substring(15); - } - var unPackedMessage = receivedMessage.ToXFEArray(); - var messageId = unPackedMessage[0]; - var signature = unPackedMessage[1]; - var message = unPackedMessage[2]; - var senderName = unPackedMessage[3]; - var sendTime = DateTime.Parse(unPackedMessage[4]); - var messageType = XCCTextMessageType.Text; - switch (signature) - { - case "[XCCTextMessage]": - messageType = XCCTextMessageType.Text; - break; - case "[XCCImage]": - messageType = XCCTextMessageType.Image; - break; - case "[XCCAudio]": - messageType = XCCTextMessageType.Audio; - break; - case "[XCCVideo]": - messageType = XCCTextMessageType.Video; - break; - default: - break; - } - workBase.textMessageReceived?.Invoke(this, new XCCTextMessageReceivedEventArgsImpl(this, ClientWebSocket, messageId, messageType, message, senderName, sendTime, isHistory)); - } - catch (Exception ex) - { - workBase.exceptionMessageReceived?.Invoke(this, new XCCExceptionMessageReceivedEventArgsImpl(this, ClientWebSocket, null, null, new XFECyberCommException("接收XCC服务器消息时发生异常", ex))); - } - } - if (receiveResult.MessageType == WebSocketMessageType.Binary) - { - try - { - var messageType = XCCBinaryMessageType.Binary; - var xFEBuffer = XFEBuffer.ToXFEBuffer(receivedBinaryBuffer); - var sender = Encoding.UTF8.GetString(xFEBuffer["Sender"]); - var signature = Encoding.UTF8.GetString(xFEBuffer["Type"]); - var messageId = Encoding.UTF8.GetString(xFEBuffer["ID"]); - byte[] unPackedBuffer = signature == "callback" ? null : xFEBuffer[sender]; - switch (signature) - { - case "text": - messageType = XCCBinaryMessageType.Text; - break; - case "image": - messageType = XCCBinaryMessageType.Image; - break; - case "audio": - messageType = XCCBinaryMessageType.Audio; - break; - case "audio-buffer": - messageType = XCCBinaryMessageType.AudioBuffer; - break; - case "video": - messageType = XCCBinaryMessageType.Video; - break; - case "callback": - UpdateTaskTrigger?.Invoke(true, messageId); - continue; - default: - messageType = XCCBinaryMessageType.Binary; - break; - } - workBase.binaryMessageReceived?.Invoke(this, new XCCBinaryMessageReceivedEventArgsImpl(this, ClientWebSocket, sender, messageId, unPackedBuffer, messageType, signature)); - } - catch (Exception ex) - { - workBase.exceptionMessageReceived?.Invoke(this, new XCCExceptionMessageReceivedEventArgsImpl(this, ClientWebSocket, null, null, new XFECyberCommException("接收XCC服务器消息时发生异常", ex))); - } - } - } - catch (Exception ex) - { - try { await ClientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Close", CancellationToken.None); } catch { } - if (IsConnected == true) - { - workBase.connectionClosed?.Invoke(this, new XCCConnectionClosedEventArgsImpl(this, ClientWebSocket, false)); - } - IsConnected = false; - if (autoReconnect) - { - if (autoReconnect) - { - Thread.Sleep(reconnectTryDelay); - if (reconnectTimes <= reconnectMaxTimes || reconnectMaxTimes == -1) - goto XCCReconnect; - } - } - else - { - workBase.exceptionMessageReceived?.Invoke(this, new XCCExceptionMessageReceivedEventArgsImpl(this, ClientWebSocket, null, null, new XFECyberCommException("与XCC网络通讯服务器建立连接时发生异常", ex))); - return; - } - } - } - workBase.connectionClosed?.Invoke(this, new XCCConnectionClosedEventArgsImpl(this, ClientWebSocket, true)); - } - /// - /// 发送文本消息,返回消息ID - /// - /// 待发送的文本 - /// 最长超时时长 - /// 服务器接收校验是否成功 - public async Task SendTextMessage(string message, int timeout = 30000) - { - var messageId = Guid.NewGuid().ToString(); - return await SendTextMessage(message, messageId, timeout); - } - /// - /// 发送文本消息 - /// - /// 待发送的文本 - /// 消息ID - /// 最长超时时长 - /// - /// 服务器接收校验是否成功 - public async Task SendTextMessage(string message, string messageId, int timeout) - { - try - { - byte[] sendBuffer = Encoding.UTF8.GetBytes(new string[] { messageId, "[XCCTextMessage]", message }.ToXFEString()); - await ClientWebSocket.SendAsync(new ArraySegment(sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None); - var endTask = Task.Run(async () => - { - await Task.Delay(timeout); - UpdateTaskTrigger?.Invoke(false, messageId); - }); - return await new XFEWaitTask(ref UpdateTaskTrigger, messageId); - } - catch (Exception ex) - { - throw new XFECyberCommException("客户端发送文本到服务器时出现异常", ex); - } - } - /// - /// 发送标准的文本消息 - /// - /// 发送者角色 - /// 待发送的文本 - /// - /// 服务器接收校验是否成功 - [Obsolete("发送者已统一,请使用SendTextMessage或SendBinaryTextMessage")] - public async Task SendStandardTextMessage(string role, string message) - { - try - { - return await SendTextMessage(message); - } - catch (Exception ex) - { - throw new XFECyberCommException("客户端发送文本到服务器时出现异常", ex); - } - } - /// - /// 发送签名二进制消息,返回消息ID - /// - /// 二进制消息 - /// 签名标识 - /// 最长超时时长 - /// 服务器接收校验是否成功 - public async Task SendSignedBinaryMessage(byte[] message, string signature, int timeout = 10000) - { - var messageId = Guid.NewGuid().ToString(); - return await SendSignedBinaryMessage(message, messageId, signature, timeout); - } - /// - /// 发送签名二进制消息 - /// - /// 二进制消息 - /// 消息ID - /// 签名标识 - /// 最长超时时长 - /// 服务器接收校验是否成功 - /// - public async Task SendSignedBinaryMessage(byte[] message, string messageId, string signature, int timeout) - { - try - { - var xFEBuffer = new XFEBuffer(Sender, message, "Type", Encoding.UTF8.GetBytes(signature), "ID", Encoding.UTF8.GetBytes(messageId)); - await ClientWebSocket.SendAsync(new ArraySegment(xFEBuffer.ToBuffer()), WebSocketMessageType.Binary, true, CancellationToken.None); - var endTask = Task.Run(async () => - { - await Task.Delay(timeout); - UpdateTaskTrigger?.Invoke(false, messageId); - }); - return await new XFEWaitTask(ref UpdateTaskTrigger, messageId); - } - catch (Exception ex) - { - throw new XFECyberCommException("客户端发送二进制数据到服务器时出现异常", ex); - } - } - /// - /// 发送二进制文本消息 - /// - /// 消息 - /// 服务器接收校验是否成功 - /// - public async Task SendBinaryTextMessage(string message) - { - try - { - return await SendSignedBinaryMessage(Encoding.UTF8.GetBytes(message), "text"); - } - catch (Exception ex) - { - throw new XFECyberCommException("客户端发送文本到服务器时出现异常", ex); - } - } - /// - /// 发送默认标准的二进制消息 - /// - /// 待发送的二进制数据 - /// 最长超时时长 - /// - /// 服务器接收校验是否成功 - public async Task SendBinaryMessage(byte[] message, int timeout = 1000) - { - try - { - return await SendSignedBinaryMessage(message, "binary", timeout); - } - catch (Exception ex) - { - throw new XFECyberCommException("客户端发送二进制数据到服务器时出现异常", ex); - } - } - /// - /// 发送图片 - /// - /// 图片路径 - /// 服务器接收校验是否成功 - /// - public async Task SendImage(string filePath) - { - try - { - return await SendSignedBinaryMessage(File.ReadAllBytes(filePath), "image", 60000); - } - catch (Exception ex) - { - throw new XFECyberCommException("客户端发送图片到服务器时出现异常", ex); - } - } - /// - /// 发送视频 - /// - /// 视频路径 - /// 服务器接收校验是否成功 - /// - public async Task SendVideo(string filePath) - { - try - { - return await SendSignedBinaryMessage(File.ReadAllBytes(filePath), "video", 300000); - } - catch (Exception ex) - { - throw new XFECyberCommException("客户端发送视频到服务器时出现异常", ex); - } - } - /// - /// 发送音频 - /// - /// 音频路径 - /// 服务器接收校验是否成功 - /// - public async Task SendAudio(string filePath) - { - try - { - return await SendSignedBinaryMessage(File.ReadAllBytes(filePath), "audio"); - } - catch (Exception ex) { throw new XFECyberCommException("客户端发送音频到服务器时出现异常", ex); } - } - /// - /// 发送音频字节流(服务器不会缓存) - /// - /// 二进制音频流 - /// 服务器接收校验是否成功 - /// - public async Task SendAudioBuffer(byte[] buffer) - { - try - { - return await SendSignedBinaryMessage(buffer, "audio-buffer"); - } - catch (Exception ex) { throw new XFECyberCommException("客户端发送音频到服务器时出现异常", ex); } - } - /// - /// 获取历史记录 - /// - /// - public async Task GetHistory() - { - try - { - var messageId = Guid.NewGuid().ToString(); - byte[] sendBuffer = Encoding.UTF8.GetBytes(new string[] { messageId, "[XCCGetHistory]", "[XCCGetHistory]" }.ToXFEString()); - await ClientWebSocket.SendAsync(new ArraySegment(sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None); - var endTask = Task.Run(async () => - { - await Task.Delay(5000); - UpdateTaskTrigger?.Invoke(false, messageId); - }); - return await new XFEWaitTask(ref UpdateTaskTrigger, messageId); - } - catch (Exception ex) - { - throw new XFECyberCommException("客户端获取历史记录时出现异常", ex); - } - } - /// - /// 关闭XCC会话 - /// - /// - /// - public async Task CloseXCC() - { - try - { - await ClientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "客户端主动关闭连接", CancellationToken.None); - } - catch (Exception ex) - { - throw new XFECyberCommException("客户端关闭连接时出现异常", ex); - } - } - #endregion - internal XCCGroup(string groupId, string sender, XCCNetWorkBase xCCNetWorkBase) - { - GroupId = groupId; - Sender = sender; - workBase = xCCNetWorkBase; - } - } - /// - /// XCC文件类型 - /// - public enum XCCFileType - { - /// - /// 图片 - /// - Image, - /// - /// 视频 - /// - Video, - /// - /// 音频 - /// - Audio - } - /// - /// XCC消息接收器 - /// - public class XCCMessageReceiveHelper - { - private readonly Dictionary xCCFileDictionary = new Dictionary(); - private readonly Dictionary xCCMessageDictionary = new Dictionary(); - /// - /// 自动保存到本地 - /// - public bool AutoSaveInLocal { get; set; } - /// - /// 保存的根目录 - /// - public string SavePathRoot { get; set; } - /// - /// 接收到文件事件 - /// - public event EventHandler FileReceived; - /// - /// 接收到文本事件 - /// - public event EventHandler TextReceived; - /// - /// 从设置的根目录加载 - /// - /// - public async Task Load() - { - await Task.Run(() => - { - foreach (var groupId in Directory.EnumerateDirectories(SavePathRoot)) - { - foreach (var file in Directory.EnumerateFiles($"{SavePathRoot}/{groupId}")) - { - var filePath = $"{SavePathRoot}/{groupId}/{file}"; - if (file == "XFEMessage.xfe") - { - xCCMessageDictionary.Add(groupId, new XFEMultiDictionary(File.ReadAllText(filePath))); - } - else - { - LoadFile(groupId, filePath); - } - } - } - }); - } - /// - /// 从设置的根目录的指定群组加载 - /// - /// 群组ID - /// - public async Task LoadGroup(string groupId) - { - await Task.Run(() => - { - foreach (var file in Directory.EnumerateFiles($"{SavePathRoot}/{groupId}")) - { - var filePath = $"{SavePathRoot}/{groupId}/{file}"; - if (file == "XFEMessage.xfe") - { - xCCMessageDictionary.Add(groupId, new XFEMultiDictionary(File.ReadAllText(filePath))); - } - else - { - LoadFile(groupId, filePath); - } - } - }); - } - private void LoadFile(string groupId, string filePath) - { - var messageId = Path.GetFileNameWithoutExtension(filePath); - var fileBuffer = File.ReadAllBytes(filePath); - switch (Path.GetExtension(filePath)) - { - case ".png": - xCCFileDictionary.Add(messageId, new XCCFile(groupId, messageId, XCCFileType.Image, fileBuffer)); - break; - case ".mp4": - xCCFileDictionary.Add(messageId, new XCCFile(groupId, messageId, XCCFileType.Video, fileBuffer)); - break; - case ".mp3": - xCCFileDictionary.Add(messageId, new XCCFile(groupId, messageId, XCCFileType.Audio, fileBuffer)); - break; - default: - break; - } - } - /// - /// 获取文件 - /// - /// 消息ID - /// - public XCCFile GetFile(string messageId) - { - return xCCFileDictionary.ContainsKey(messageId) ? xCCFileDictionary[messageId] : null; - } - /// - /// 添加文件 - /// - /// XCC文件实例 - public void AddFile(XCCFile xCCFile) - { - xCCFileDictionary.Add(xCCFile.MessageId, xCCFile); - if (AutoSaveInLocal && xCCFile.FileBuffer != null) - Save(xCCFile); - } - /// - /// 保存图片 - /// - /// XCC图片实例 - public void Save(XCCFile xCCFile) - { - if (!Directory.Exists($"{SavePathRoot}/{xCCFile.GroupId}")) - { - Directory.CreateDirectory($"{SavePathRoot}/{xCCFile.GroupId}"); - } - switch (xCCFile.FileType) - { - case XCCFileType.Image: - File.WriteAllBytes($"{SavePathRoot}/{xCCFile.GroupId}/{xCCFile.MessageId}.png", xCCFile.FileBuffer); - break; - case XCCFileType.Video: - File.WriteAllBytes($"{SavePathRoot}/{xCCFile.GroupId}/{xCCFile.MessageId}.mp4", xCCFile.FileBuffer); - break; - case XCCFileType.Audio: - File.WriteAllBytes($"{SavePathRoot}/{xCCFile.GroupId}/{xCCFile.MessageId}.mp3", xCCFile.FileBuffer); - break; - default: - break; - } - } - private void ReceiveFilePlaceHolder(XCCTextMessageReceivedEventArgs e, XCCFileType fileType) - { - if (!xCCFileDictionary.ContainsKey(e.MessageId)) - { - xCCFileDictionary.Add(e.MessageId, new XCCFile(e.GroupId, e.MessageId, fileType)); - } - } - /// - /// 接收文本消息 - /// - /// 发送者 - /// 事件参数 - public void ReceiveTextMessage(object sender, XCCTextMessageReceivedEventArgs e) - { - switch (e.MessageType) - { - case XCCTextMessageType.Text: - //xCCMessageDictionary.Add(e.GroupId, new XFEMultiDictionary(e.)); - break; - case XCCTextMessageType.Image: - ReceiveFilePlaceHolder(e, XCCFileType.Image); - break; - case XCCTextMessageType.Audio: - ReceiveFilePlaceHolder(e, XCCFileType.Audio); - break; - case XCCTextMessageType.Video: - ReceiveFilePlaceHolder(e, XCCFileType.Video); - break; - default: - break; - } - } - /// - /// 接收二进制消息 - /// - /// 发送者 - /// 事件参数 - public void ReceiveBinaryMessage(object sender, XCCBinaryMessageReceivedEventArgs e) - { - if (xCCFileDictionary.ContainsKey(e.MessageId)) - { - var xCCFile = xCCFileDictionary[e.MessageId]; - if (!xCCFile.Loaded) - { - xCCFileDictionary[e.MessageId].LoadFile(e.BinaryMessage); - if (AutoSaveInLocal) - Save(xCCFile); - } - } - } - /// - /// XCC消息接收器 - /// - /// 保存根目录 - /// 自动保存 - public XCCMessageReceiveHelper(string savePathRoot, bool autoSaveInLocal = true) - { - AutoSaveInLocal = autoSaveInLocal; - SavePathRoot = savePathRoot; - } - } - /// - /// XCC文件 - /// - public class XCCFile - { - /// - /// 文件加载完成时触发 - /// - public event EventHandler FileLoaded; - /// - /// 群组ID - /// - public string GroupId { get; } - /// - /// 消息ID - /// - public string MessageId { get; } - /// - /// XCC文件类型 - /// - public XCCFileType FileType { get; } - /// - /// 是否已加载 - /// - public bool Loaded { get; private set; } - /// - /// 文件流 - /// - public byte[] FileBuffer { get; set; } - /// - /// 加载文件 - /// - /// 文件流 - public void LoadFile(byte[] fileBuffer) - { - FileBuffer = fileBuffer; - Loaded = true; - FileLoaded?.Invoke(this, fileBuffer); - } - /// - /// XCC文件 - /// - /// 群组ID - /// 文件消息ID - /// 文件类型 - /// 文件的Buffer - public XCCFile(string groupId, string messageId, XCCFileType fileType, byte[] fileBuffer) - { - GroupId = groupId; - FileType = fileType; - MessageId = messageId; - FileBuffer = fileBuffer; - Loaded = true; - } - /// - /// XCC文件 - /// - /// 群组ID - /// 文件ID - /// 文件类型 - public XCCFile(string groupId, string messageId, XCCFileType fileType) - { - GroupId = groupId; - FileType = fileType; - MessageId = messageId; - Loaded = false; - } - } - /// - /// XCC消息 - /// - public class XCCMessage : XFEEntry - { - /// - /// XCC消息 - /// - /// XCC头 - /// 内容 - public XCCMessage(string Header, string Content) : base(Header, Content) { } - } - /// - /// XCC网络通讯事件 - /// - public abstract class XCCMessageReceivedEventArgs : EventArgs - { - /// - /// 当前WebSocket - /// - public ClientWebSocket CurrentWebSocket { get; } - /// - /// 触发事件的群组 - /// - public XCCGroup Group { get; } - /// - /// 消息ID - /// - public string MessageId { get; } - /// - /// 群组ID - /// - public string GroupId - { - get - { - return Group.GroupId; - } - } - /// - /// 发送者 - /// - public string Sender { get; } - /// - /// 回复文本消息 - /// - /// 待发送的文本 - /// 发送进程 - public async Task ReplyTextMessage(string message) - { - try - { - byte[] sendBuffer = Encoding.UTF8.GetBytes(message); - await CurrentWebSocket.SendAsync(new ArraySegment(sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None); - } - catch (Exception ex) - { - throw new XFECyberCommException("收到服务器端数据后客户端回复文本数据时出现异常", ex); - } - } - /// - /// 回复二进制消息 - /// - /// 二进制消息 - /// - public async Task ReplyBinaryMessage(byte[] message) - { - try - { - await CurrentWebSocket.SendAsync(new ArraySegment(message), WebSocketMessageType.Binary, true, CancellationToken.None); - } - catch (Exception ex) - { - throw new XFECyberCommException("收到服务器端数据后客户端回复文本数据时出现异常", ex); - } - } - /// - /// 关闭连接 - /// - /// - public async Task Close() - { - try - { - await CurrentWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "客户端主动关闭连接", CancellationToken.None); - } - catch (Exception ex) - { - throw new XFECyberCommException("客户端关闭连接时出现异常", ex); - } - } - internal XCCMessageReceivedEventArgs(XCCGroup group, ClientWebSocket clientWebSocket, string sender, string messageId) - { - Group = group; - CurrentWebSocket = clientWebSocket; - Sender = sender; - MessageId = messageId; - } - } - /// - /// XCC网络通讯接收到明文消息事件 - /// - public abstract class XCCTextMessageReceivedEventArgs : XCCMessageReceivedEventArgs - { - /// - /// 返回文本消息类型 - /// - public XCCTextMessageType MessageType { get; } - /// - /// 文本消息 - /// - public string TextMessage { get; } - /// - /// 发送时间 - /// - public DateTime SendTime { get; } - /// - /// 是否为历史消息 - /// - public bool IsHistory { get; } - internal XCCTextMessageReceivedEventArgs(XCCGroup group, ClientWebSocket clientWebSocket, string messageId, XCCTextMessageType messageType, string message, string sender, DateTime sendTime, bool isHistory) : base(group, clientWebSocket, sender, messageId) - { - MessageType = messageType; - TextMessage = message; - SendTime = sendTime; - IsHistory = isHistory; - } - - } - /// - /// XCC网络通讯接收到二进制消息事件 - /// - public abstract class XCCBinaryMessageReceivedEventArgs : XCCMessageReceivedEventArgs - { - /// - /// 返回二进制消息类型 - /// - public XCCBinaryMessageType MessageType { get; } - /// - /// 消息签名 - /// - public string Signature { get; } - /// - /// 二进制消息 - /// - public byte[] BinaryMessage { get; } - internal XCCBinaryMessageReceivedEventArgs(XCCGroup group, ClientWebSocket clientWebSocket, string sender, string messageId, byte[] buffer, XCCBinaryMessageType messageType, string signature) : base(group, clientWebSocket, sender, messageId) - { - BinaryMessage = buffer; - MessageType = messageType; - Signature = signature; - } - } - /// - /// XCC网络通讯期间异常事件 - /// - public abstract class XCCExceptionMessageReceivedEventArgs : XCCMessageReceivedEventArgs - { - /// - /// 异常信息 - /// - public XFECyberCommException Exception { get; } - internal XCCExceptionMessageReceivedEventArgs(XCCGroup group, ClientWebSocket clientWebSocket, string sender, string messageId, XFECyberCommException exception) : base(group, clientWebSocket, sender, messageId) - { - Exception = exception; - } - } - /// - /// XCC会话关闭事件 - /// - public abstract class XCCConnectionClosedEventArgs : EventArgs - { - /// - /// 当前WebSocket - /// - public ClientWebSocket CurrentWebSocket { get; } - /// - /// 是否正常关闭 - /// - public bool ClosedNormally { get; } - /// - /// 触发事件的群组 - /// - public XCCGroup Group { get; } - internal XCCConnectionClosedEventArgs(XCCGroup group, ClientWebSocket clientWebSocket, bool closeNormally) - { - CurrentWebSocket = clientWebSocket; - Group = group; - ClosedNormally = closeNormally; - } - } - /// - /// XCC连接事件 - /// - public abstract class XCCConnectedEventArgs : EventArgs - { - /// - /// 当前WebSocket - /// - public ClientWebSocket CurrentWebSocket { get; } - /// - /// 触发事件的群组 - /// - public XCCGroup Group { get; } - internal XCCConnectedEventArgs(XCCGroup group, ClientWebSocket clientWebSocket) - { - CurrentWebSocket = clientWebSocket; - Group = group; - } - } - class XCCGroupImpl : XCCGroup - { - internal XCCGroupImpl(string groupId, string sender, XCCNetWorkBase xCCNetWorkBase) : base(groupId, sender, xCCNetWorkBase) { } - } - class XCCConnectionClosedEventArgsImpl : XCCConnectionClosedEventArgs - { - internal XCCConnectionClosedEventArgsImpl(XCCGroup group, ClientWebSocket clientWebSocket, bool closeNormally) : base(group, clientWebSocket, closeNormally) { } - } - class XCCConnectedEventArgsImpl : XCCConnectedEventArgs - { - internal XCCConnectedEventArgsImpl(XCCGroup group, ClientWebSocket clientWebSocket) : base(group, clientWebSocket) { } - } - class XCCTextMessageReceivedEventArgsImpl : XCCTextMessageReceivedEventArgs - { - internal XCCTextMessageReceivedEventArgsImpl(XCCGroup group, ClientWebSocket clientWebSocket, string messageId, XCCTextMessageType messageType, string message, string sender, DateTime sendTime, bool isHistory) : base(group, clientWebSocket, messageId, messageType, message, sender, sendTime, isHistory) { } - } - class XCCBinaryMessageReceivedEventArgsImpl : XCCBinaryMessageReceivedEventArgs - { - internal XCCBinaryMessageReceivedEventArgsImpl(XCCGroup group, ClientWebSocket clientWebSocket, string sender, string messageId, byte[] buffer, XCCBinaryMessageType messageType, string signature) : base(group, clientWebSocket, sender, messageId, buffer, messageType, signature) { } - } - class XCCExceptionMessageReceivedEventArgsImpl : XCCExceptionMessageReceivedEventArgs - { - internal XCCExceptionMessageReceivedEventArgsImpl(XCCGroup group, ClientWebSocket clientWebSocket, string sender, string messageId, XFECyberCommException exception) : base(group, clientWebSocket, sender, messageId, exception) { } - } - } } \ No newline at end of file diff --git "a/XFE\345\220\204\347\261\273\346\213\223\345\261\225/DelegateExtension.cs" "b/XFE\345\220\204\347\261\273\346\213\223\345\261\225/DelegateExtension.cs" new file mode 100644 index 0000000..350e5e1 --- /dev/null +++ "b/XFE\345\220\204\347\261\273\346\213\223\345\261\225/DelegateExtension.cs" @@ -0,0 +1,23 @@ +namespace XFE各类拓展.DelegateExtension +{ + /// + /// 指定发送者泛型的委托事件 + /// + /// 发送者泛型 + /// 发送者 + public delegate void XFEEventHandler(T sender); + /// + /// 指定发送者泛型的委托事件 + /// + /// 发送者泛型 + /// 事件参数泛型 + /// 发送者 + /// 事件参数 + public delegate void XFEEventHandler(T1 sender, T2 e); + /// + /// 委托事件拓展 + /// + public static class DelegateExtension + { + } +} diff --git "a/XFE\345\220\204\347\261\273\346\213\223\345\261\225/FormatExtension.cs" "b/XFE\345\220\204\347\261\273\346\213\223\345\261\225/FormatExtension.cs" index 2558256..6bf856f 100644 --- "a/XFE\345\220\204\347\261\273\346\213\223\345\261\225/FormatExtension.cs" +++ "b/XFE\345\220\204\347\261\273\346\213\223\345\261\225/FormatExtension.cs" @@ -487,6 +487,17 @@ public void CopyTo(XFEEntry[] array, int arrayIndex) xFEMultiDictionaryList.CopyTo(array, arrayIndex); } /// + /// 获取内容 + /// + /// 头 + /// + public List GetContents(string header) + { + var list = new List(); + xFEMultiDictionaryList.FindAll(x => x.Header == header)?.ForEach(y => list.Add(y.Content)); + return list; + } + /// /// 获取索引器 /// /// diff --git "a/XFE\345\220\204\347\261\273\346\213\223\345\261\225/Properties/AssemblyInfo.cs" "b/XFE\345\220\204\347\261\273\346\213\223\345\261\225/Properties/AssemblyInfo.cs" index e986368..ae35b7c 100644 --- "a/XFE\345\220\204\347\261\273\346\213\223\345\261\225/Properties/AssemblyInfo.cs" +++ "b/XFE\345\220\204\347\261\273\346\213\223\345\261\225/Properties/AssemblyInfo.cs" @@ -32,5 +32,5 @@ //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 //通过使用 "*",如下所示: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.2")] -[assembly: AssemblyFileVersion("1.0.0.2")] +[assembly: AssemblyVersion("1.0.0.3")] +[assembly: AssemblyFileVersion("1.0.0.3")] diff --git "a/XFE\345\220\204\347\261\273\346\213\223\345\261\225/XCCNetWork.cs" "b/XFE\345\220\204\347\261\273\346\213\223\345\261\225/XCCNetWork.cs" new file mode 100644 index 0000000..f09e20f --- /dev/null +++ "b/XFE\345\220\204\347\261\273\346\213\223\345\261\225/XCCNetWork.cs" @@ -0,0 +1,1542 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using XFE各类拓展.ArrayExtension; +using XFE各类拓展.BufferExtension; +using XFE各类拓展.DelegateExtension; +using XFE各类拓展.FormatExtension; +using XFE各类拓展.TaskExtension; + +namespace XFE各类拓展.CyberComm.XCCNetWork +{ + class XCCNetWorkBase + { + /// + /// 明文消息接收时触发 + /// + public EventHandler textMessageReceived; + /// + /// 二进制消息接收时触发 + /// + public EventHandler binaryMessageReceived; + /// + /// 异常消息接收时触发 + /// + public EventHandler exceptionMessageReceived; + /// + /// 连接关闭时触发 + /// + public EventHandler connectionClosed; + /// + /// 连接成功时触发 + /// + public EventHandler connected; + } + /// + /// XCC客户端连接类型 + /// + public enum XCCClientType + { + /// + /// 明文类型 + /// + TextMessageClient, + /// + /// 文件类型 + /// + FileTransportClient + } + /// + /// XCC网络通讯 + /// + public class XCCNetWork + { + private readonly XCCNetWorkBase xCCNetWorkBase; + /// + /// XCC当前群组 + /// + public List Groups { get; set; } + /// + /// 明文消息接收时触发 + /// + public event EventHandler TextMessageReceived + { + add + { + xCCNetWorkBase.textMessageReceived += value; + } + remove + { + xCCNetWorkBase.textMessageReceived -= value; + } + } + /// + /// 二进制消息接收时触发 + /// + public event EventHandler BinaryMessageReceived + { + add + { + xCCNetWorkBase.binaryMessageReceived += value; + } + remove + { + xCCNetWorkBase.binaryMessageReceived -= value; + } + } + /// + /// 异常消息接收时触发 + /// + public event EventHandler ExceptionMessageReceived + { + add + { + xCCNetWorkBase.exceptionMessageReceived += value; + } + remove + { + xCCNetWorkBase.exceptionMessageReceived -= value; + } + } + /// + /// 连接关闭时触发 + /// + public event EventHandler ConnectionClosed + { + add + { + xCCNetWorkBase.connectionClosed += value; + } + remove + { + xCCNetWorkBase.connectionClosed -= value; + } + } + /// + /// 连接成功时触发 + /// + public event EventHandler Connected + { + add + { + xCCNetWorkBase.connected += value; + } + remove + { + xCCNetWorkBase.connected -= value; + } + } + /// + /// 创建XCC群组会话 + /// + /// 群组名称 + /// 发送者 + /// + public XCCGroup CreateGroup(string groupId, string sender) + { + var group = new XCCGroupImpl(Guid.NewGuid().ToString(), groupId, sender, xCCNetWorkBase); + Groups.Add(group); + return group; + } + /// + /// XCC网络通信会话 + /// + public XCCNetWork() + { + xCCNetWorkBase = new XCCNetWorkBase(); + Groups = new List(); + } + } + /// + /// XCC群组 + /// + public abstract class XCCGroup + { + private event EndTaskTrigger UpdateTaskTrigger; + private readonly XCCNetWorkBase workBase; + private int reconnectTimes = -1; + private bool readyToClose = false; + #region 公有属性 + /// + /// 客户端标识名 + /// + public string Signature { get; } + /// + /// 群组ID + /// + public string GroupId { get; } + /// + /// 发送者 + /// + public string Sender { get; } + /// + /// 明文传输服务器是否连接 + /// + public bool TextMessageClientConnected { get; private set; } = false; + /// + /// 文件传输服务器是否连接 + /// + public bool FileTransportClientConnected { get; private set; } = false; + /// + /// WebSocket明文传输客户端 + /// + public ClientWebSocket TextMessageClientWebSocket { get; private set; } + /// + /// WebSocket文件传输客户端 + /// + public ClientWebSocket FileTransportClientWebSocket { get; private set; } + #endregion + #region 公有方法 + /// + /// 启动XCC会话 + /// + /// 是否自动重连 + /// 最大重连次数,-1则为无限次 + /// 重连尝试延迟 + /// + public async Task StartXCC(bool autoReconnect = true, int reconnectMaxTimes = -1, int reconnectTryDelay = 100) + { + var textMessageXCCTask = StartTextMessageXCC(autoReconnect, reconnectMaxTimes, reconnectTryDelay); + var fileTransportXCCTask = StartFileTransportXCC(autoReconnect, reconnectMaxTimes, reconnectTryDelay); + await Task.WhenAll(textMessageXCCTask, fileTransportXCCTask); + } + /// + /// 启动XCC文本会话 + /// + /// 是否自动重连 + /// 最大重连次数,-1则为无限次 + /// 重连尝试延迟 + /// + public async Task StartTextMessageXCC(bool autoReconnect = true, int reconnectMaxTimes = -1, int reconnectTryDelay = 100) + { + XCCReconnect: + TextMessageClientWebSocket = new ClientWebSocket(); + Uri serverUri = new Uri("ws://xcc.api.xfegzs.com"); + var base64GroupId = Convert.ToBase64String(Encoding.UTF8.GetBytes(GroupId)); + var base64SenderId = Convert.ToBase64String(Encoding.UTF8.GetBytes(Sender)); + TextMessageClientWebSocket.Options.SetRequestHeader("Group", base64GroupId); + TextMessageClientWebSocket.Options.SetRequestHeader("Sender", base64SenderId); + TextMessageClientWebSocket.Options.SetRequestHeader("Type", "Text"); + TextMessageClientWebSocket.Options.SetRequestHeader("Signature", Signature); + reconnectTimes++; + try + { + if (TextMessageClientWebSocket.State != WebSocketState.Open) + await TextMessageClientWebSocket.ConnectAsync(serverUri, CancellationToken.None); + } + catch (Exception ex) + { + if (readyToClose) + { + return; + } + if (TextMessageClientConnected == true) + { + workBase.connectionClosed?.Invoke(this, new XCCConnectionClosedEventArgsImpl(this, XCCClientType.TextMessageClient, TextMessageClientWebSocket, FileTransportClientWebSocket, false)); + } + TextMessageClientConnected = false; + if (autoReconnect) + { + if (reconnectTimes <= reconnectMaxTimes || reconnectMaxTimes == -1) + { + Thread.Sleep(reconnectTryDelay); + goto XCCReconnect; + } + } + else + { + workBase.exceptionMessageReceived?.Invoke(this, new XCCExceptionMessageReceivedEventArgsImpl(this, TextMessageClientWebSocket, FileTransportClientWebSocket, XCCClientType.TextMessageClient, null, null, new XFECyberCommException("与XCC网络通讯明文服务器建立连接时发生异常", ex))); + return; + } + } + reconnectTimes = 0; + TextMessageClientConnected = true; + workBase.connected?.Invoke(this, new XCCConnectedEventArgsImpl(this, XCCClientType.TextMessageClient, TextMessageClientWebSocket, FileTransportClientWebSocket)); + while (TextMessageClientWebSocket.State == WebSocketState.Open) + { + try + { + byte[] receiveBuffer = new byte[1024]; + WebSocketReceiveResult receiveResult = await TextMessageClientWebSocket.ReceiveAsync(new ArraySegment(receiveBuffer), CancellationToken.None); + var bufferList = new List(); + bufferList.AddRange(receiveBuffer.Take(receiveResult.Count)); + //ReceiveCompletedMessageByUsingWhile + while (!receiveResult.EndOfMessage) + { + receiveResult = await TextMessageClientWebSocket.ReceiveAsync(new ArraySegment(receiveBuffer), CancellationToken.None); + bufferList.AddRange(receiveBuffer.Take(receiveResult.Count)); + } + var receivedBinaryBuffer = bufferList.ToArray(); + if (receiveResult.MessageType == WebSocketMessageType.Text) + { + try + { + var receivedMessage = Encoding.UTF8.GetString(receivedBinaryBuffer); + var isHistory = receivedMessage.IndexOf("[XCCGetHistory]") == 0; + if (isHistory) + { + receivedMessage = receivedMessage.Substring(15); + } + var unPackedMessage = receivedMessage.ToXFEArray(); + var messageId = unPackedMessage[0]; + var signature = unPackedMessage[1]; + var message = unPackedMessage[2]; + var senderName = unPackedMessage[3]; + var sendTime = DateTime.Parse(unPackedMessage[4]); + var messageType = XCCTextMessageType.Text; + switch (signature) + { + case "[XCCTextMessage]": + messageType = XCCTextMessageType.Text; + break; + case "[XCCImage]": + messageType = XCCTextMessageType.Image; + break; + case "[XCCAudio]": + messageType = XCCTextMessageType.Audio; + break; + case "[XCCVideo]": + messageType = XCCTextMessageType.Video; + break; + default: + break; + } + workBase.textMessageReceived?.Invoke(this, new XCCTextMessageReceivedEventArgsImpl(this, TextMessageClientWebSocket, FileTransportClientWebSocket, XCCClientType.TextMessageClient, messageId, messageType, message, senderName, sendTime, isHistory)); + } + catch (Exception ex) + { + workBase.exceptionMessageReceived?.Invoke(this, new XCCExceptionMessageReceivedEventArgsImpl(this, TextMessageClientWebSocket, FileTransportClientWebSocket, XCCClientType.TextMessageClient, null, null, new XFECyberCommException("接收XCC服务器消息时发生异常", ex))); + } + } + else if (receiveResult.MessageType == WebSocketMessageType.Binary) + { + try + { + var xFEBuffer = XFEBuffer.ToXFEBuffer(receivedBinaryBuffer); + var signature = Encoding.UTF8.GetString(xFEBuffer["Type"]); + var messageId = Encoding.UTF8.GetString(xFEBuffer["ID"]); + if (signature == "callback") + UpdateTaskTrigger?.Invoke(true, messageId); + } + catch (Exception ex) + { + workBase.exceptionMessageReceived?.Invoke(this, new XCCExceptionMessageReceivedEventArgsImpl(this, TextMessageClientWebSocket, FileTransportClientWebSocket, XCCClientType.TextMessageClient, null, null, new XFECyberCommException("接收XCC服务器消息时发生异常", ex))); + } + } + } + catch (Exception ex) + { + try { await TextMessageClientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Close", CancellationToken.None); } catch { } + if (TextMessageClientConnected == true) + { + workBase.connectionClosed?.Invoke(this, new XCCConnectionClosedEventArgsImpl(this, XCCClientType.TextMessageClient, TextMessageClientWebSocket, FileTransportClientWebSocket, false)); + } + TextMessageClientConnected = false; + if (autoReconnect) + { + if (autoReconnect) + { + Thread.Sleep(reconnectTryDelay); + if (reconnectTimes <= reconnectMaxTimes || reconnectMaxTimes == -1) + goto XCCReconnect; + } + } + else + { + workBase.exceptionMessageReceived?.Invoke(this, new XCCExceptionMessageReceivedEventArgsImpl(this, TextMessageClientWebSocket, FileTransportClientWebSocket, XCCClientType.TextMessageClient, null, null, new XFECyberCommException("与XCC网络通讯服务器建立连接时发生异常", ex))); + return; + } + } + } + workBase.connectionClosed?.Invoke(this, new XCCConnectionClosedEventArgsImpl(this, XCCClientType.TextMessageClient, TextMessageClientWebSocket, FileTransportClientWebSocket, true)); + } + /// + /// 启动XCC文件传输会话 + /// + /// 是否自动重连 + /// 最大重连次数,-1则为无限次 + /// 重连尝试延迟 + /// + public async Task StartFileTransportXCC(bool autoReconnect = true, int reconnectMaxTimes = -1, int reconnectTryDelay = 100) + { + XCCReconnect: + FileTransportClientWebSocket = new ClientWebSocket(); + Uri serverUri = new Uri("ws://xcc.api.xfegzs.com"); + var base64GroupId = Convert.ToBase64String(Encoding.UTF8.GetBytes(GroupId)); + var base64SenderId = Convert.ToBase64String(Encoding.UTF8.GetBytes(Sender)); + FileTransportClientWebSocket.Options.SetRequestHeader("Group", base64GroupId); + FileTransportClientWebSocket.Options.SetRequestHeader("Sender", base64SenderId); + FileTransportClientWebSocket.Options.SetRequestHeader("Type", "File"); + FileTransportClientWebSocket.Options.SetRequestHeader("Signature", Signature); + reconnectTimes++; + try + { + if (FileTransportClientWebSocket.State != WebSocketState.Open) + await FileTransportClientWebSocket.ConnectAsync(serverUri, CancellationToken.None); + } + catch (Exception ex) + { + if (readyToClose) + { + return; + } + if (FileTransportClientConnected == true) + { + workBase.connectionClosed?.Invoke(this, new XCCConnectionClosedEventArgsImpl(this, XCCClientType.FileTransportClient, TextMessageClientWebSocket, FileTransportClientWebSocket, false)); + } + FileTransportClientConnected = false; + if (autoReconnect) + { + if (reconnectTimes <= reconnectMaxTimes || reconnectMaxTimes == -1) + { + Thread.Sleep(reconnectTryDelay); + goto XCCReconnect; + } + } + else + { + workBase.exceptionMessageReceived?.Invoke(this, new XCCExceptionMessageReceivedEventArgsImpl(this, TextMessageClientWebSocket, FileTransportClientWebSocket, XCCClientType.FileTransportClient, null, null, new XFECyberCommException("与XCC网络通讯明文服务器建立连接时发生异常", ex))); + return; + } + } + reconnectTimes = 0; + FileTransportClientConnected = true; + workBase.connected?.Invoke(this, new XCCConnectedEventArgsImpl(this, XCCClientType.FileTransportClient, TextMessageClientWebSocket, FileTransportClientWebSocket)); + while (FileTransportClientWebSocket.State == WebSocketState.Open) + { + try + { + byte[] receiveBuffer = new byte[1024]; + WebSocketReceiveResult receiveResult = await FileTransportClientWebSocket.ReceiveAsync(new ArraySegment(receiveBuffer), CancellationToken.None); + var bufferList = new List(); + bufferList.AddRange(receiveBuffer.Take(receiveResult.Count)); + //ReceiveCompletedMessageByUsingWhile + while (!receiveResult.EndOfMessage) + { + receiveResult = await FileTransportClientWebSocket.ReceiveAsync(new ArraySegment(receiveBuffer), CancellationToken.None); + bufferList.AddRange(receiveBuffer.Take(receiveResult.Count)); + } + var receivedBinaryBuffer = bufferList.ToArray(); + if (receiveResult.MessageType == WebSocketMessageType.Binary) + { + try + { + var messageType = XCCBinaryMessageType.Binary; + var xFEBuffer = XFEBuffer.ToXFEBuffer(receivedBinaryBuffer); + var sender = Encoding.UTF8.GetString(xFEBuffer["Sender"]); + var signature = Encoding.UTF8.GetString(xFEBuffer["Type"]); + if (signature == "callback") + return; + var messageId = Encoding.UTF8.GetString(xFEBuffer["ID"]); + bool isHistory = Encoding.UTF8.GetString(xFEBuffer["IsHistory"]) == "True"; + var sendTime = DateTime.Parse(Encoding.UTF8.GetString(xFEBuffer["SendTime"])); + byte[] unPackedBuffer = xFEBuffer[sender]; + switch (signature) + { + case "text": + messageType = XCCBinaryMessageType.Text; + break; + case "image": + messageType = XCCBinaryMessageType.Image; + break; + case "audio": + messageType = XCCBinaryMessageType.Audio; + break; + case "audio-buffer": + messageType = XCCBinaryMessageType.AudioBuffer; + break; + case "video": + messageType = XCCBinaryMessageType.Video; + break; + case "callback": + UpdateTaskTrigger?.Invoke(true, messageId); + continue; + default: + messageType = XCCBinaryMessageType.Binary; + break; + } + workBase.binaryMessageReceived?.Invoke(this, new XCCBinaryMessageReceivedEventArgsImpl(this, TextMessageClientWebSocket, FileTransportClientWebSocket, XCCClientType.FileTransportClient, sender, messageId, unPackedBuffer, messageType, signature, sendTime, isHistory)); + } + catch (Exception ex) + { + workBase.exceptionMessageReceived?.Invoke(this, new XCCExceptionMessageReceivedEventArgsImpl(this, TextMessageClientWebSocket, FileTransportClientWebSocket, XCCClientType.FileTransportClient, null, null, new XFECyberCommException("接收XCC服务器消息时发生异常", ex))); + } + } + } + catch (Exception ex) + { + try { await FileTransportClientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Close", CancellationToken.None); } catch { } + if (FileTransportClientConnected == true) + { + workBase.connectionClosed?.Invoke(this, new XCCConnectionClosedEventArgsImpl(this, XCCClientType.FileTransportClient, TextMessageClientWebSocket, FileTransportClientWebSocket, false)); + } + FileTransportClientConnected = false; + if (autoReconnect) + { + if (autoReconnect) + { + Thread.Sleep(reconnectTryDelay); + if (reconnectTimes <= reconnectMaxTimes || reconnectMaxTimes == -1) + goto XCCReconnect; + } + } + else + { + workBase.exceptionMessageReceived?.Invoke(this, new XCCExceptionMessageReceivedEventArgsImpl(this, TextMessageClientWebSocket, FileTransportClientWebSocket, XCCClientType.FileTransportClient, null, null, new XFECyberCommException("与XCC网络通讯服务器建立连接时发生异常", ex))); + return; + } + } + } + workBase.connectionClosed?.Invoke(this, new XCCConnectionClosedEventArgsImpl(this, XCCClientType.FileTransportClient, TextMessageClientWebSocket, FileTransportClientWebSocket, true)); + } + /// + /// 等待明文服务器和文件服务器均连接 + /// + /// + public async Task WaitConnect() + { + await Task.Run(() => { while (!TextMessageClientConnected || !FileTransportClientConnected) { } }); + } + /// + /// 发送文本消息 + /// + /// 待发送的文本 + /// 最长超时时长 + /// 服务器接收校验是否成功 + public async Task SendTextMessage(string message, int timeout = 30000) + { + var messageId = Guid.NewGuid().ToString(); + return await SendTextMessage(message, messageId, timeout); + } + /// + /// 发送文本消息 + /// + /// 待发送的文本 + /// 消息ID + /// 最长超时时长 + /// + /// 服务器接收校验是否成功 + public async Task SendTextMessage(string message, string messageId, int timeout) + { + try + { + byte[] sendBuffer = Encoding.UTF8.GetBytes(new string[] { messageId, "[XCCTextMessage]", message }.ToXFEString()); + await TextMessageClientWebSocket.SendAsync(new ArraySegment(sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None); + var endTask = Task.Run(async () => + { + await Task.Delay(timeout); + UpdateTaskTrigger?.Invoke(false, messageId); + }); + return await new XFEWaitTask(ref UpdateTaskTrigger, messageId); + } + catch (Exception ex) + { + throw new XFECyberCommException("客户端发送文本到服务器时出现异常", ex); + } + } + /// + /// 发送标准的文本消息 + /// + /// 发送者角色 + /// 待发送的文本 + /// + /// 服务器接收校验是否成功 + [Obsolete("发送者已统一,请使用SendTextMessage或SendBinaryTextMessage")] + public async Task SendStandardTextMessage(string role, string message) + { + try + { + return await SendTextMessage(message); + } + catch (Exception ex) + { + throw new XFECyberCommException("客户端发送文本到服务器时出现异常", ex); + } + } + /// + /// 发送签名二进制消息 + /// + /// 二进制消息 + /// 签名标识 + /// 最长超时时长 + /// 服务器接收校验是否成功 + public async Task SendSignedBinaryMessage(byte[] message, string signature, int timeout = 10000) + { + var messageId = Guid.NewGuid().ToString(); + return await SendSignedBinaryMessage(message, messageId, signature, timeout); + } + /// + /// 发送签名二进制消息 + /// + /// 二进制消息 + /// 消息ID + /// 签名标识 + /// 最长超时时长 + /// 服务器接收校验是否成功 + /// + public async Task SendSignedBinaryMessage(byte[] message, string messageId, string signature, int timeout) + { + try + { + var xFEBuffer = new XFEBuffer(Sender, message, "Type", Encoding.UTF8.GetBytes(signature), "ID", Encoding.UTF8.GetBytes(messageId)); + await FileTransportClientWebSocket.SendAsync(new ArraySegment(xFEBuffer.ToBuffer()), WebSocketMessageType.Binary, true, CancellationToken.None); + var endTask = Task.Run(async () => + { + await Task.Delay(timeout); + UpdateTaskTrigger?.Invoke(false, messageId); + }); + return await new XFEWaitTask(ref UpdateTaskTrigger, messageId); + } + catch (Exception ex) + { + throw new XFECyberCommException("客户端发送二进制数据到服务器时出现异常", ex); + } + } + /// + /// 发送二进制文本消息 + /// + /// 消息 + /// 服务器接收校验是否成功 + /// + public async Task SendBinaryTextMessage(string message) + { + try + { + return await SendSignedBinaryMessage(Encoding.UTF8.GetBytes(message), "text"); + } + catch (Exception ex) + { + throw new XFECyberCommException("客户端发送文本到服务器时出现异常", ex); + } + } + /// + /// 发送默认标准的二进制消息 + /// + /// 待发送的二进制数据 + /// 最长超时时长 + /// + /// 服务器接收校验是否成功 + public async Task SendBinaryMessage(byte[] message, int timeout = 1000) + { + try + { + return await SendSignedBinaryMessage(message, "binary", timeout); + } + catch (Exception ex) + { + throw new XFECyberCommException("客户端发送二进制数据到服务器时出现异常", ex); + } + } + /// + /// 发送图片 + /// + /// 图片路径 + /// 服务器接收校验是否成功 + /// + public async Task SendImage(string filePath) + { + try + { + return await SendSignedBinaryMessage(File.ReadAllBytes(filePath), "image", 60000); + } + catch (Exception ex) + { + throw new XFECyberCommException("客户端发送图片到服务器时出现异常", ex); + } + } + /// + /// 发送视频 + /// + /// 视频路径 + /// 服务器接收校验是否成功 + /// + public async Task SendVideo(string filePath) + { + try + { + return await SendSignedBinaryMessage(File.ReadAllBytes(filePath), "video", 300000); + } + catch (Exception ex) + { + throw new XFECyberCommException("客户端发送视频到服务器时出现异常", ex); + } + } + /// + /// 发送音频 + /// + /// 音频路径 + /// 服务器接收校验是否成功 + /// + public async Task SendAudio(string filePath) + { + try + { + return await SendSignedBinaryMessage(File.ReadAllBytes(filePath), "audio"); + } + catch (Exception ex) { throw new XFECyberCommException("客户端发送音频到服务器时出现异常", ex); } + } + /// + /// 发送音频字节流(服务器不会缓存) + /// + /// 二进制音频流 + /// 服务器接收校验是否成功 + /// + public async Task SendAudioBuffer(byte[] buffer) + { + try + { + return await SendSignedBinaryMessage(buffer, "audio-buffer"); + } + catch (Exception ex) { throw new XFECyberCommException("客户端发送音频到服务器时出现异常", ex); } + } + /// + /// 获取历史记录 + /// + /// + public async Task GetHistory() + { + try + { + var messageId = Guid.NewGuid().ToString(); + byte[] sendBuffer = Encoding.UTF8.GetBytes(new string[] { messageId, "[XCCGetHistory]", "[XCCGetHistory]" }.ToXFEString()); + await TextMessageClientWebSocket.SendAsync(new ArraySegment(sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None); + var endTask = Task.Run(async () => + { + await Task.Delay(5000); + UpdateTaskTrigger?.Invoke(false, messageId); + }); + return await new XFEWaitTask(ref UpdateTaskTrigger, messageId); + } + catch (Exception ex) + { + throw new XFECyberCommException("客户端获取历史记录时出现异常", ex); + } + } + /// + /// 关闭XCC会话 + /// + /// + /// + public async Task CloseXCC() + { + try + { + readyToClose = true; + await TextMessageClientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "客户端主动关闭连接", CancellationToken.None); + await FileTransportClientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "客户端主动关闭连接", CancellationToken.None); + } + catch (Exception ex) + { + throw new XFECyberCommException("客户端关闭连接时出现异常", ex); + } + } + #endregion + internal XCCGroup(string signature, string groupId, string sender, XCCNetWorkBase xCCNetWorkBase) + { + Signature = signature; + GroupId = groupId; + Sender = sender; + workBase = xCCNetWorkBase; + } + } + /// + /// XCC文件类型 + /// + public enum XCCFileType + { + /// + /// 图片 + /// + Image, + /// + /// 视频 + /// + Video, + /// + /// 音频 + /// + Audio + } + /// + /// 消息接收触发器 + /// + /// + /// 是否为历史消息 + /// 消息 + public delegate void MessageReceivedHandler(bool isHistory, T message); + /// + /// XCC消息接收器 + /// + public class XCCMessageReceiveHelper + { + private readonly Dictionary xCCFileDictionary = new Dictionary(); + private readonly Dictionary> xCCMessageDictionary = new Dictionary>(); + private bool loaded = false; + /// + /// 自动保存到本地 + /// + public bool AutoSaveInLocal { get; set; } + /// + /// 保存的根目录 + /// + public string SavePathRoot { get; set; } + /// + /// 接收到文件事件 + /// + public event MessageReceivedHandler FileReceived; + /// + /// 接收到文本事件 + /// + public event MessageReceivedHandler TextReceived; + /// + /// 错误发生事件 + /// + public event XFEEventHandler ExceptionOccurred; + /// + /// 接收到实时音频字节流事件 + /// + public event XFEEventHandler AudioBufferReceived; + /// + /// 从设置的根目录加载 + /// + /// + public async Task Load() + { + await Task.Run(() => + { + if (Directory.Exists(SavePathRoot)) + { + foreach (var groupIdFullPath in Directory.EnumerateDirectories(SavePathRoot)) + { + var groupId = Path.GetFileName(groupIdFullPath); + if (File.Exists($"{groupIdFullPath}/XFEMessage/XFEMessage.xfe")) + { + var xCCMessageList = new List(); + foreach (var entry in new XFEMultiDictionary(File.ReadAllText($"{groupIdFullPath}/XFEMessage/XFEMessage.xfe"))) + { + var xCCMessage = XCCMessage.ConvertToXCCMessage(entry.Content, groupId); + xCCMessageList.Add(xCCMessage); + if (xCCMessage.MessageType == XCCTextMessageType.Text) + TextReceived?.Invoke(true, xCCMessage); + else + FileReceived?.Invoke(true, LoadFile(xCCMessage)); + } + xCCMessageDictionary.Add(groupId, xCCMessageList); + } + } + } + else + { + Directory.CreateDirectory(SavePathRoot); + } + }); + loaded = true; + } + /// + /// 从设置的根目录的指定群组加载 + /// + /// 群组ID + /// + public async Task LoadGroup(string groupId) + { + await Task.Run(() => + { + if (File.Exists($"{SavePathRoot}/{groupId}/XFEMessage/XFEMessage.xfe")) + { + var xCCMessageList = new List(); + foreach (var entry in new XFEMultiDictionary(File.ReadAllText($"{SavePathRoot}/{groupId}/XFEMessage/XFEMessage.xfe"))) + { + var xCCMessage = XCCMessage.ConvertToXCCMessage(entry.Content, groupId); + xCCMessageList.Add(xCCMessage); + if (xCCMessage.MessageType == XCCTextMessageType.Text) + TextReceived?.Invoke(true, xCCMessage); + else + FileReceived?.Invoke(true, LoadFile(xCCMessage)); + } + xCCMessageDictionary.Add(groupId, xCCMessageList); + } + }); + loaded = true; + } + /// + /// 清理无用文件 + /// + /// + public async Task ClearUselessFile() + { + if (!loaded) + throw new XFEExtensionException("不能在加载完成前调用清理"); + await Task.Run(() => + { + foreach (var groupId in xCCMessageDictionary.Keys) + { + foreach (var file in Directory.EnumerateFiles($"{SavePathRoot}/{groupId}")) + { + var filePath = $"{SavePathRoot}/{groupId}/{file}"; + var messageId = Path.GetFileNameWithoutExtension(filePath); + if (!xCCMessageDictionary.ContainsKey(groupId) || xCCMessageDictionary[groupId].Find(x => x.MessageId == messageId) == null) + { + File.Delete(filePath); + } + } + } + }); + } + private XCCFile LoadFile(XCCMessage xCCMessage) + { + var filePath = $"{SavePathRoot}/{xCCMessage.GroupId}/{xCCMessage.MessageId}.xfe"; + byte[] fileBuffer = null; + if (File.Exists(filePath)) + fileBuffer = File.ReadAllBytes(filePath); + XCCFile xCCFile; + if (xCCFileDictionary.ContainsKey(xCCMessage.MessageId)) + { + if (!xCCFileDictionary[xCCMessage.MessageId].Loaded && fileBuffer != null) + xCCFileDictionary[xCCMessage.MessageId].LoadFile(fileBuffer); + return xCCFileDictionary[xCCMessage.MessageId]; + } + switch (xCCMessage.MessageType) + { + case XCCTextMessageType.Image: + xCCFile = new XCCFile(xCCMessage.GroupId, xCCMessage.MessageId, XCCFileType.Image, xCCMessage.Sender, xCCMessage.SendTime, fileBuffer); + break; + case XCCTextMessageType.Audio: + xCCFile = new XCCFile(xCCMessage.GroupId, xCCMessage.MessageId, XCCFileType.Audio, xCCMessage.Sender, xCCMessage.SendTime, fileBuffer); + break; + case XCCTextMessageType.Video: + xCCFile = new XCCFile(xCCMessage.GroupId, xCCMessage.MessageId, XCCFileType.Video, xCCMessage.Sender, xCCMessage.SendTime, fileBuffer); + break; + default: + return null; + } + xCCFileDictionary.Add(xCCMessage.MessageId, xCCFile); + return xCCFile; + } + /// + /// 获取文件 + /// + /// 消息ID + /// + public XCCFile GetFile(string messageId) + { + return xCCFileDictionary.ContainsKey(messageId) ? xCCFileDictionary[messageId] : null; + } + /// + /// 添加文件 + /// + /// XCC文件实例 + public void AddFile(XCCFile xCCFile) + { + xCCFileDictionary.Add(xCCFile.MessageId, xCCFile); + if (AutoSaveInLocal && xCCFile.FileBuffer != null) + SaveFile(xCCFile); + } + /// + /// 保存文件 + /// + /// XCC文件实例 + public void SaveFile(XCCFile xCCFile) + { + if (!Directory.Exists($"{SavePathRoot}/{xCCFile.GroupId}")) + { + Directory.CreateDirectory($"{SavePathRoot}/{xCCFile.GroupId}"); + } + File.WriteAllBytes($"{SavePathRoot}/{xCCFile.GroupId}/{xCCFile.MessageId}.xfe", xCCFile.FileBuffer); + } + /// + /// 保存群组消息 + /// + /// + public void SaveMessage(string groupId) + { + var filePath = $"{SavePathRoot}/{groupId}/XFEMessage"; + if (!Directory.Exists(filePath)) + { + Directory.CreateDirectory(filePath); + } + var storageDictionary = new XFEDictionary(); + foreach (var xCCMessage in xCCMessageDictionary[groupId]) + { + storageDictionary.Add(xCCMessage.MessageId, xCCMessage.ToString()); + } + File.WriteAllText(filePath + "/XFEMessage.xfe", storageDictionary.ToString()); + } + private void ReceiveFilePlaceHolder(XCCTextMessageReceivedEventArgs e, XCCFileType fileType) + { + var xCCFile = new XCCFile(e.GroupId, e.MessageId, fileType, e.Sender, e.SendTime); + if (!xCCFileDictionary.ContainsKey(e.MessageId)) + { + xCCFileDictionary.Add(e.MessageId, xCCFile); + } + FileReceived?.Invoke(e.IsHistory, xCCFile); + if (AutoSaveInLocal) + SaveMessage(e.GroupId); + } + private void ReceiveTextMessage(object sender, XCCTextMessageReceivedEventArgs e) + { + var message = new XCCMessage(e.MessageId, e.MessageType, e.TextMessage, e.Sender, e.SendTime, e.GroupId); + if (xCCMessageDictionary.ContainsKey(e.GroupId)) + { + if (xCCMessageDictionary[e.GroupId].Find(x => x.MessageId == e.MessageId) == null) + { + xCCMessageDictionary[e.GroupId].Add(message); + } + else + { + return; + } + } + else + { + xCCMessageDictionary.Add(e.GroupId, new List() { message }); + } + switch (e.MessageType) + { + case XCCTextMessageType.Text: + TextReceived?.Invoke(e.IsHistory, message); + if (AutoSaveInLocal) + SaveMessage(e.GroupId); + break; + case XCCTextMessageType.Image: + ReceiveFilePlaceHolder(e, XCCFileType.Image); + break; + case XCCTextMessageType.Audio: + ReceiveFilePlaceHolder(e, XCCFileType.Audio); + break; + case XCCTextMessageType.Video: + ReceiveFilePlaceHolder(e, XCCFileType.Video); + break; + default: + break; + } + } + private void ReceiveBinaryMessage(object sender, XCCBinaryMessageReceivedEventArgs e) + { + if (e.MessageType == XCCBinaryMessageType.AudioBuffer) + { + AudioBufferReceived?.Invoke(e.BinaryMessage); + return; + } + if (xCCFileDictionary.ContainsKey(e.MessageId)) + { + var xCCFile = xCCFileDictionary[e.MessageId]; + if (!xCCFile.Loaded) + { + xCCFileDictionary[e.MessageId].LoadFile(e.BinaryMessage); + if (AutoSaveInLocal) + SaveFile(xCCFile); + } + } + else + { + var fileType = XCCFileType.Image; + switch (e.MessageType) + { + case XCCBinaryMessageType.Image: + fileType = XCCFileType.Image; + break; + case XCCBinaryMessageType.Audio: + fileType = XCCFileType.Audio; + break; + case XCCBinaryMessageType.AudioBuffer: + break; + case XCCBinaryMessageType.Video: + fileType = XCCFileType.Video; + break; + default: + break; + } + var xCCFile = new XCCFile(e.GroupId, e.MessageId, fileType, e.Sender, e.SendTime, e.BinaryMessage); + xCCFileDictionary.Add(e.MessageId, xCCFile); + if (!e.IsHistory) + FileReceived?.Invoke(e.IsHistory, xCCFile); + } + } + private void XCCNetWork_ExceptionMessageReceived(object sender, XCCExceptionMessageReceivedEventArgs e) + { + ExceptionOccurred?.Invoke(e.Exception); + } + /// + /// XCC消息接收器 + /// + /// 保存根目录 + /// 自动保存 + public XCCMessageReceiveHelper(string savePathRoot, bool autoSaveInLocal = true) + { + AutoSaveInLocal = autoSaveInLocal; + SavePathRoot = savePathRoot; + } + /// + /// XCC消息接收器 + /// + /// 保存根目录 + /// XCC网络通讯实例 + /// 自动保存 + public XCCMessageReceiveHelper(string savePathRoot, XCCNetWork xCCNetWork, bool autoSaveInLocal = true) + { + AutoSaveInLocal = autoSaveInLocal; + SavePathRoot = savePathRoot; + xCCNetWork.TextMessageReceived += ReceiveTextMessage; + xCCNetWork.BinaryMessageReceived += ReceiveBinaryMessage; + xCCNetWork.ExceptionMessageReceived += XCCNetWork_ExceptionMessageReceived; + } + } + /// + /// XCC文件 + /// + public class XCCFile + { + /// + /// 文件加载完成时触发 + /// + public event XFEEventHandler FileLoaded; + /// + /// 群组ID + /// + public string GroupId { get; } + /// + /// 消息ID + /// + public string MessageId { get; } + /// + /// 发送者 + /// + public string Sender { get; } + /// + /// 发送时间 + /// + public DateTime? SendTime { get; } + /// + /// XCC文件类型 + /// + public XCCFileType FileType { get; } + /// + /// 是否已加载 + /// + public bool Loaded { get; private set; } + /// + /// 文件流 + /// + public byte[] FileBuffer { get; set; } + /// + /// 加载文件 + /// + /// 文件流 + public void LoadFile(byte[] fileBuffer) + { + FileBuffer = fileBuffer; + Loaded = true; + FileLoaded?.Invoke(this); + } + /// + /// XCC文件 + /// + /// 群组ID + /// 文件消息ID + /// 文件类型 + /// 发送者 + /// 发送时间 + /// 文件的Buffer + public XCCFile(string groupId, string messageId, XCCFileType fileType, string sender, DateTime? sendTime, byte[] fileBuffer = null) + { + GroupId = groupId; + MessageId = messageId; + FileType = fileType; + Sender = sender; + SendTime = sendTime; + FileBuffer = fileBuffer; + Loaded = fileBuffer != null; + } + } + /// + /// XCC消息 + /// + public class XCCMessage + { + /// + /// 消息ID + /// + public string MessageId { get; } + /// + /// 消息类型 + /// + public XCCTextMessageType MessageType { get; } + /// + /// 消息内容 + /// + public string Message { get; } + /// + /// 发送者 + /// + public string Sender { get; } + /// + /// 发送时间 + /// + public DateTime SendTime { get; } + /// + /// 群组ID + /// + public string GroupId { get; } + /// + /// 封装为字符串 + /// + /// + public override string ToString() + { + return new string[] { MessageId, MessageType.ToString(), Message, Sender, SendTime.ToString() }.ToXFEString(); + } + /// + /// 将封装后的XCC消息字符串转换为XCC消息对象 + /// + /// + /// + /// + public static XCCMessage ConvertToXCCMessage(string xCCMessageStringFormat, string groupId) + { + var unPackedMessage = xCCMessageStringFormat.ToXFEArray(); + return new XCCMessage(unPackedMessage[0], (XCCTextMessageType)Enum.Parse(typeof(XCCTextMessageType), unPackedMessage[1]), unPackedMessage[2], unPackedMessage[3], DateTime.Parse(unPackedMessage[4]), groupId); + } + /// + /// XCC消息 + /// + /// 消息ID + /// 消息类型 + /// 消息内容 + /// 发送者 + /// 发送时间 + /// 群组ID + public XCCMessage(string messageId, XCCTextMessageType messageType, string message, string sender, DateTime sendTime, string groupId) + { + MessageId = messageId; + MessageType = messageType; + Message = message; + Sender = sender; + SendTime = sendTime; + GroupId = groupId; + } + } + /// + /// XCC网络通讯事件 + /// + public abstract class XCCMessageReceivedEventArgs : EventArgs + { + /// + /// 触发事件的群组 + /// + public XCCGroup Group { get; } + /// + /// WebSocket明文传输客户端 + /// + public ClientWebSocket TextMessageClientWebSocket { get; private set; } + /// + /// WebSocket文件传输客户端 + /// + public ClientWebSocket FileTransportClientWebSocket { get; private set; } + /// + /// XCC服务器连接类型 + /// + public XCCClientType XCCClientType { get; } + /// + /// 消息ID + /// + public string MessageId { get; } + /// + /// 群组ID + /// + public string GroupId + { + get + { + return Group.GroupId; + } + } + /// + /// 发送者 + /// + public string Sender { get; } + /// + /// 回复文本消息 + /// + /// 待发送的文本 + /// 发送进程 + public async Task ReplyTextMessage(string message) + { + try + { + byte[] sendBuffer = Encoding.UTF8.GetBytes(message); + await TextMessageClientWebSocket.SendAsync(new ArraySegment(sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None); + } + catch (Exception ex) + { + throw new XFECyberCommException("收到服务器端数据后客户端回复文本数据时出现异常", ex); + } + } + /// + /// 回复二进制消息 + /// + /// 二进制消息 + /// + public async Task ReplyBinaryMessage(byte[] message) + { + try + { + await FileTransportClientWebSocket.SendAsync(new ArraySegment(message), WebSocketMessageType.Binary, true, CancellationToken.None); + } + catch (Exception ex) + { + throw new XFECyberCommException("收到服务器端数据后客户端回复文本数据时出现异常", ex); + } + } + /// + /// 关闭连接 + /// + /// + public async Task Close() + { + try + { + if (TextMessageClientWebSocket.State == WebSocketState.Open) + await TextMessageClientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "客户端主动关闭连接", CancellationToken.None); + if (FileTransportClientWebSocket.State == WebSocketState.Open) + await FileTransportClientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "客户端主动关闭连接", CancellationToken.None); + } + catch (Exception ex) + { + throw new XFECyberCommException("客户端关闭连接时出现异常", ex); + } + } + internal XCCMessageReceivedEventArgs(XCCGroup group, ClientWebSocket textMessageClientWebSocket, ClientWebSocket fileTransportClientWebSocket, XCCClientType xCCClientType, string sender, string messageId) + { + Group = group; + TextMessageClientWebSocket = textMessageClientWebSocket; + FileTransportClientWebSocket = fileTransportClientWebSocket; + XCCClientType = xCCClientType; + Sender = sender; + MessageId = messageId; + } + } + /// + /// XFE网络通信明文返回消息类型 + /// + public enum XCCTextMessageType + { + /// + /// 文本消息 + /// + Text, + /// + /// 图片消息 + /// + Image, + /// + /// 音频消息 + /// + Audio, + /// + /// 视频消息 + /// + Video + } + /// + /// XCC网络通讯接收到明文消息事件 + /// + public abstract class XCCTextMessageReceivedEventArgs : XCCMessageReceivedEventArgs + { + /// + /// 返回文本消息类型 + /// + public XCCTextMessageType MessageType { get; } + /// + /// 文本消息 + /// + public string TextMessage { get; } + /// + /// 发送时间 + /// + public DateTime SendTime { get; } + /// + /// 是否为历史消息 + /// + public bool IsHistory { get; } + internal XCCTextMessageReceivedEventArgs(XCCGroup group, ClientWebSocket textMessageClientWebSocket, ClientWebSocket fileTransportClientWebSocket, XCCClientType xCCClientType, string messageId, XCCTextMessageType messageType, string message, string sender, DateTime sendTime, bool isHistory) : base(group, textMessageClientWebSocket, fileTransportClientWebSocket, xCCClientType, sender, messageId) + { + MessageType = messageType; + TextMessage = message; + SendTime = sendTime; + IsHistory = isHistory; + } + + } + /// + /// XFE网络通信二进制返回消息类型 + /// + public enum XCCBinaryMessageType + { + /// + /// 文本消息 + /// + Text, + /// + /// 二进制消息 + /// + Binary, + /// + /// 图片消息 + /// + Image, + /// + /// 音频消息 + /// + Audio, + /// + /// 实时音频 + /// + AudioBuffer, + /// + /// 视频消息 + /// + Video + } + /// + /// XCC网络通讯接收到二进制消息事件 + /// + public abstract class XCCBinaryMessageReceivedEventArgs : XCCMessageReceivedEventArgs + { + /// + /// 返回二进制消息类型 + /// + public XCCBinaryMessageType MessageType { get; } + /// + /// 消息签名 + /// + public string Signature { get; } + /// + /// 二进制消息 + /// + public byte[] BinaryMessage { get; } + /// + /// 发送时间 + /// + public DateTime SendTime { get; } + /// + /// 是否为历史消息 + /// + public bool IsHistory { get; } + internal XCCBinaryMessageReceivedEventArgs(XCCGroup group, ClientWebSocket textMessageClientWebSocket, ClientWebSocket fileTransportClientWebSocket, XCCClientType xCCClientType, string sender, string messageId, byte[] buffer, XCCBinaryMessageType messageType, string signature, DateTime sendTime, bool isHistory) : base(group, textMessageClientWebSocket, fileTransportClientWebSocket, xCCClientType, sender, messageId) + { + BinaryMessage = buffer; + MessageType = messageType; + Signature = signature; + SendTime = sendTime; + IsHistory = isHistory; + } + } + /// + /// XCC网络通讯期间异常事件 + /// + public abstract class XCCExceptionMessageReceivedEventArgs : XCCMessageReceivedEventArgs + { + /// + /// 异常信息 + /// + public XFECyberCommException Exception { get; } + internal XCCExceptionMessageReceivedEventArgs(XCCGroup group, ClientWebSocket textMessageClientWebSocket, ClientWebSocket fileTransportClientWebSocket, XCCClientType xCCClientType, string sender, string messageId, XFECyberCommException exception) : base(group, textMessageClientWebSocket, fileTransportClientWebSocket, xCCClientType, sender, messageId) + { + Exception = exception; + } + } + /// + /// XCC会话关闭事件 + /// + public abstract class XCCConnectionClosedEventArgs : EventArgs + { + /// + /// 触发事件的群组 + /// + public XCCGroup Group { get; } + /// + /// XCC服务器连接类型 + /// + public XCCClientType XCCClientType { get; } + /// + /// WebSocket明文传输客户端 + /// + public ClientWebSocket TextMessageClientWebSocket { get; private set; } + /// + /// WebSocket文件传输客户端 + /// + public ClientWebSocket FileTransportClientWebSocket { get; private set; } + /// + /// 是否正常关闭 + /// + public bool ClosedNormally { get; } + + internal XCCConnectionClosedEventArgs(XCCGroup group, XCCClientType xCCClientType, ClientWebSocket textMessageClientWebSocket, ClientWebSocket fileTransportClientWebSocket, bool closedNormally) + { + Group = group; + XCCClientType = xCCClientType; + TextMessageClientWebSocket = textMessageClientWebSocket; + FileTransportClientWebSocket = fileTransportClientWebSocket; + ClosedNormally = closedNormally; + } + } + /// + /// XCC连接事件 + /// + public abstract class XCCConnectedEventArgs : EventArgs + { + /// + /// 触发事件的群组 + /// + public XCCGroup Group { get; } + /// + /// XCC服务器连接类型 + /// + public XCCClientType XCCClientType { get; } + /// + /// WebSocket明文传输客户端 + /// + public ClientWebSocket TextMessageClientWebSocket { get; private set; } + /// + /// WebSocket文件传输客户端 + /// + public ClientWebSocket FileTransportClientWebSocket { get; private set; } + + internal XCCConnectedEventArgs(XCCGroup group, XCCClientType xCCClientType, ClientWebSocket textMessageClientWebSocket, ClientWebSocket fileTransportClientWebSocket) + { + Group = group; + XCCClientType = xCCClientType; + TextMessageClientWebSocket = textMessageClientWebSocket; + FileTransportClientWebSocket = fileTransportClientWebSocket; + } + } + class XCCGroupImpl : XCCGroup + { + internal XCCGroupImpl(string signature, string groupId, string sender, XCCNetWorkBase xCCNetWorkBase) : base(signature, groupId, sender, xCCNetWorkBase) { } + } + class XCCConnectionClosedEventArgsImpl : XCCConnectionClosedEventArgs + { + public XCCConnectionClosedEventArgsImpl(XCCGroup group, XCCClientType xCCClientType, ClientWebSocket textMessageClientWebSocket, ClientWebSocket fileTransportClientWebSocket, bool closedNormally) : base(group, xCCClientType, textMessageClientWebSocket, fileTransportClientWebSocket, closedNormally) { } + } + class XCCConnectedEventArgsImpl : XCCConnectedEventArgs + { + public XCCConnectedEventArgsImpl(XCCGroup group, XCCClientType xCCClientType, ClientWebSocket textMessageClientWebSocket, ClientWebSocket fileTransportClientWebSocket) : base(group, xCCClientType, textMessageClientWebSocket, fileTransportClientWebSocket) { } + } + class XCCTextMessageReceivedEventArgsImpl : XCCTextMessageReceivedEventArgs + { + internal XCCTextMessageReceivedEventArgsImpl(XCCGroup group, ClientWebSocket textMessageClientWebSocket, ClientWebSocket fileTransportClientWebSocket, XCCClientType xCCClientType, string messageId, XCCTextMessageType messageType, string message, string sender, DateTime sendTime, bool isHistory) : base(group, textMessageClientWebSocket, fileTransportClientWebSocket, xCCClientType, messageId, messageType, message, sender, sendTime, isHistory) { } + } + class XCCBinaryMessageReceivedEventArgsImpl : XCCBinaryMessageReceivedEventArgs + { + internal XCCBinaryMessageReceivedEventArgsImpl(XCCGroup group, ClientWebSocket textMessageClientWebSocket, ClientWebSocket fileTransportClientWebSocket, XCCClientType xCCClientType, string sender, string messageId, byte[] buffer, XCCBinaryMessageType messageType, string signature, DateTime sendTime, bool isHistory) : base(group, textMessageClientWebSocket, fileTransportClientWebSocket, xCCClientType, sender, messageId, buffer, messageType, signature, sendTime, isHistory) { } + } + class XCCExceptionMessageReceivedEventArgsImpl : XCCExceptionMessageReceivedEventArgs + { + internal XCCExceptionMessageReceivedEventArgsImpl(XCCGroup group, ClientWebSocket textMessageClientWebSocket, ClientWebSocket fileTransportClientWebSocket, XCCClientType xCCClientType, string sender, string messageId, XFECyberCommException exception) : base(group, textMessageClientWebSocket, fileTransportClientWebSocket, xCCClientType, sender, messageId, exception) { } + } +} \ No newline at end of file diff --git "a/XFE\345\220\204\347\261\273\346\213\223\345\261\225/XFEChatGPT.cs" "b/XFE\345\220\204\347\261\273\346\213\223\345\261\225/XFEChatGPT.cs" index a0fea90..ffd21e2 100644 --- "a/XFE\345\220\204\347\261\273\346\213\223\345\261\225/XFEChatGPT.cs" +++ "b/XFE\345\220\204\347\261\273\346\213\223\345\261\225/XFEChatGPT.cs" @@ -67,22 +67,26 @@ public enum ChatGPTModel /// /// 比任何 GPT-3.5 型号都更强大,能够执行更复杂的任务,并针对聊天进行了优化。将在发布 2 周后使用我们最新的模型迭代进行更新。 /// - [Obsolete("该模型暂时不可使用", true)] gpt4, /// + /// 最新的GPT-4模型,具有改进的指令跟随、JSON模式、可复制输出、并行函数调用等功能。最多返回4096个输出标记。此预览模型还不适合生产流量。 + /// + gpt4turbo, + /// + /// 能够理解图像,以及所有其他GPT-4 Turbo功能。最多返回4096个输出标记。这是一个预览模型版本,还不适合生产流量。 + /// + gpt4turbovision, + /// /// 13 年 2023 月 3 日带有函数调用数据的快照。与gpt-4不同,此模型不会收到更新,并将在新版本发布3个月后弃用。基于gpt-4 /// - [Obsolete("该模型暂时不可使用", true)] gpt40613, /// /// 功能与gpt4基本模型相同,但上下文长度是其 4 倍。将使用最新的模型迭代进行更新。基于gpt-4 /// - [Obsolete("该模型暂时不可使用", true)] gpt432k, /// /// 13 年 2023 月 3 日的快照。与GPT-4-32K不同,此模型不会收到更新,并将在新版本发布 3 个月后弃用。基于gpt-4-32k /// - [Obsolete("该模型暂时不可使用", true)] gpt432k0613, /// /// 功能最强大的 GPT-3.5 型号,针对聊天进行了优化,成本仅为 .将在发布 1 周后使用最新的模型迭代进行更新。 @@ -128,6 +132,18 @@ public static string GetModelString(this ChatGPTModel chatGPTModel) { switch (chatGPTModel) { + case ChatGPTModel.gpt4: + return "gpt-4"; + case ChatGPTModel.gpt4turbo: + return "gpt-4-1106-preview"; + case ChatGPTModel.gpt4turbovision: + return "gpt-4-vision-preview"; + case ChatGPTModel.gpt40613: + return "gpt-4-0613"; + case ChatGPTModel.gpt432k: + return "gpt-4-32k"; + case ChatGPTModel.gpt432k0613: + return "gpt-4-32k-0613"; case ChatGPTModel.gpt3point5turbo: return "gpt-3.5-turbo"; case ChatGPTModel.gpt3point5turbo16k: diff --git "a/XFE\345\220\204\347\261\273\346\213\223\345\261\225/XFE\345\220\204\347\261\273\346\213\223\345\261\225.csproj" "b/XFE\345\220\204\347\261\273\346\213\223\345\261\225/XFE\345\220\204\347\261\273\346\213\223\345\261\225.csproj" index 3b171d4..4c08423 100644 --- "a/XFE\345\220\204\347\261\273\346\213\223\345\261\225/XFE\345\220\204\347\261\273\346\213\223\345\261\225.csproj" +++ "b/XFE\345\220\204\347\261\273\346\213\223\345\261\225/XFE\345\220\204\347\261\273\346\213\223\345\261\225.csproj" @@ -57,12 +57,14 @@ + +