TCP 是位元組流協議,無訊息邊界。send 多次調用的數據可能合併為一個網絡包到達(黏包),一次 send 的大數據可能被分片多次 recv 到達(半包)。應用層必須自定義協議框架,還原完整業務訊息。
// 固定長度包頭 (8 bytes 示例)
struct PacketHeader {
uint16_t opcode; // 操作碼:業務指令標識
uint32_t length; // 整包總長度(含包頭),網絡位序
uint16_t reserved; // 擴展/校驗/版本
};
// 完整封包
struct Packet {
PacketHeader header;
uint8_t body[]; // 變長業務數據
};
關鍵點:
length 必須包含包頭自身,便於接收端一次性判斷完整性。htons/htonl),跨平台兼容。bool SendPacket(SOCKET sock, uint16_t opcode, const void* data, uint32_t dataLen) {
const uint32_t headerSize = sizeof(PacketHeader);
uint32_t totalLen = headerSize + dataLen;
if (totalLen > MAX_PACKET_SIZE) return false;
// 分配發送緩衝
uint8_t* buf = (uint8_t*)malloc(totalLen);
PacketHeader* hdr = (PacketHeader*)buf;
hdr->opcode = htons(opcode);
hdr->length = htonl(totalLen);
hdr->reserved = 0;
if (dataLen > 0) memcpy(buf + headerSize, data, dataLen);
// 發送(處理發送緩衝區滿/非阻塞場景需循環 send)
int sent = send(sock, (char*)buf, totalLen, 0);
free(buf);
return sent == (int)totalLen;
}
維護每連接一個動態接收緩衝區,循環處理:
recv 到的數據 append 到緩衝區尾部。sizeof(PacketHeader),返回等待更多數據。totalLen(網絡序轉主機序)。totalLen,返回等待(半包)。body 分發業務邏輯,從緩衝區移除該包數據(memmove 或環形緩衝區)。void ServerLoop(SOCKET listenSock) {
SOCKET client = accept(listenSock, NULL, NULL);
uint8_t recvBuf[RECV_BUF_SIZE];
uint32_t dataLen = 0;
while (true) {
int r = recv(client, (char*)recvBuf + dataLen, RECV_BUF_SIZE - dataLen, 0);
if (r <= 0) break; // 斷開/錯誤
dataLen += r;
// 解包循環
while (dataLen >= sizeof(PacketHeader)) {
PacketHeader* hdr = (PacketHeader*)recvBuf;
uint32_t pktLen = ntohl(hdr->length);
if (dataLen < pktLen) break; // 半包
// 完整包到達
uint16_t opcode = ntohs(hdr->opcode);
uint8_t* body = recvBuf + sizeof(PacketHeader);
uint32_t bodyLen = pktLen - sizeof(PacketHeader);
HandleBusiness(opcode, body, bodyLen); // 業務分發
// 移除已處理包,前移剩餘數據
dataLen -= pktLen;
if (dataLen > 0) memmove(recvBuf, recvBuf + pktLen, dataLen);
}
}
closesocket(client);
}
void ClientLoop(SOCKET sock) {
// 發送 5 個測試包
for (int i = 0; i < 5; ++i) {
TestMsg msg = { .index = i };
strcpy(msg.text, "[1]Hello[2]World[3]!");
SendPacket(sock, OP_TEST, &msg, sizeof(msg));
Sleep(100);
}
// 接收回應邏輯同服務端解包循環...
}
memmove 開銷,O(1) 入隊出隊。MAX_PACKET_SIZE = 1MB)。select/poll/epoll/IOCP 支撐高併發。memcpy。TCP 封包核心在於「長度前綴 + 緩衝區狀態機」。理解黏包/半包本質,掌握動態緩衝區解析邏輯,是網絡程式設計的入門必修課。生產環境建議成熟框架,但手寫簡易版是深入理解的最佳途徑。