您的位置:首页 > 理论基础 > 计算机网络

TCP/IP 网络数据封包和解包

2016-01-05 12:01 543 查看
TCP/IP 网络数据以流的方式传输,数据流是由包组成,如何判定接收方收到的包是否是一个完整的包就要在发送时对包进行处理,这就是封包技术,将包处理成包头,包体。
包头是包的开始标记,整个包的大小就是包的结束标记。接收方只要按同样的方式解包即可,下面是我在网上搜罗的一个网络服务端和客户端程序代码,整理了下。


服务器代码

common.h

#pragma once

#define NET_PACKET_DATA_SIZE 1024
#define NET_PACKET_SIZE (sizeof(NetPacketHeader) + NET_PACKET_DATA_SIZE) * 10
#define SERVER_PORT 6000

/// 网络数据包包头
struct NetPacketHeader
{
unsigned short      wDataSize;  ///< 数据包大小,包含封包头和封包数据大小
unsigned short      wOpcode;    ///< 操作码
};

/// 网络数据包
struct NetPacket
{
NetPacketHeader     Header;                         ///< 包头
unsigned char       Data[NET_PACKET_DATA_SIZE];     ///< 数据
};

//////////////////////////////////////////////////////////////////////////

/// 网络操作码
enum eNetOpcode
{
NET_TEST1 = 1,
};

/// 测试1的网络数据包定义
struct NetPacket_Test1
{
int  nIndex;
char name[20];
char sex[20];
int  age;
char arrMessage[512];
};


TCPServer.h

#pragma once
#include <iostream>
#include <stdlib.h>
#include <winsock2.h>
#include "common.h"

using namespace std;
#pragma comment(lib, "ws2_32.lib")

class TCPServer
{
public:
TCPServer();
virtual ~TCPServer();

public:
void run();

/// 接受客户端接入
void acceptClient();

/// 关闭客户端
void closeClient();

/// 发送数据
bool SendData(unsigned short nOpcode, const char* pDataBuffer, const unsigned int& nDataSize);

private:
/// 服务器套接字句柄
SOCKET mServerSocket;

/// 服务器地址
sockaddr_in mServerAddr;

/// 接受的客户端套接字句柄
SOCKET mAcceptSocket;

/// 接收的客户端地址
sockaddr_in mAcceptAddr;

/// 发送的数据
char m_cbSendBuf[NET_PACKET_SIZE];
};


TCPServer.cpp

#include "TCPServer.h"

TCPServer::TCPServer()
: mServerSocket(INVALID_SOCKET)
{
WORD wVersionRequested;
WSADATA wsaData;
int err;

wVersionRequested = MAKEWORD(1, 1);

err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return ;
}

if (LOBYTE(wsaData.wVersion) != 1 ||
HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup();
return ;
}

// 创建套接字
mServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (mServerSocket == INVALID_SOCKET)
{
cout << "创建套接字失败!" << endl;
return;
}

// 填充服务器的IP和端口号
mServerAddr.sin_family = AF_INET;
mServerAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
mServerAddr.sin_port = htons(SERVER_PORT);

// 绑定IP和端口
if (::bind(mServerSocket, (sockaddr*)&mServerAddr, sizeof(mServerAddr)) == SOCKET_ERROR)
{
cout << "绑定IP和端口失败!" << endl;
return;
}

// 监听客户端请求,最大同时连接数设置为10.
if (::listen(mServerSocket, SOMAXCONN) == SOCKET_ERROR)
{
cout << "监听端口失败!" << endl;
return;
}

cout << "启动TCP服务器成功!" << endl;
}

TCPServer::~TCPServer()
{
::closesocket(mServerSocket);
cout << "关闭TCP服务器成功!" << endl;
}

void TCPServer::run()
{
// 接收客户端的连接
acceptClient();

int nCount = 0;
for (;;)
{
if (mAcceptSocket == INVALID_SOCKET)
{
cout << "客户端主动断开了连接!" << endl;
break;
}

// 发送数据包
NetPacket_Test1 msg;//消息类型
msg.nIndex = nCount;
msg.age = 23;
strcpy_s(msg.arrMessage, "北京市朝阳区");
strcpy_s(msg.name, "天策");
strcpy_s(msg.sex, "男");
//strncpy(msg.name, "天策", sizeof(msg.name));
//strncpy(msg.sex, "男", sizeof(msg.sex));

bool bRet = SendData(NET_TEST1, (const char*)&msg, sizeof(msg));//强制类型转换为字符串类型
if (bRet)
{
cout << "发送数据成功!" << endl;
}
else
{
cout << "发送数据失败!" << endl;
break;
}

++nCount;
}
}

void TCPServer::closeClient()
{
// 判断套接字是否有效
if (mAcceptSocket == INVALID_SOCKET) return;

// 关闭客户端套接字
::closesocket(mAcceptSocket);
cout << "客户端套接字已关闭!" << endl;
}

void TCPServer::acceptClient()
{
// 以阻塞方式,等待接收客户端连接
int nAcceptAddrLen = sizeof(mAcceptAddr);
mAcceptSocket = ::accept(mServerSocket, (struct sockaddr*)&mAcceptAddr, &nAcceptAddrLen);
cout << "接受客户端IP:" << inet_ntoa(mAcceptAddr.sin_addr) << endl;
}

bool TCPServer::SendData(unsigned short nOpcode, const char* pDataBuffer, const unsigned int& nDataSize)
{
//pHead指向发送缓冲数组
NetPacketHeader* pHead = (NetPacketHeader*)m_cbSendBuf;//
pHead->wOpcode = nOpcode;//操作码

// 数据封包
if ((nDataSize > 0) && (pDataBuffer != 0))
{
//把将要发送的数据放到pHead指向的下一个位置(发送缓冲数组中)
CopyMemory(pHead + 1, pDataBuffer, nDataSize);
}

// 发送消息
//包的大小等于发送数据的大小加上包头大小
const unsigned short nSendSize = nDataSize + sizeof(NetPacketHeader);
pHead->wDataSize = nSendSize;//包大小
int ret = ::send(mAcceptSocket, m_cbSendBuf, nSendSize, 0);
return (ret > 0) ? true : false;
}


