• <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 .

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

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




            TCP為什么需要進行封包解包?

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

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

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

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

            需要注意的事項
            1.服務(wù)器客戶端是阻塞的,而不是非阻塞的套接字,這是為了簡單;
            2.當(dāng)客戶端收到了5個數(shù)據(jù)包之后,就主動和服務(wù)器斷開連接,這個是硬代碼;
            3.阻塞套接字其實沒有必要這樣處理數(shù)據(jù)包,主要應(yīng)用在非阻塞的套接字上;
            4.此段代碼只支持POD數(shù)據(jù),不支持變長的情況;
            5.各平臺下字節(jié)對齊方式不一樣,如Windows下默認(rèn)字節(jié)對齊為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和端口號
                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)聽客戶端請求,最大同時連接數(shù)設(shè)置為10.
                if ( ::listen(mServerSocket, SOMAXCONN) == SOCKET_ERROR)
                
            {
                    std::cout 
            << "監(jiān)聽端口失敗!" << std::endl;
                    
            return;
                }


                std::cout 
            << "啟動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 
            << "客戶端主動斷開了連接!" << 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和端口號
                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ù)器主動斷開連接!" << std::endl;
                        
            break;
                    }


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

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


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

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


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

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

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

                        
            // 分派數(shù)據(jù)包,讓應(yīng)用層進行邏輯處理
                        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ù)器斷開連接!" << 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 楊粼波 閱讀(17699) 評論(4)  編輯 收藏 引用

            評論

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

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

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

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

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

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

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

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

            @finalday

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

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

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

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


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


            国产亚洲美女精品久久久| 18禁黄久久久AAA片| 久久婷婷午色综合夜啪| 久久久九九有精品国产| 久久综合亚洲鲁鲁五月天| 国产亚洲精午夜久久久久久| 久久久综合九色合综国产| 亚洲国产精品无码久久一线| 亚洲AV伊人久久青青草原| 97精品国产97久久久久久免费| 狠狠88综合久久久久综合网| 免费精品国产日韩热久久| 久久久艹| 久久国产香蕉视频| 欧美午夜A∨大片久久| 九九热久久免费视频| 久久99精品久久久久久秒播 | 国产V综合V亚洲欧美久久| 亚洲精品国产字幕久久不卡| 99久久免费国产精品特黄| 久久久国产视频| 亚洲日韩中文无码久久| 亚洲人成网亚洲欧洲无码久久| 中文字幕乱码人妻无码久久| 精品综合久久久久久97| 久久久久人妻一区二区三区vr| 久久久久亚洲AV无码麻豆| 精品乱码久久久久久久| 亚洲成色999久久网站| 精品国产热久久久福利| 性做久久久久久久久老女人| 久久精品视频一| 日本强好片久久久久久AAA| 韩国免费A级毛片久久| 国产香蕉97碰碰久久人人| 亚洲精品WWW久久久久久| 亚洲中文久久精品无码ww16| 狠色狠色狠狠色综合久久 | 99久久国产亚洲高清观看2024| 欧美日韩精品久久久免费观看| 久久人人爽人人爽人人片av麻烦|