Pisces.IM.Communication(持续更新...)
Pisces.IM.Communication
目录结构:
│ CommunicationFactory.cs
│ CommunicationSettings.cs
│ FileTransferFactory.cs
│
├─Messaging
│ FilePacket.cs
│ FilePacketService.cs
│ MessagePacket.cs
│ MessageService.cs
│
├─Models
│ Client.cs
│ FileTransferInfo.cs
│
│
└─Services
│ ICommunication.cs
│ IFileTransfer.cs
│
├─Communications
│ CommunicationClient.cs
│ CommunicationServer.cs
│
├─Events
│ EventHandle.cs
│
└─FileTransfer
FileTransferClient.cs
FileTransferServer.cs
-
CommunicationFactory.cs
提供Communication的实例
1 public class CommunicationFactory 2 { 3 private static ICommunication? _communication; 4 5 public static void InitCommunication(ICommunication? communication) 6 { 7 _communication = communication; 8 } 9 10 public static ICommunication? GetCommunication() 11 { 12 return _communication; 13 } 14 }
-
CommunicationSettings.cs
为Communication提供必要的配置参数,保证未正确读取到配置文件中的配置数据时提供默认的参数
View Code1 public interface ICommunicationSettings 2 { 3 string IP { get; } 4 int Port { get; } 5 int MaxListen { get; } 6 int FilePort { get; } 7 } 8 9 public class CommunicationSettings : ICommunicationSettings 10 { 11 public string IP { get; private set; } 12 13 public int Port { get; private set; } 14 15 public int MaxListen { get; private set; } 16 17 public int FilePort { get; private set; } 18 19 20 public CommunicationSettings() 21 { 22 IP = AppSettingsFactory.GetAppSettings()?.ServerIPAddress ?? "127.0.0.1"; 23 24 Port = AppSettingsFactory.GetAppSettings()?.ServerPort ?? 5000; 25 26 MaxListen = AppSettingsFactory.GetAppSettings()?.MaxListener ?? 10; 27 28 FilePort = AppSettingsFactory.GetAppSettings()?.FilePort ?? 5001; 29 } 30 }
-
FileTransferFactory.cs
提供FileTransfer的实例
1 public class FileTransferFactory 2 { 3 private static IFileTransfer? _fileTransfer; 4 5 public static void InitFileTransfer(IFileTransfer? fileTransfer) 6 { 7 _fileTransfer = fileTransfer; 8 } 9 10 public static IFileTransfer? GetFileTransfer() 11 { 12 return _fileTransfer; 13 } 14 }
Messaging
FilePacket 和 MessagePacket 设计思路是一直的所以只对其中一种说明,FilePacketService和MessagePacketService 同理
为了防止在socket传输过程中出现粘包等问题封装了一层自定义协议验证收到的数据包的完整性。不完整的数据包直接丢弃
1 public enum MessageType 2 { 3 Ping = 0, 4 Identity = 1, 5 Content = 2, 6 File = 3, 7 Image = 4, 8 Video = 5, 9 Custom = 6, 10 Offline = 7, //client offline 11 } 12 13 public struct MessagePacket 14 { 15 /// <summary> 16 /// 0x504953 17 /// </summary> 18 public byte[] MessageHeader { get; set; } 19 20 public byte[] MessagePayloadLength { get; set; } 21 22 public byte[] MessagePayload { get; set; } 23 24 /// <summary> 25 /// 0x434553 26 /// </summary> 27 public byte[] MessageEnd { get; set; } 28 } 29 30 31 public struct MessagePayload 32 { 33 34 public byte MessageType { get; set; } 35 36 public byte[] MessageFromLength { get; set; } 37 public byte[] MessageToLength { get; set; } 38 /// <summary> 39 /// MessageFrom ID 40 /// </summary> 41 public byte[] MessageFrom { get; set; } 42 /// <summary> 43 /// MessageTo ID 44 /// </summary> 45 public byte[] MessageTo { get; set; } 46 47 public byte[] MessageBodyLength { get; set; } 48 49 public byte[] MessageBody { get; set; } 50 }
数据包对Header进行解析,数据头固定字节数,这里占3个字节,如果未收到十六进制数据 "0x504953" 则认为是无效数据,直接丢弃
如解析的数据包头符合要求,将解析数据负载的长度信息MessagePayloadLength,这里设计占4个字节,当前环境下一个Int32的最大长度的数据。
根据MessagePayloadLength的长度信息,读取MessagePayload
数据包对END进行解析,数据尾固定字节数,这里占3个字节,如果未收到十六进制数据 "0x434553" 则认为上述接收的数据是无效的,直接丢弃
一个完整的数据包因以符合Header开始,以符合End标识结束。符合条件则认为接收的是一个完整的数据包

MessagePayload
则按照数据长度进行对各个部分的数据接收。目前的数据长度(如MessageFromLength,MessageToLength,MessageBodyLength)均占4个字节。
如MessageType接收一个字节的数据。
MessagePacketService
负责消息的拼接和解析
例如拼接:
1 public static byte[] Combined(MessagePayload message) 2 { 3 var packet = new MessagePacket(); 4 5 int packetsLength = 0; 6 7 packet.MessageHeader = "PIS".GetBytes(); 8 9 packet.MessagePayload = GetMessagePayload(message,out var payloadLength); 10 packet.MessagePayloadLength = BitConverter.GetBytes(payloadLength) ; 11 packet.MessageEnd = "CES".GetBytes(); 12 packetsLength += packet.MessageHeader.Length; 13 packetsLength += packet.MessagePayloadLength.Length; 14 packetsLength += packet.MessagePayload.Length; 15 packetsLength += packet.MessageEnd.Length; 16 17 var buffer = new byte[packetsLength]; 18 19 packet.MessageHeader.CopyTo(buffer,0); 20 packet.MessagePayloadLength.CopyTo(buffer, packet.MessageHeader.Length); 21 packet.MessagePayload.CopyTo(buffer, packet.MessageHeader.Length + packet.MessagePayloadLength.Length); 22 packet.MessageEnd.CopyTo(buffer, packetsLength - packet.MessageEnd.Length); 23 return buffer; 24 }
解析:
1 public static MessagePayload Resolve(byte[] data) 2 { 3 var packet = new MessagePacket(); 4 var body = new MessagePayload(); 5 6 packet.MessageHeader = data.Take(3).ToArray(); 7 packet.MessagePayloadLength = data.Skip(3).Take(4).ToArray(); 8 packet.MessagePayload = data.Skip(7).Take(BitConverter.ToInt32(packet.MessagePayloadLength, 0)).ToArray(); 9 packet.MessageEnd = data.Skip(data.Length - 3).Take(3).ToArray(); 10 11 12 13 body.MessageType = packet.MessagePayload[0]; 14 body.MessageFromLength = packet.MessagePayload.Skip(1).Take(4).ToArray(); 15 body.MessageToLength = packet.MessagePayload.Skip(5).Take(4).ToArray(); 16 var messageFromLength = BitConverter.ToInt32(body.MessageFromLength, 0); 17 var messageToLength = BitConverter.ToInt32(body.MessageToLength, 0); 18 19 body.MessageFrom = packet.MessagePayload.Skip(9).Take(messageFromLength).ToArray(); 20 body.MessageTo = packet.MessagePayload.Skip(9 + messageFromLength).Take(messageToLength).ToArray(); 21 body.MessageBodyLength = packet.MessagePayload.Skip(9 + messageFromLength + messageToLength).Take(4).ToArray(); 22 body.MessageBody = packet.MessagePayload.Skip(13 + messageFromLength + messageToLength) 23 .Take(BitConverter.ToInt32(body.MessageBodyLength)).ToArray(); 24 25 //string header = Convert.ToHexString(packet.MessageHeader); 26 //string content = Encoding.UTF8.GetString(body.MessageBody); 27 //string from = Encoding.UTF8.GetString(body.MessageFrom); 28 //string to = Encoding.UTF8.GetString(body.MessageTo); 29 //byte type = body.MessageType; 30 31 //string end = Convert.ToHexString(packet.MessageEnd); 32 33 //Console.WriteLine(content); 34 35 return body; 36 }
Services
定义了文件传输和通讯的接口与实现。目前文件的传输没有用到FTP,当前的服务端没有做任何的数据存储和数据缓存,所以使用了socket进行实时的传输。目限制为50MB作为演示
/// <summary> /// Actual please use FTP service as file upload and download, /// currently in order to reduce the pressure on the server storage, as a demo /// limited to 50MB /// </summary> public interface IFileTransfer { bool IsConnected { get; } Socket GetCurrentSocket(); bool Create(string Id); void Start(); void Stop(); void DisConnect(); void ReConnect(string id); #region events event EventHandle.ReceiveFileMessageHandle? ReceiveFileMessage; #endregion }
Communications
当前文件夹下定义了客户端和服务端的实现
CommunicationClient.cs
外部注入配置数据,为了保证socket的活跃性,定时发送心跳包,默认5分钟发送一次
1 public CommunicationClient(ICommunicationSettings communicationSettings) 2 { 3 _communicationSettings = communicationSettings; 4 _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 5 IPAddress hostIP = IPAddress.Parse(_communicationSettings.IP); 6 _remoteEndPoint = new IPEndPoint(hostIP, _communicationSettings.Port); 7 8 tokenSourceByReceive = new CancellationTokenSource(); 9 10 //default 5 min 11 double pingInterval = AppSettingsFactory.GetAppSettings()?.PingInterval ?? 1000 * 60 * 5; 12 _pingTimer = new Timer(pingInterval); 13 _pingTimer.Elapsed += PingTimer_Elapsed; 14 }
根据服务端转发的消息进行解析后进行对应的数据处理
1 private void ProcessMessage(MessagePayload messagePayload) 2 { 3 switch ((MessageType)messagePayload.MessageType) 4 { 5 case MessageType.Ping: 6 ReceiveServerPing(messagePayload); 7 break; 8 case MessageType.Identity: 9 GetIdentityMessage(messagePayload); 10 break; 11 case MessageType.Content: 12 GetContentMessage(messagePayload); 13 break; 14 case MessageType.File: 15 GetFileMessage(messagePayload); 16 break; 17 case MessageType.Image: 18 break; 19 case MessageType.Video: 20 break; 21 case MessageType.Custom: 22 break; 23 case MessageType.Offline: 24 ResponseOffline(messagePayload); 25 break; 26 default: 27 28 break; 29 30 } 31 }
CommunicationServer.cs
存放已经连接到服务端的客户端Socket,当前版本并未做强制下线
1 private void CreateClient(MessagePayload messagePayload, Socket clientSocket) 2 { 3 string from = messagePayload.MessageFrom.GetString(); 4 string msg; 5 6 if (!_clients.ContainsKey(from)) 7 { 8 _clients.Add(from, new Client() 9 { 10 Id = from, 11 CurrentSocket = clientSocket 12 }); 13 14 msg = $"Id:{from} is online..."; 15 16 } 17 else msg = $"Id:{from} is already online..."; //做强制下线 18 19 20 var body = MessageService.CreateMessagePayload(_serviceId, from, msg, 1); 21 var buffer = MessageService.Combined(body); 22 23 if (clientSocket.RemoteEndPoint != null) 24 clientSocket.SendTo(buffer, clientSocket.RemoteEndPoint); 25 26 Console.WriteLine($"[server]:{msg},to:{clientSocket.RemoteEndPoint}"); 27 }
外部注入配置数据,进行对客户端的监听和客户端的数据转发。
Events
EventHandle.cs
处理客户端收到消息,订阅事件处理即时处理消息数据
1 public class ContentMessageEventArgs 2 { 3 public string? FromId { get; set; } 4 public string? ToId { get; set;} 5 public string? Content { get; set; } 6 public MessageType MessageType { get; set; } 7 } 8 9 10 public class FileMessageEventArgs 11 { 12 public string? FromId { get; set; } 13 public string? ToId { get; set; } 14 public string? FileId { get; set; } 15 public byte[]? Buffer { get; set; } 16 } 17 18 19 20 public class EventHandle 21 { 22 public delegate void ReceiveContentMessageHandle(ContentMessageEventArgs e); 23 24 public delegate void ReceiveFileMessageHandle(FileMessageEventArgs e); 25 }
FileTransfer
与Communications思路类似不在赘述
完整代码:github
浙公网安备 33010602011771号