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

时间:2021-03-03 11:03:40

这是一个网上的代码;下面列出资料并简略分析代码;


TCP/IP 网络数据封包和解包 
.
 
TCP/IP 网络数据以流的方式传输,数据流是由包组成,如何判定接收方收到的包是否是一个完整的包就要在发送时对包进行处理,这就是封包技术,将包处理成包头,包体
 
包头是包的开始标记,整个包的大小就是包的结束标记。接收方只要按同样的方式解包即可,下面是一个网络服务端和客户端程序代码。
 
客户端和服务端共享的文件:(数据包的定义) 


01.#pragma once  
02.  
03.  
04.#define NET_PACKET_DATA_SIZE 1024   
05.#define NET_PACKET_SIZE (sizeof(NetPacketHeader) + NET_PACKET_DATA_SIZE) * 10  
06.  
07.  
08./// 网络数据包包头  
09.struct NetPacketHeader  
10.{  
11.    unsigned short      wDataSize;  ///< 数据包大小,包含封包头和封包数据大小  
12.    unsigned short      wOpcode;    ///< 操作码  
13.};  
14.  
15./// 网络数据包  
16.struct NetPacket  
17.{  
18.    NetPacketHeader     Header;                         ///< 包头  
19.    unsigned char       Data[NET_PACKET_DATA_SIZE];     ///< 数据  
20.};  
21.  
22.  
23.  
24.//////////////////////////////////////////////////////////////////////////  
25.  
26.  
27./// 网络操作码  
28.enum eNetOpcode  
29.{  
30.    NET_TEST1           = 1,  
31.};  
32.  
33./// 测试1的网络数据包定义  
34.struct NetPacket_Test1  
35.{  
36.    int     nIndex;  
37.    char name[20];  
38.    char sex[20];  
39.    int age;  
40.    char    arrMessage[512];  
41.};  




服务端:




[cpp] view plaincopyprint?
01.#pragma once  
02.  
03.class TCPServer  
04.{  
05.public:  
06.    TCPServer();  
07.    virtual ~TCPServer();  
08.  
09.public:  
10.    void run();  
11.  
12.    /// 接受客户端接入  
13.    void acceptClient();  
14.  
15.    /// 关闭客户端  
16.    void closeClient();  
17.  
18.    /// 发送数据  
19.    bool SendData(unsigned short nOpcode, const char* pDataBuffer, const unsigned int& nDataSize);  
20.  
21.private:  
22.    SOCKET      mServerSocket;  ///< 服务器套接字句柄  
23.    sockaddr_in mServerAddr;    ///< 服务器地址  
24.  
25.    SOCKET      mAcceptSocket;  ///< 接受的客户端套接字句柄  
26.    sockaddr_in mAcceptAddr;    ///< 接收的客户端地址  
27.  
28.    char        m_cbSendBuf[NET_PACKET_SIZE];  
29.};  



