因為TCP是基於Stream的方式傳輸,所以會有封包沾黏或讀取不完全的問題
解決方式就是在封包最前面附帶資料長度的資訊
這個解決方法是不管什麼程式語言都能適用
在這邊我將以C#舉例,Python例子在這
開啟Visual Studio,建立兩個專案,分別是Server和Client,由Client不斷地傳送訊息給Server
我這邊使用.NET 6.0做示範
Server端
#pragma warning disable CS8618 //關掉編譯器的警告資訊
using System.Net;
using System.Net.Sockets;
using System.Text;
//設定封包格式。不一定要用struct,這邊選它只是為了讓資料結構更好看
public class PacketProtocol
{
//https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/integral-numeric-types
public int Length;//4 Bytes
public byte[] Data;//因為Length使用int型態,所以最大允許的資料數是2,147,483,647
}
public class Server
{
//使用委派和事件把接收到的資料傳遞出去
public delegate void DReceive(string Message);
public event DReceive Receive;
private Socket server;//建立Server端的開口
private Socket socket;//一旦有目標連到server的開口,就分配給這個物件
private Thread threadAccept;//因為程式會在等待連線中停住,所以需要使用Thread等非同步方式去處理
private Thread threadReceive;
private bool FLAG_RECEIVE = true;
public Server(int port, int allowNumber)
{
//InterNetwork: IPv4
//Stream: 因為TCP是串流方式傳輸,所以這邊要選擇Stream
//TCP: 建立TCP連線
server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
server.Bind(new IPEndPoint(IPAddress.Any, port));//綁定連線參數
server.Listen(allowNumber);//傳入的參數代表允許多少個目標連入
threadAccept = new Thread(Accept);
threadAccept.Start();
}
~Server()
{
Close();
}
private void Accept()
{
try
{
Console.WriteLine("Wait Connect...");
socket = server.Accept();
server.Close();//若不需要再接收其他連線則可以關掉server這個socket物件
Console.WriteLine("Connect SUCCESS");
//準備接收資料
threadReceive = new Thread(ReceiveMessage);
threadReceive.Start();
}
catch (SocketException se)
{
Console.WriteLine(se.ToString());
}
}
public void Close()
{
server.Close();
socket.Close();
FLAG_RECEIVE = false;
}
public bool IsConnected
{
get
{
if (socket == null) return false;
else return socket.Connected;
}
}
private void ReceiveMessage()
{
while(FLAG_RECEIVE)
{
if (Receive == null) continue;//只有設定Receive事件後才開始處理接收事件
PacketProtocol data = new PacketProtocol();
//先接收資料長度資訊
byte[] length = new byte[4];//因為int是4bytes大小
socket.Receive(length);
data.Length = BitConverter.ToInt32(length, 0);
//接收數據資料
data.Data = new byte[data.Length];
socket.Receive(data.Data);
//將接收的資料轉為字串後,透過事件發送出去
Receive(Encoding.UTF8.GetString(data.Data));
}
}
}
public class Process
{
public static void Main()
{
Server server = new Server(8080, 1);
server.Receive += ReceiveMessage;
Console.ReadKey();//pause
server.Receive -= ReceiveMessage;
server.Close();
}
private static void ReceiveMessage(string Message)
{
Console.WriteLine(Message);
}
}
Client端
#pragma warning disable CS8618 //關掉警告資訊
using System.Net;
using System.Net.Sockets;
using System.Text;
//設定封包格式。不一定要用struct,這邊選它只是為了讓資料結構更好看
public class PacketProtocol
{
//https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/integral-numeric-types
public int Length;//4 Bytes
public byte[] Data;//因為Length使用int型態,所以最大允許的資料數是2,147,483,647
}
public class Client
{
private Socket socket;
public Client(string ip, int port)
{
//InterNetwork: IPv4
//Stream: 因為TCP是串流方式傳輸,所以這邊要選擇Stream
//TCP: 建立TCP連線
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Console.WriteLine("Wait Connect...");
socket.Connect(ip, port);//這裡只考慮Server先開啟,再開Client的狀況
Console.WriteLine("Connect SUCCESS");
}
~Client()
{
Close();
}
public void Close()
{
socket.Close();
}
public bool IsConnected
{
get
{
if (socket == null) return false;
else return socket.Connected;
}
}
public void Send(string message)
{
//只有連線成功才開始傳遞資料
if (socket.Connected)
{
PacketProtocol packet = new PacketProtocol();
packet.Data = Encoding.UTF8.GetBytes(message);
packet.Length = packet.Data.Length;
byte[] data = PacketProtocolToBytes(packet);
socket.Send(data);
}
}
private byte[] PacketProtocolToBytes(PacketProtocol data)
{
//將封包資料轉換為Byte陣列
byte[] bytes;
using (MemoryStream ms = new MemoryStream())
{
using (BinaryWriter bw = new BinaryWriter(ms))
{
//先寫入資料長度,再寫入資料
//寫入順序錯誤會導致解析失敗
bw.Write(data.Length);
bw.Write(data.Data);
bytes = ms.ToArray();
}
}
return bytes;
}
}
public class Process
{
public static void Main()
{
Client client = new Client("127.0.0.1", 8080);
while(client.IsConnected)
{
client.Send("Hello Server!");
}
Console.ReadKey();//pause
client.Close();
}
}