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

TCP/IP 网络数据以流的方式传输,数据流是由包组成,如何判定接收方收到的包是否是一个完整的包就要在发送时对包进行处理,这就是封包技术,将包处理成包头,包体

包头是包的开始标记,整个包的大小就是包的结束标记。接收方只要按同样的方式解包即可,下面是我在网上搜罗的一个网络服务端和客户端程序代码。

客户端和服务端共享的文件:(数据包的定义)


#pragma once


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


/// 网络数据包包头
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];
};


服务端:

#pragma once

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];
};


#include "stdafx.h"


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

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

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

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

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

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

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

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

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

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

		++nCount;
	}
}

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

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

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

bool TCPServer::SendData( unsigned short nOpcode, const char* pDataBuffer, const unsigned int& nDataSize )
{
	NetPacketHeader* pHead = (NetPacketHeader*) m_cbSendBuf;
	pHead->wOpcode = nOpcode;//操作码

	// 数据封包
	if ( (nDataSize > 0) && (pDataBuffer != 0) )
	{
		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;
}


// testTCPServer.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"



int _tmain(int argc, _TCHAR* argv[])
{
	TCPServer server;
	server.run();

	system("pause");
	return 0;
}



客户端:

#pragma once

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				mServerSocket;	///< 服务器套接字句柄
	sockaddr_in			mServerAddr;	///< 服务器地址

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


#include "stdafx.h"



TCPClient::TCPClient()
{
	memset( m_cbRecvBuf, 0, sizeof(m_cbRecvBuf) );
	m_nRecvSize = 0;

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

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

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

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

void TCPClient::run()
{
	int nCount = 0;
	for (;;)
	{
		// 接收数据
		int nRecvSize = ::recv(mServerSocket,
			m_cbRecvBuf+m_nRecvSize, 
			sizeof(m_cbRecvBuf)-m_nRecvSize, 0);
		if (nRecvSize <= 0)
		{
			std::cout << "服务器主动断开连接!" << std::endl;
			break;
		}

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

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

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

			// 判断是否已接收到足够一个完整包的数据
			if (m_nRecvSize < nPacketSize)
			{
				// 还不够拼凑出一个完整包
				break;
			}

			// 拷贝到数据缓存
			CopyMemory(m_cbDataBuf, m_cbRecvBuf, 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), nDataSize);

			++nCount;
		}
	}

	std::cout << "已经和服务器断开连接!" << std::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:
		{
			std::cout << "收取到未知网络数据包:" << nOpcode << std::endl;
			return false;
		}
		break;
	}
}

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

#include "stdafx.h"


int _tmain(int argc, _TCHAR* argv[])
{
	TCPClient client;
	client.run();

	system("pause");
	return 0;
}






  • 6
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值