• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            牽著老婆滿街逛

            嚴(yán)以律己,寬以待人. 三思而后行.
            GMail/GTalk: yanglinbo#google.com;
            MSN/Email: tx7do#yahoo.com.cn;
            QQ: 3 0 3 3 9 6 9 2 0 .

            最簡(jiǎn)單的TCP網(wǎng)絡(luò)封包解包

            如若描述或者代碼當(dāng)中有謬誤之處,還望指正。




            TCP為什么需要進(jìn)行封包解包?

                    TCP采用字節(jié)流的方式,即以字節(jié)為單位傳輸字節(jié)序列。那么,我們r(jià)ecv到的就是一串毫無(wú)規(guī)則的字節(jié)流。如果要讓這無(wú)規(guī)則的字節(jié)流有規(guī)則,那么,就需要我們?nèi)ザx一個(gè)規(guī)則。那便是所謂的“封包規(guī)則”。

            封包結(jié)構(gòu)是怎么樣的?
                    封包就像是信,信是由:信封、信內(nèi)容。兩部分組成。而網(wǎng)絡(luò)封包也是由兩部分組成:包頭、數(shù)據(jù)。包頭域是定長(zhǎng)的,數(shù)據(jù)域是不定長(zhǎng)的。包頭必然包含兩個(gè)信息:操作碼、包長(zhǎng)度。包頭可能還包含別的信息,這個(gè)呢就要視乎情況去定了。操作碼是該網(wǎng)絡(luò)數(shù)據(jù)包的標(biāo)識(shí)符,這就和UI里面的事件ID什么的差不多。其中,操作碼有的只有一級(jí),有的則有兩級(jí)甚至多級(jí)操作碼,這個(gè)的設(shè)計(jì)也要看情況去了,不過(guò),這些底層的東西,定好了,基本就不能動(dòng)了,就像房子都砌起來(lái)了,再去動(dòng)地基,那就歐也了。
            以下是網(wǎng)絡(luò)數(shù)據(jù)包的偽代碼:
            struct NetPacket
            {
            包頭;
            數(shù)據(jù);
            };
            以下是包頭的偽代碼:
            struct NetPacketHeader
            {
            操作碼;
            包長(zhǎng)度;
            };

            收包中存在的一個(gè)問(wèn)題(粘包,半包)
                    在現(xiàn)實(shí)的網(wǎng)絡(luò)情況中,網(wǎng)絡(luò)傳輸往往是不可靠的,因此會(huì)有丟包之類(lèi)的情況發(fā)生,對(duì)此,TCP相應(yīng)的有一個(gè)重傳的機(jī)制。對(duì)于接收者來(lái)說(shuō),它接收到的數(shù)據(jù)流中的數(shù)據(jù)有可能不是完整的數(shù)據(jù)包,或是只有一部分,或是粘著別的數(shù)據(jù)包,因此,接收者還需要對(duì)接收到的數(shù)據(jù)流的數(shù)據(jù)進(jìn)行分包。

            服務(wù)器客戶端邏輯描述
                    服務(wù)等待一個(gè)客戶端的連接,客戶端連接上了以后,服務(wù)器向客戶端發(fā)送5個(gè)數(shù)據(jù)包,客戶端接收服務(wù)器端的數(shù)據(jù)并解包然后做相應(yīng)的邏輯處理。

            需要注意的事項(xiàng)
            1.服務(wù)器客戶端是阻塞的,而不是非阻塞的套接字,這是為了簡(jiǎn)單;
            2.當(dāng)客戶端收到了5個(gè)數(shù)據(jù)包之后,就主動(dòng)和服務(wù)器斷開(kāi)連接,這個(gè)是硬代碼;
            3.阻塞套接字其實(shí)沒(méi)有必要這樣處理數(shù)據(jù)包,主要應(yīng)用在非阻塞的套接字上;
            4.此段代碼只支持POD數(shù)據(jù),不支持變長(zhǎng)的情況;
            5.各平臺(tái)下字節(jié)對(duì)齊方式不一樣,如Windows下默認(rèn)字節(jié)對(duì)齊為4,這是此方式需要注意的。


            服務(wù)器CPP代碼:

            #include 
            "stdafx.h"
            #include 
            "TCPServer.h"

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


                
            // 填充服務(wù)器的IP和端口號(hào)
                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;
                }


                
            // 監(jiān)聽(tīng)客戶端請(qǐng)求,最大同時(shí)連接數(shù)設(shè)置為10.
                if ( ::listen(mServerSocket, SOMAXCONN) == SOCKET_ERROR)
                
            {
                    std::cout 
            << "監(jiān)聽(tīng)端口失敗!" << std::endl;
                    
            return;
                }


                std::cout 
            << "啟動(dòng)TCP服務(wù)器成功!" << std::endl;
            }


            TCPServer::
            ~TCPServer()
            {
                ::closesocket(mServerSocket);
                std::cout 
            << "關(guān)閉TCP服務(wù)器成功!" << std::endl;
            }


            void TCPServer::run()
            {
                
            // 接收客戶端的連接
                acceptClient();

                
            int nCount = 0;
                
            for (;;)
                
            {
                    
            if (mAcceptSocket == INVALID_SOCKET) 
                    
            {
                        std::cout 
            << "客戶端主動(dòng)斷開(kāi)了連接!" << std::endl;
                        
            break;
                    }


                    
            // 發(fā)送數(shù)據(jù)包
                    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 
            << "發(fā)送數(shù)據(jù)成功!" << std::endl;
                    }

                    
            else
                    
            {
                        std::cout 
            << "發(fā)送數(shù)據(jù)失敗!" << std::endl;
                        
            break;
                    }


                    
            ++nCount;
                }

            }


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

                
            // 關(guān)閉客戶端套接字
                ::closesocket(mAcceptSocket);
                std::cout 
            << "客戶端套接字已關(guān)閉!" << 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;

                
            // 數(shù)據(jù)封包
                if ( (nDataSize > 0&& (pDataBuffer != 0) )
                
            {
                    CopyMemory(pHead
            +1, pDataBuffer, nDataSize);
                }


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



            客戶端CPP代碼:

            #include 
            "stdafx.h"
            #include 
            "TCPClient.h"


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

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


                
            // 填充服務(wù)器的IP和端口號(hào)
                mServerAddr.sin_family        = AF_INET;
                mServerAddr.sin_addr.s_addr    
            = inet_addr(SERVER_IP);
                mServerAddr.sin_port        
            = htons((u_short)SERVER_PORT);

                
            // 連接到服務(wù)器
                if ( ::connect(mServerSocket, (struct sockaddr*)&mServerAddr, sizeof(mServerAddr)))
                
            {
                    ::closesocket(mServerSocket);
                    std::cout 
            << "連接服務(wù)器失敗!" << std::endl;
                    
            return;    
                }

            }


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


            void TCPClient::run()
            {
                
            int nCount = 0;
                
            for (;;)
                
            {
                    
            // 接收數(shù)據(jù)
                    int nRecvSize = ::recv(mServerSocket,
                        m_cbRecvBuf
            +m_nRecvSize, 
                        
            sizeof(m_cbRecvBuf)-m_nRecvSize, 0);
                    
            if (nRecvSize <= 0)
                    
            {
                        std::cout 
            << "服務(wù)器主動(dòng)斷開(kāi)連接!" << std::endl;
                        
            break;
                    }


                    
            // 保存已經(jīng)接收數(shù)據(jù)的大小
                    m_nRecvSize += nRecvSize;

                    
            // 接收到的數(shù)據(jù)夠不夠一個(gè)包頭的長(zhǎng)度
                    while (m_nRecvSize >= sizeof(NetPacketHeader))
                    
            {
                        
            // 收夠5個(gè)包,主動(dòng)與服務(wù)器斷開(kāi)
                        if (nCount >= 5)
                        
            {
                            ::closesocket(mServerSocket);
                            
            break;
                        }


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

                        
            // 判斷是否已接收到足夠一個(gè)完整包的數(shù)據(jù)
                        if (m_nRecvSize < nPacketSize)
                        
            {
                            
            // 還不夠拼湊出一個(gè)完整包
                            break;
                        }


                        
            // 拷貝到數(shù)據(jù)緩存
                        CopyMemory(m_cbDataBuf, m_cbRecvBuf, nPacketSize);

                        
            // 從接收緩存移除
                        MoveMemory(m_cbRecvBuf, m_cbRecvBuf+nPacketSize, m_nRecvSize);
                        m_nRecvSize 
            -= nPacketSize;

                        
            // 解密數(shù)據(jù),以下省略一萬(wàn)字
                        
            // 

                        
            // 分派數(shù)據(jù)包,讓?xiě)?yīng)用層進(jìn)行邏輯處理
                        pHead = (NetPacketHeader*) (m_cbDataBuf);
                        
            const unsigned short nDataSize = nPacketSize - (unsigned short)sizeof(NetPacketHeader);
                        OnNetMessage(pHead
            ->wOpcode, m_cbDataBuf+sizeof(NetPacketHeader), nDataSize);

                        
            ++nCount;
                    }

                }


                std::cout 
            << "已經(jīng)和服務(wù)器斷開(kāi)連接!" << 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 
            << "收取到未知網(wǎng)絡(luò)數(shù)據(jù)包:" << nOpcode << std::endl;
                        
            return false;
                    }

                    
            break;
                }

            }


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



            源代碼打包下載:
            testNetPacket.rar

            posted on 2011-05-04 23:59 楊粼波 閱讀(17681) 評(píng)論(4)  編輯 收藏 引用

            評(píng)論

            # re: 最簡(jiǎn)單的TCP網(wǎng)絡(luò)封包解包 2011-05-05 10:02 百思寒

            bucuo,收藏了  回復(fù)  更多評(píng)論   

            # re: 最簡(jiǎn)單的TCP網(wǎng)絡(luò)封包解包 2011-05-05 13:38 jc_ontheroad

            嘗試一下。  回復(fù)  更多評(píng)論   

            # re: 最簡(jiǎn)單的TCP網(wǎng)絡(luò)封包解包 2011-05-05 14:30 finalday

            用sizeof(class)的方式打包是糟糕透頂?shù)闹饕狻?br>1)每臺(tái)機(jī)軟硬件平臺(tái)不一致:LE BE, 32位機(jī)器 or 64位機(jī)器, 對(duì)齊字節(jié)數(shù)……,這些不一致都會(huì)導(dǎo)致錯(cuò)誤的結(jié)果
            2)即使軟硬件平臺(tái)完全一致,class里面有指針怎么辦?有vector怎么辦?有虛函數(shù)怎么辦?這種打包解包方式對(duì)class有很多要求。但是很不幸:違反這些規(guī)則時(shí)編譯鏈接一點(diǎn)問(wèn)題都沒(méi)有,因?yàn)橹挥玫絪izeof和指針轉(zhuǎn)換,運(yùn)行起來(lái)才會(huì)把進(jìn)程崩掉。

            Anyway,敢扔代碼總是好事,比單純夸夸其談好。
            封包解包不難,但也沒(méi)這么容易,去看看Google Protocal Buffer吧。我之前參考他代碼實(shí)現(xiàn)了一套,后面發(fā)現(xiàn)完全沒(méi)必要,直接用它的就很OK了。  回復(fù)  更多評(píng)論   

            # re: 最簡(jiǎn)單的TCP網(wǎng)絡(luò)封包解包[未登錄](méi) 2011-05-05 15:40 楊粼波

            @finalday

            忘記說(shuō)明了,此種方式只支持POD類(lèi)型數(shù)據(jù),這是我描述的缺失。

            字節(jié)對(duì)齊,這個(gè)是可以指定的,問(wèn)題倒不大,比如windows可以用:
            #pragma pack(1)

            Google Protocal Buffer使用的是序列化的方式,而且提供了它自己的協(xié)議描述語(yǔ)言,對(duì)于跨語(yǔ)言的情況下,是非常好用的。

            這個(gè)文章,這段代碼是講解如何封包解包的。當(dāng)然,缺點(diǎn)是,無(wú)法應(yīng)付變長(zhǎng)的情況。這一切都只是為了“簡(jiǎn)單”:套接字用的是阻塞的,不考慮非POD數(shù)據(jù)變長(zhǎng)數(shù)據(jù)的情況。  回復(fù)  更多評(píng)論   


            只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問(wèn)   Chat2DB   管理


            久久人人爽人人爽人人片AV不 | 久久久精品2019免费观看| 久久人人爽人人爽人人片AV麻烦| 亚洲欧美一级久久精品| 久久久黄色大片| 人妻无码中文久久久久专区| 精品乱码久久久久久久| 亚洲嫩草影院久久精品| 99久久婷婷国产一区二区| 国产综合精品久久亚洲| 色婷婷久久久SWAG精品| 亚洲午夜久久久久久久久久| 99久久无码一区人妻a黑| 91久久精品电影| 亚洲色欲久久久久综合网| 亚洲AV日韩精品久久久久久久| 高清免费久久午夜精品| 久久免费视频6| 亚洲精品无码久久久久去q | 91麻豆精品国产91久久久久久| 国产激情久久久久影院| 久久婷婷色香五月综合激情 | 色综合久久久久综合99| 久久久久久久91精品免费观看| 日本强好片久久久久久AAA | .精品久久久麻豆国产精品| 国产一区二区精品久久凹凸 | 久久久久免费视频| 国内高清久久久久久| 久久亚洲精品视频| 久久久久久久久66精品片| 国产精品久久久福利| 久久精品亚洲精品国产欧美| 色偷偷88888欧美精品久久久| 国产A级毛片久久久精品毛片| 奇米影视7777久久精品人人爽| 久久99国产精品久久99| 免费精品久久天干天干| 国产成人久久久精品二区三区| 国产A三级久久精品| 国内精品久久久久影院网站 |