TCP为什么需要进行封包解包?
TCP采用字节流的方式,即以字节为单位传输字节序列。那么,我们recv到的就是一串毫无规则的字节流。如果要让这无规则的字节流有规则,那么,就需要我们去定义一个规则。那便是所谓的“封包规则”。
封包结构是怎么样的?
封包就像是信,信是由:信封、信内容。两部分组成。而网络封包也是由两部分组成:包头、数据。包头域是定长的,数据域是不定长的。包头必然包含两个信息:操作码、包长度。包头可能还包含别的信息,这个呢就要视乎情况去定了。操作码是该网络数据包的标识符,这就和UI里面的事件ID什么的差不多。其中,操作码有的只有一级,有的则有两级甚至多级操作码,这个的设计也要看情况去了,不过,这些底层的东西,定好了,基本就不能动了,就像房子都砌起来了,再去动地基,那就欧也了。
以下是网络数据包的伪代码:
1 2 3 4 5 |
struct NetPacket { 包头; 数据; }; |
以下是包头的伪代码:
1 2 3 4 5 |
struct NetPacketHeader { 操作码; 包长度; }; |
收包中存在的一个问题(粘包,半包)
在现实的网络情况中,网络传输往往是不可靠的,因此会有丢包之类的情况发生,对此,TCP相应的有一个重传的机制。对于接收者来说,它接收到的数据流中的数据有可能不是完整的数据包,或是只有一部分,或是粘着别的数据包,因此,接收者还需要对接收到的数据流的数据进行分包。
服务器客户端逻辑描述
服务等待一个客户端的连接,客户端连接上了以后,服务器向客户端发送5个数据包,客户端接收服务器端的数据并解包然后做相应的逻辑处理。
需要注意的事项
1.服务器客户端是阻塞的,而不是非阻塞的套接字,这是为了简单;
2.当客户端收到了5个数据包之后,就主动和服务器断开连接,这个是硬代码;
3.阻塞套接字其实没有必要这样处理数据包,主要应用在非阻塞的套接字上;
4.此段代码只支持POD数据,不支持变长的情况;
5.各平台下字节对齐方式不一样,如Windows下默认字节对齐为4,这是此方式需要注意的。
服务器CPP代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
#include "stdafx.h" #include "TCPServer.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; strncpy(msg.arrMessage, "[1]你好[2]你好[3]你好", sizeof(msg.arrMessage) ); 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; } |
客户端CPP代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
#include "stdafx.h" #include "TCPClient.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 << std::endl; return true; } |
源代码打包下载:
testnetpacket.zip
发表评论
要发表评论,您必须先登录。