最简单的TCP网络封包解包

简介:
TCP为什么需要进行封包解包?
        TCP采用字节流的方式,即以字节为单位传输字节序列。那么,我们recv到的就是一串毫无规则的字节流。如果要让这无规则的字节流有规则,那么,就需要我们去定义一个规则。那便是所谓的“封包规则”。

封包结构是怎么样的?
        封包就像是信,信是由:信封、信内容。两部分组成。而网络封包也是由两部分组成:包头、数据。包头域是定长的,数据域是不定长的。包头必然包含两个信息:操作码、包长度。包头可能还包含别的信息,这个呢就要视乎情况去定了。操作码是该网络数据包的标识符,这就和UI里面的事件ID什么的差不多。其中,操作码有的只有一级,有的则有两级甚至多级操作码,这个的设计也要看情况去了,不过,这些底层的东西,定好了,基本就不能动了,就像房子都砌起来了,再去动地基,那就欧也了。
以下是网络数据包的伪代码:
struct NetPacket
{
包头;
数据;
};
以下是包头的伪代码:
struct NetPacketHeader
{
操作码;
包长度;
};

收包中存在的一个问题(粘包,半包)
        在现实的网络情况中,网络传输往往是不可靠的,因此会有丢包之类的情况发生,对此,TCP相应的有一个重传的机制。对于接收者来说,它接收到的数据流中的数据有可能不是完整的数据包,或是只有一部分,或是粘着别的数据包,因此,接收者还需要对接收到的数据流的数据进行分包。

服务器客户端逻辑描述
        服务等待一个客户端的连接,客户端连接上了以后,服务器向客户端发送5个数据包,客户端接收服务器端的数据并解包然后做相应的逻辑处理。

需要注意的事项
1.服务器客户端是阻塞的,而不是非阻塞的套接字,这是为了简单;
2.当客户端收到了5个数据包之后,就主动和服务器断开连接,这个是硬代码;
3.阻塞套接字其实没有必要这样处理数据包,主要应用在非阻塞的套接字上;
4.此段代码只支持POD数据,不支持变长的情况;
5.各平台下字节对齐方式不一样,如Windows下默认字节对齐为4,这是此方式需要注意的。