[cpp] view plaincopyprint?
01.#include "stdafx.h"  
02.  
03.  
04.TCPServer::TCPServer()  
05.: mServerSocket(INVALID_SOCKET)  
06.{  
07.    // 创建套接字  
08.    mServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);  
09.    if (mServerSocket == INVALID_SOCKET)  
10.    {  
11.        std::cout << "创建套接字失败!" << std::endl;  
12.        return;  
13.    }  
14.  
15.    // 填充服务器的IP和端口号  
16.    mServerAddr.sin_family      = AF_INET;  
17.    mServerAddr.sin_addr.s_addr = INADDR_ANY;  
18.    mServerAddr.sin_port        = htons((u_short)SERVER_PORT);  
19.  
20.    // 绑定IP和端口  
21.    if ( ::bind(mServerSocket, (sockaddr*)&mServerAddr, sizeof(mServerAddr)) == SOCKET_ERROR)  
22.    {  
23.        std::cout << "绑定IP和端口失败!" << std::endl;  
24.        return;  
25.    }  
26.  
27.    // 监听客户端请求,最大同时连接数设置为10.  
28.    if ( ::listen(mServerSocket, SOMAXCONN) == SOCKET_ERROR)  
29.    {  
30.        std::cout << "监听端口失败!" << std::endl;  
31.        return;  
32.    }  
33.  
34.    std::cout << "启动TCP服务器成功!" << std::endl;  
35.}  
36.  
37.TCPServer::~TCPServer()  
38.{  
39.    ::closesocket(mServerSocket);  
40.    std::cout << "关闭TCP服务器成功!" << std::endl;  
41.}  
42.  
43.void TCPServer::run()  
44.{  
45.    // 接收客户端的连接  
46.    acceptClient();  
47.  
48.    int nCount = 0;  
49.    for (;;)  
50.    {  
51.        if (mAcceptSocket == INVALID_SOCKET)   
52.        {  
53.            std::cout << "客户端主动断开了连接!" << std::endl;  
54.            break;  
55.        }  
56.  
57.        // 发送数据包  
58.        NetPacket_Test1 msg;//消息类型  
59.        msg.nIndex = nCount;  
60.        msg.age=23;  
61.        strncpy(msg.arrMessage, "北京市朝阳区", sizeof(msg.arrMessage) );  
62.        strncpy(msg.name, "天策", sizeof(msg.name) );  
63.        strncpy(msg.sex, "男", sizeof(msg.sex) );  
64.  
65.        bool bRet = SendData(NET_TEST1, (const char*)&msg, sizeof(msg));//强制类型转换为字符串类型  
66.        if (bRet)  
67.        {  
68.            std::cout << "发送数据成功!" << std::endl;  
69.        }  
70.        else  
71.        {  
72.            std::cout << "发送数据失败!" << std::endl;  
73.            break;  
74.        }  
75.  
76.        ++nCount;  
77.    }  
78.}  
79.  
80.void TCPServer::closeClient()  
81.{  
82.    // 判断套接字是否有效  
83.    if (mAcceptSocket == INVALID_SOCKET) return;  
84.  
85.    // 关闭客户端套接字  
86.    ::closesocket(mAcceptSocket);  
87.    std::cout << "客户端套接字已关闭!" << std::endl;  
88.}  
89.  
90.void TCPServer::acceptClient()  
91.{  
92.    // 以阻塞方式,等待接收客户端连接  
93.    int nAcceptAddrLen = sizeof(mAcceptAddr);  
94.    mAcceptSocket = ::accept(mServerSocket, (struct sockaddr*)&mAcceptAddr, &nAcceptAddrLen);  
95.    std::cout << "接受客户端IP:" << inet_ntoa(mAcceptAddr.sin_addr) << std::endl;  
96.}  
97.  
98.bool TCPServer::SendData( unsigned short nOpcode, const char* pDataBuffer, const unsigned int& nDataSize )  
99.{  
100.    NetPacketHeader* pHead = (NetPacketHeader*) m_cbSendBuf;  
101.    pHead->wOpcode = nOpcode;//操作码  
102.  
103.    // 数据封包  
104.    if ( (nDataSize > 0) && (pDataBuffer != 0) )  
105.    {  
106.        CopyMemory(pHead+1, pDataBuffer, nDataSize);   
107.    }  
108.  
109.    // 发送消息  
110.    const unsigned short nSendSize = nDataSize + sizeof(NetPacketHeader);//包的大小事发送数据的大小加上包头大小  
111.    pHead->wDataSize = nSendSize;//包大小  
112.    int ret = ::send(mAcceptSocket, m_cbSendBuf, nSendSize, 0);  
113.    return (ret > 0) ? true : false;  
114.}  


[cpp] view plaincopyprint?
01.// testTCPServer.cpp : 定义控制台应用程序的入口点。  
02.//  
03.  
04.#include "stdafx.h"  
05.  
06.  
07.  
08.int _tmain(int argc, _TCHAR* argv[])  
09.{  
10.    TCPServer server;  
11.    server.run();  
12.  
13.    system("pause");  
14.    return 0;  
15.}  




客户端:

[cpp] view plaincopyprint?
01.<span style="font-size: 14px;">#pragma once  
02.  
03.class TCPClient  
04.{  
05.public:  
06.    TCPClient();  
07.    virtual ~TCPClient();  
08.  
09.public:  
10.    /// 主循环  
11.    void run();  
12.  
13.    /// 处理网络消息  
14.    bool OnNetMessage(const unsigned short& nOpcode,   
15.        const char* pDataBuffer, unsigned short nDataSize);  
16.  
17.    bool OnNetPacket(NetPacket_Test1* pMsg);  
18.  
19.private:  
20.    SOCKET              mServerSocket;  ///< 服务器套接字句柄  
21.    sockaddr_in         mServerAddr;    ///< 服务器地址  
22.  
23.    char                m_cbRecvBuf[NET_PACKET_SIZE];  
24.    char                m_cbDataBuf[NET_PACKET_SIZE];  
25.    int                 m_nRecvSize;  
26.};  
27.</span>  


 

