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

            牽著老婆滿街逛

            嚴以律己,寬以待人. 三思而后行.
            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ò)封包解包

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




            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ù)包的標識符,這就和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.當客戶端收到了5個數(shù)據(jù)包之后,就主動和服務(wù)器斷開連接,這個是硬代碼;
            3.阻塞套接字其實沒有必要這樣處理數(shù)據(jù)包,主要應(yīng)用在非阻塞的套接字上;
            4.此段代碼只支持POD數(shù)據(jù),不支持變長的情況;
            5.各平臺下字節(jié)對齊方式不一樣,如Windows下默認字節(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 楊粼波 閱讀(17681) 評論(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ù)……,這些不一致都會導致錯誤的結(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é)議描述語言,對于跨語言的情況下,是非常好用的。

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

            欧美精品久久久久久久自慰| 精品国产一区二区三区久久久狼| 久久免费的精品国产V∧| 久久久久亚洲AV无码专区首JN| 亚洲а∨天堂久久精品| 人人狠狠综合久久亚洲婷婷| 青青草原1769久久免费播放| 久久夜色精品国产噜噜亚洲AV| 久久精品国产亚洲AV蜜臀色欲| 久久成人小视频| 99久久这里只精品国产免费| 亚洲精品WWW久久久久久| 久久青青草原精品国产不卡| 日本久久久久久久久久| 久久人人爽人人爽人人AV | 天天久久狠狠色综合| AV狠狠色丁香婷婷综合久久| 国产福利电影一区二区三区,免费久久久久久久精 | 国产精品免费久久久久久久久| 久久久综合香蕉尹人综合网| 国内精品伊人久久久久777| 久久人妻无码中文字幕| 少妇无套内谢久久久久| 久久九九有精品国产23百花影院| 26uuu久久五月天| 久久996热精品xxxx| 久久久久久久久66精品片| 成人免费网站久久久| 精品国产热久久久福利| 一本综合久久国产二区| 人妻无码αv中文字幕久久琪琪布 人妻无码久久一区二区三区免费 人妻无码中文久久久久专区 | 色综合久久精品中文字幕首页| 久久久久亚洲精品日久生情| 国产99久久久国产精免费| 色狠狠久久AV五月综合| 狠狠久久亚洲欧美专区| 亚洲AV无码久久| 91精品国产91久久久久久| 午夜精品久久久久| 久久本道久久综合伊人| 99久久精品国产一区二区三区|