之前自己寫了一篇介紹TCP的一些常用的功能介紹和特徵,並且用程式碼做了示例,最終開發了一個EasyTcp4Net的TCP工具庫,其最大的特色就是使用了微軟提供的高效能庫中的一些資料結構來處理TCP資料。
最近辭職待業在家,也沒啥事做,就利用自己寫的TCP通訊庫基礎上開發了一個示例的聊天程式,功能包括,文字傳送,圖片傳送,斷線重連,訊息傳送成功確認,訊息傳送失敗提示等。
示例
發訊息給自己
收到訊息
傳送圖片
訊息傳送中
重連中
傳送失敗
資料包結構以及拆包
定義資料包結構
資料包結構定義了每次傳送一個資料的完整的資料結構,我們將包體長度定義在包頭中來解決粘包和斷包的問題。
資料包我們採用了簡單的序列化成byte陣列的方式來傳送。
[StructLayout(LayoutKind.Sequential)]
public class Message<TBody> : IMsssage<TBody> where TBody : Packet
{
//資料包包體長度 4位元組
public int BodyLength { get; private set; }
//訊息型別 4位元組
public MessageType MessageType { get; set; }
public TBody? Body { get; set; }
private Message<TBody> Deserialize(byte[] bodyData)
{
var bodyStr = System.Text.Encoding.Default.GetString(bodyData);
Body = JsonSerializer.Deserialize<TBody>(bodyStr);
return this;
}
public static Message<TBody> FromBytes(ReadOnlyMemory<byte> data)
{
Message<TBody> packet = new Message<TBody>();
packet.BodyLength = BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 4).Span);
packet.MessageType = (MessageType)BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4).Span);
packet.Deserialize(data.Slice(8, packet.BodyLength).Span.ToArray());
return packet;
}
public byte[] Serialize()
{
var Length = 4 + 4;
var bodyArray = System.Text.Encoding.Default.GetBytes(JsonSerializer.Serialize(Body));
BodyLength = bodyArray.Length;
Length += bodyArray.Length;
byte[] result = new byte[Length];
result.AddInt32(0, bodyArray.Length);
result.AddInt32(4, (int)MessageType);
Buffer.BlockCopy(bodyArray, 0, result, 8, bodyArray.Length);
return result;
}
public TBody GetBody()
{
return Body;
}
}
public interface IMsssage <out TBody> where TBody : Packet
{
public TBody GetBody();
}
我們在服務端和客戶端根據我們定義的資料結構,來呼叫EasyTcp4Net提供的固定包頭來解析資料包
_easyTcpClient.SetReceiveFilter(new FixedHeaderPackageFilter(8, 0, 4, false));
文字/圖片傳送
我們可以定義訊息基類,再擴充兩個訊息類,一個文字訊息,一個圖片訊息
public class SendMessagePacket : Packet
{
public string MessageId { get; set; } = Guid.NewGuid().ToString();
public string From { get; set; }
public string To { get; set; }
}
圖片訊息
public class SendImageMessagePacket : SendMessagePacket
{
public byte[] Data { get; set; }
public string FileName { get; set; }
}
文字訊息
public class SendTextMessagePacket : SendMessagePacket
{
public string Text { get; set; }
}
我們還需要在介面中增加相關的文字和圖片的ViewModel
傳送訊息的時候,傳送者可以立刻將訊息新增到聊天介面,然後等待收到自己傳送的訊息從服務端發來的時候,根據狀態判斷訊息是否傳送成功,等待的時候可以將訊息設定傳送中的介面狀態顯示,這種傳送訊息邏輯和微信基本一致。
斷線處理
利用EasyTcp4Net提供的斷線的事件,可以非常方便的在服務端知道客戶端突然斷開了,或者在客戶端知道和服務端連線斷開了。
客戶端
_easyTcpClient.OnDisConnected += async (obj, e) =>
{
Title = Title + _disConnectTip;
await ReConnectAsync();
};
主要是觸發了重連的機制。
服務端
_easyTcpServer.OnClientConnectionChanged += (obj, e) =>
{
if (e.Status == ConnectsionStatus.DisConnected)
{
_accounts.TryRemove(e.ClientSession.SessionId, out var account);
}
};
主要是將該使用者從線上列表中移除。
總結
總體來說做一個聊天軟體需要考慮的細節比較多。
示例地址:https://github.com/BruceQiu1996/EasyChat