[cpp] view plaincopyprint?
01.#include "stdafx.h"  
02.  
03.  
04.  
05.TCPClient::TCPClient()  
06.{  
07.    memset( m_cbRecvBuf, 0, sizeof(m_cbRecvBuf) );  
08.    m_nRecvSize = 0;  
09.  
10.    // 创建套接字  
11.    mServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);  
12.    if (mServerSocket == INVALID_SOCKET)  
13.    {  
14.        std::cout << "创建套接字失败!" << std::endl;  
15.        return;  
16.    }  
17.  
18.    // 填充服务器的IP和端口号  
19.    mServerAddr.sin_family      = AF_INET;  
20.    mServerAddr.sin_addr.s_addr = inet_addr(SERVER_IP);  
21.    mServerAddr.sin_port        = htons((u_short)SERVER_PORT);  
22.  
23.    // 连接到服务器  
24.    if ( ::connect(mServerSocket, (struct sockaddr*)&mServerAddr, sizeof(mServerAddr)))  
25.    {  
26.        ::closesocket(mServerSocket);  
27.        std::cout << "连接服务器失败!" << std::endl;  
28.        return;   
29.    }  
30.}  
31.  
32.TCPClient::~TCPClient()  
33.{  
34.    ::closesocket(mServerSocket);  
35.}  
36.  
37.void TCPClient::run()  
38.{  
39.    int nCount = 0;  
40.    for (;;)  
41.    {  
42.        // 接收数据  
43.        int nRecvSize = ::recv(mServerSocket,  
44.            m_cbRecvBuf+m_nRecvSize,   
45.            sizeof(m_cbRecvBuf)-m_nRecvSize, 0);  
46.        if (nRecvSize <= 0)  
47.        {  
48.            std::cout << "服务器主动断开连接!" << std::endl;  
49.            break;  
50.        }  
51.  
52.        // 保存已经接收数据的大小  
53.        m_nRecvSize += nRecvSize;  
54.  
55.        // 接收到的数据够不够一个包头的长度  
56.        while (m_nRecvSize >= sizeof(NetPacketHeader))//已经收到一个完整的包,如果没用收到一个完整的包,此处循环不执行,继续下一轮循环  
57.        {  
58.            // 收够5个包,主动与服务器断开  
59.            if (nCount >= 5)  
60.            {  
61.                ::closesocket(mServerSocket);  
62.                break;  
63.            }  
64.  
65.            // 读取包头  
66.            NetPacketHeader* pHead = (NetPacketHeader*) (m_cbRecvBuf);  
67.            const unsigned short nPacketSize = pHead->wDataSize;  
68.  
69.            // 判断是否已接收到足够一个完整包的数据  
70.            if (m_nRecvSize < nPacketSize)  
71.            {  
72.                // 还不够拼凑出一个完整包  
73.                break;  
74.            }  
75.  
76.            // 拷贝到数据缓存  
77.            CopyMemory(m_cbDataBuf, m_cbRecvBuf, nPacketSize);  
78.  
79.            // 从接收缓存移除  
80.            MoveMemory(m_cbRecvBuf, m_cbRecvBuf+nPacketSize, m_nRecvSize);  
81.            m_nRecvSize -= nPacketSize;  
82.  
83.            // 解密数据,以下省略一万字  
84.            // ...  
85.  
86.            // 分派数据包,让应用层进行逻辑处理  
87.            pHead = (NetPacketHeader*) (m_cbDataBuf);  
88.            const unsigned short nDataSize = nPacketSize - (unsigned short)sizeof(NetPacketHeader);  
89.            OnNetMessage(pHead->wOpcode, m_cbDataBuf+sizeof(NetPacketHeader), nDataSize);  
90.  
91.            ++nCount;  
92.        }  
93.    }  
94.  
95.    std::cout << "已经和服务器断开连接!" << std::endl;  
96.}  
97.  
98.bool TCPClient::OnNetMessage( const unsigned short& nOpcode,   
99.                             const char* pDataBuffer, unsigned short nDataSize )  
100.{  
101.    switch (nOpcode)  
102.    {  
103.    case NET_TEST1:  
104.        {  
105.            NetPacket_Test1* pMsg = (NetPacket_Test1*) pDataBuffer;  
106.            return OnNetPacket(pMsg);  
107.        }  
108.        break;  
109.  
110.    default:  
111.        {  
112.            std::cout << "收取到未知网络数据包:" << nOpcode << std::endl;  
113.            return false;  
114.        }  
115.        break;  
116.    }  
117.}  
118.  
119.bool TCPClient::OnNetPacket( NetPacket_Test1* pMsg )  
120.{  
121.    std::cout << "索引:" << pMsg->nIndex << "  字符串:" << pMsg->arrMessage <<"name:"<<pMsg->name<<"sex:"<<pMsg->sex<<"age:"<<pMsg->age<< std::endl;  
122.    return true;  
123.}  


[cpp] view plaincopyprint?
01.#include "stdafx.h"  
02.  
03.  
04.int _tmain(int argc, _TCHAR* argv[])  
05.{  
06.    TCPClient client;  
07.    client.run();  
08.  
09.    system("pause");  
10.    return 0;  
11.}  




下面分析一下其代码;
首先这是一个VC++程序;因为,_tmain是VC++下的类似于C的main的东东;


首先定义包头结构体,包结构体,操作码枚举;


首先运行客户端;在客户端的构造函数中,分配接收缓冲区,创建套接字,连接服务器;
run()函数运行,接收并保存包,调用OnNetMessage()进行处理;
OnNetMessage()中,如果nOpcode是NET_TEST1,调用OnNetPacket();
OnNetPacket()中输出包的内容;


服务端的构造函数,创建套接字,绑定IP和端口,监听客户端请求;
run()中,接收客户端连接,发送数据包;
服务端也是一个单独的控制台应用程序;单独启动;