服务器CPP代码:
None.gif
None.gif#include "stdafx.h"
None.gif#include "TCPServer.h"
None.gif
None.gifTCPServer::TCPServer()
None.gif: mServerSocket(INVALID_SOCKET)
ExpandedBlockStart.gif {
InBlock.gif    // 创建套接字
InBlock.gif
    mServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
InBlock.gif    if (mServerSocket == INVALID_SOCKET)
ExpandedSubBlockStart.gif    {
InBlock.gif        std::cout << "创建套接字失败!" << std::endl;
InBlock.gif        return;
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    // 填充服务器的IP和端口号
InBlock.gif
    mServerAddr.sin_family        = AF_INET;
InBlock.gif    mServerAddr.sin_addr.s_addr    = INADDR_ANY;
InBlock.gif    mServerAddr.sin_port        = htons((u_short)SERVER_PORT);
InBlock.gif
InBlock.gif    // 绑定IP和端口
InBlock.gif
    if ( ::bind(mServerSocket, (sockaddr*)&mServerAddr, sizeof(mServerAddr)) == SOCKET_ERROR)
ExpandedSubBlockStart.gif    {
InBlock.gif        std::cout << "绑定IP和端口失败!" << std::endl;
InBlock.gif        return;
ExpandedSubBlockEnd.gif    }

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

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

None.gif
None.gifTCPServer::~TCPServer()
ExpandedBlockStart.gif {
InBlock.gif    ::closesocket(mServerSocket);
InBlock.gif    std::cout << "关闭TCP服务器成功!" << std::endl;
ExpandedBlockEnd.gif}

None.gif
None.gif void TCPServer::run()
ExpandedBlockStart.gif {
InBlock.gif    // 接收客户端的连接
InBlock.gif
    acceptClient();
InBlock.gif
InBlock.gif    int nCount = 0;
InBlock.gif    for (;;)
ExpandedSubBlockStart.gif    {
InBlock.gif        if (mAcceptSocket == INVALID_SOCKET) 
ExpandedSubBlockStart.gif        {
InBlock.gif            std::cout << "客户端主动断开了连接!" << std::endl;
InBlock.gif            break;
ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif        // 发送数据包
InBlock.gif
        NetPacket_Test1 msg;
InBlock.gif        msg.nIndex = nCount;
InBlock.gif        strncpy(msg.arrMessage, "[1]你好[2]你好[3]你好", sizeof(msg.arrMessage) );
InBlock.gif        bool bRet = SendData(NET_TEST1, (const char*)&msg, sizeof(msg));
InBlock.gif        if (bRet)
ExpandedSubBlockStart.gif        {
InBlock.gif            std::cout << "发送数据成功!" << std::endl;
ExpandedSubBlockEnd.gif        }

InBlock.gif        else
ExpandedSubBlockStart.gif        {
InBlock.gif            std::cout << "发送数据失败!" << std::endl;
InBlock.gif            break;
ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif        ++nCount;
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

None.gif
None.gif void TCPServer::closeClient()
ExpandedBlockStart.gif {
InBlock.gif    // 判断套接字是否有效
InBlock.gif
    if (mAcceptSocket == INVALID_SOCKET) return;
InBlock.gif
InBlock.gif    // 关闭客户端套接字
InBlock.gif
    ::closesocket(mAcceptSocket);
InBlock.gif    std::cout << "客户端套接字已关闭!" << std::endl;
ExpandedBlockEnd.gif}

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

None.gif
None.gif bool TCPServer::SendData( unsigned  short nOpcode,  const  char* pDataBuffer,  const unsigned  int& nDataSize )
ExpandedBlockStart.gif {
InBlock.gif    NetPacketHeader* pHead = (NetPacketHeader*) m_cbSendBuf;
InBlock.gif    pHead->wOpcode = nOpcode;
InBlock.gif
InBlock.gif    // 数据封包
InBlock.gif
    if ( (nDataSize > 0) && (pDataBuffer != 0) )
ExpandedSubBlockStart.gif    {
InBlock.gif        CopyMemory(pHead+1, pDataBuffer, nDataSize);
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    // 发送消息
InBlock.gif
    const unsigned short nSendSize = nDataSize + sizeof(NetPacketHeader);
InBlock.gif    pHead->wDataSize = nSendSize;
InBlock.gif    int ret = ::send(mAcceptSocket, m_cbSendBuf, nSendSize, 0);
InBlock.gif    return (ret > 0) ? true : false;
ExpandedBlockEnd.gif}

None.gif


客户端CPP代码:
None.gif
None.gif#include "stdafx.h"
None.gif#include "TCPClient.h"
None.gif
None.gif
None.gifTCPClient::TCPClient()
ExpandedBlockStart.gif {
InBlock.gif    memset( m_cbRecvBuf, 0, sizeof(m_cbRecvBuf) );
InBlock.gif    m_nRecvSize = 0;
InBlock.gif
InBlock.gif    // 创建套接字
InBlock.gif
    mServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
InBlock.gif    if (mServerSocket == INVALID_SOCKET)
ExpandedSubBlockStart.gif    {
InBlock.gif        std::cout << "创建套接字失败!" << std::endl;
InBlock.gif        return;
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    // 填充服务器的IP和端口号
InBlock.gif
    mServerAddr.sin_family        = AF_INET;
InBlock.gif    mServerAddr.sin_addr.s_addr    = inet_addr(SERVER_IP);
InBlock.gif    mServerAddr.sin_port        = htons((u_short)SERVER_PORT);
InBlock.gif
InBlock.gif    // 连接到服务器
InBlock.gif
    if ( ::connect(mServerSocket, (struct sockaddr*)&mServerAddr, sizeof(mServerAddr)))
ExpandedSubBlockStart.gif    {
InBlock.gif        ::closesocket(mServerSocket);
InBlock.gif        std::cout << "连接服务器失败!" << std::endl;
InBlock.gif        return;    
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

None.gif
None.gifTCPClient::~TCPClient()
ExpandedBlockStart.gif {
InBlock.gif    ::closesocket(mServerSocket);
ExpandedBlockEnd.gif}

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

InBlock.gif
InBlock.gif        // 保存已经接收数据的大小
InBlock.gif
        m_nRecvSize += nRecvSize;
InBlock.gif
InBlock.gif        // 接收到的数据够不够一个包头的长度
InBlock.gif
        while (m_nRecvSize >= sizeof(NetPacketHeader))
ExpandedSubBlockStart.gif        {
InBlock.gif            // 收够5个包,主动与服务器断开
InBlock.gif
            if (nCount >= 5)
ExpandedSubBlockStart.gif            {
InBlock.gif                ::closesocket(mServerSocket);
InBlock.gif                break;
ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            // 读取包头
InBlock.gif
            NetPacketHeader* pHead = (NetPacketHeader*) (m_cbRecvBuf);
InBlock.gif            const unsigned short nPacketSize = pHead->wDataSize;
InBlock.gif
InBlock.gif            // 判断是否已接收到足够一个完整包的数据
InBlock.gif
            if (m_nRecvSize < nPacketSize)
ExpandedSubBlockStart.gif            {
InBlock.gif                // 还不够拼凑出一个完整包
InBlock.gif
                break;
ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            // 拷贝到数据缓存
InBlock.gif
            CopyMemory(m_cbDataBuf, m_cbRecvBuf, nPacketSize);
InBlock.gif
InBlock.gif            // 从接收缓存移除
InBlock.gif
            MoveMemory(m_cbRecvBuf, m_cbRecvBuf+nPacketSize, m_nRecvSize);
InBlock.gif            m_nRecvSize -= nPacketSize;
InBlock.gif
InBlock.gif            // 解密数据,以下省略一万字
InBlock.gif            
// dot.gif
InBlock.gif
InBlock.gif            
// 分派数据包,让应用层进行逻辑处理
InBlock.gif
            pHead = (NetPacketHeader*) (m_cbDataBuf);
InBlock.gif            const unsigned short nDataSize = nPacketSize - (unsigned short)sizeof(NetPacketHeader);
InBlock.gif            OnNetMessage(pHead->wOpcode, m_cbDataBuf+sizeof(NetPacketHeader), nDataSize);
InBlock.gif
InBlock.gif            ++nCount;
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    std::cout << "已经和服务器断开连接!" << std::endl;
ExpandedBlockEnd.gif}

None.gif
None.gif bool TCPClient::OnNetMessage(  const unsigned  short& nOpcode, 
None.gif                              const  char* pDataBuffer, unsigned  short nDataSize )
ExpandedBlockStart.gif {
InBlock.gif    switch (nOpcode)
ExpandedSubBlockStart.gif    {
InBlock.gif    case NET_TEST1:
ExpandedSubBlockStart.gif        {
InBlock.gif            NetPacket_Test1* pMsg = (NetPacket_Test1*) pDataBuffer;
InBlock.gif            return OnNetPacket(pMsg);
ExpandedSubBlockEnd.gif        }

InBlock.gif        break;
InBlock.gif
InBlock.gif    default:
ExpandedSubBlockStart.gif        {
InBlock.gif            std::cout << "收取到未知网络数据包:" << nOpcode << std::endl;
InBlock.gif            return false;
ExpandedSubBlockEnd.gif        }

InBlock.gif        break;
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

None.gif
None.gif bool TCPClient::OnNetPacket( NetPacket_Test1* pMsg )
ExpandedBlockStart.gif {
InBlock.gif    std::cout << "索引:" << pMsg->nIndex << "  字符串:" << pMsg->arrMessage << std::endl;
InBlock.gif    return true;
ExpandedBlockEnd.gif}

None.gif


源代码打包下载:
testNetPacket.rar
目录
相关文章
|
29天前
|
移动开发 网络协议 安全
网络面试题:什么是 TCP/IP?
网络面试题:什么是 TCP/IP?
43 0
网络面试题:什么是 TCP/IP?
|
30天前
|
监控 负载均衡 网络协议
TCP重传与超时机制:解锁网络性能之秘
TCP重传与超时机制:解锁网络性能之秘
58 0
|
2天前
|
网络协议 Java API
深度剖析:Java网络编程中的TCP/IP与HTTP协议实践
【4月更文挑战第17天】Java网络编程重在TCP/IP和HTTP协议的应用。TCP提供可靠数据传输,通过Socket和ServerSocket实现;HTTP用于Web服务,常借助HttpURLConnection或Apache HttpClient。两者结合,构成网络服务基础。Java有多种高级API和框架(如Netty、Spring Boot)简化开发,助力高效、高并发的网络通信。
|
11天前
|
网络协议 安全 网络性能优化
|
21天前
|
缓存 网络协议 数据库连接
【底层服务/编程功底系列】「网络通信体系」深入探索和分析TCP协议的运输连接管理的核心原理和技术要点
【底层服务/编程功底系列】「网络通信体系」深入探索和分析TCP协议的运输连接管理的核心原理和技术要点
20 0
|
30天前
|
网络协议 算法 Linux
探索TCP状态机之旅:发现网络连接的生命周期与神秘魅力
探索TCP状态机之旅:发现网络连接的生命周期与神秘魅力
67 0
|
1月前
|
网络协议 网络性能优化
网络面试题:TCP和UDP的区别
网络面试题:TCP和UDP的区别
25 0
|
1月前
|
网络协议 Python
Python网络编程实现TCP和UDP连接
Python网络编程实现TCP和UDP连接
27 0
|
1月前
|
缓存 网络协议 安全
计算机网络:传输层(TCP详解)
计算机网络:传输层(TCP详解)
|
1月前
|
网络协议 Java
Java基于TCP的网络编程
Java基于TCP的网络编程