myMain.cpp

#include "TCPServer.h"

int main()
{
TCPServer server;
server.run();

system("pause");
return 0;
}


客户端代码

TCPClient.h

#pragma once
#include <iostream>
#include <stdlib.h>
#include <winsock2.h>
#include "common.h"

using namespace std;
#pragma comment(lib, "ws2_32.lib")

class TCPClient
{
public:
TCPClient();
virtual ~TCPClient();

public:
/// 主循环
void run();

/// 处理网络消息
bool OnNetMessage(const unsigned short& nOpcode,
const char* pDataBuffer, unsigned short nDataSize);

bool OnNetPacket(NetPacket_Test1* pMsg);

private:
// 客户端套接字句柄
SOCKET              mClientSocket;
// 服务器地址
sockaddr_in         mServerAddr;

char                m_cbRecvBuf[NET_PACKET_SIZE];
char                m_cbDataBuf[NET_PACKET_SIZE];
int                 m_nRecvSize;
};


TCPClient.cpp

#include "TCPClient.h"

TCPClient::TCPClient()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;

wVersionRequested = MAKEWORD(1, 1);

err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return ;
}

if (LOBYTE(wsaData.wVersion) != 1 ||
HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup();
return ;
}

memset(m_cbRecvBuf, 0, sizeof(m_cbRecvBuf));
m_nRecvSize = 0;

// 创建套接字
mClientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (mClientSocket == INVALID_SOCKET)
{
cout << "创建套接字失败!" << endl;
return;
}

// 填充服务器的IP和端口号
mServerAddr.sin_family = AF_INET;
mServerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
mServerAddr.sin_port = htons(SERVER_PORT);//(u_short)

// 连接到服务器
if (::connect(mClientSocket, (struct sockaddr*)&mServerAddr, sizeof(mServerAddr)))
{
::closesocket(mClientSocket);
cout << "连接服务器失败!" << endl;
return;
}
cout << "连接服务器成功!" << endl;
}

TCPClient::~TCPClient()
{
::closesocket(mClientSocket);
}

void TCPClient::run()
{
int nCount = 0;
for (;;)
{
// 接收数据
int nRecvSize = ::recv(mClientSocket ,m_cbRecvBuf + m_nRecvSize,
sizeof(m_cbRecvBuf) - m_nRecvSize, 0);
//一个完整的包接收过程中,m_nRecvSize 不断增加,保证接收的数据不超过 m_cbRecvBuf 的大小

if (nRecvSize <= 0)
{
cout << "服务器主动断开连接!" << endl;
break;
}

// 保存已经接收数据的大小
m_nRecvSize += nRecvSize;

// 接收到的数据够不够一个包头的长度
//已经收到一个完整的包,如果没用收到一个完整的包,此处循环不执行,继续下一轮循环
while (m_nRecvSize >= sizeof(NetPacketHeader))
{
// 收够5个包,主动与服务器断开
if (nCount >= 5)
{
cout << "收够5个包,主动与服务器断开!" << endl;
::closesocket(mClientSocket);
break;
}

// 读取包头
NetPacketHeader* pHead = (NetPacketHeader*)(m_cbRecvBuf);
const unsigned short nPacketSize = pHead->wDataSize;

// 判断是否已接收到足够一个完整包的数据  除包头外????
//if((m_nRecvSize - sizeof(NetPacketHeader)) < nPacketSize)
if (m_nRecvSize < nPacketSize)
{
// 还不够拼凑出一个完整包
cout << "没有接收到完整的数据!" << endl;
break;
}

// 拷贝到数据缓存
CopyMemory(m_cbDataBuf, m_cbRecvBuf, nPacketSize);//nPacketSize包括包头在内

// 从接收缓存-移除
MoveMemory(m_cbRecvBuf, m_cbRecvBuf + nPacketSize//指向数据包的末尾
, m_nRecvSize);
m_nRecvSize -= nPacketSize;

// 解密数据,以下省略一万字
// ...

// 分派数据包,让应用层进行逻辑处理
pHead = (NetPacketHeader*)(m_cbDataBuf);
const unsigned short nDataSize = nPacketSize - (unsigned short)sizeof(NetPacketHeader);
OnNetMessage(pHead->wOpcode, m_cbDataBuf + sizeof(NetPacketHeader)//m_cbDataBuf本身包括包头,为什么加上包头大小??
, nDataSize);

++nCount;
}
}

cout << "已经和服务器断开连接!" << endl;
}

bool TCPClient::OnNetMessage(const unsigned short& nOpcode,
const char* pDataBuffer, unsigned short nDataSize)
{
switch (nOpcode)
{
case NET_TEST1:
{
NetPacket_Test1* pMsg = (NetPacket_Test1*)pDataBuffer;
return OnNetPacket(pMsg);
}
break;

default:
{
cout << "收取到未知网络数据包:" << nOpcode << endl;
return false;
}
break;
}
}

bool TCPClient::OnNetPacket(NetPacket_Test1* pMsg)
{
cout << "索引:" << pMsg->nIndex << "  字符串:" << pMsg->arrMessage
<< "name:" << pMsg->name << "sex:" << pMsg->sex << "age:" << pMsg->age << endl;
return true;
}


myMain.cpp

#include "TCPClient.h"

int main()
{
TCPClient client;
client.run();

system("pause");
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: