• <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網絡封包解包

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




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

                    TCP采用字節流的方式,即以字節為單位傳輸字節序列。那么,我們recv到的就是一串毫無規則的字節流。如果要讓這無規則的字節流有規則,那么,就需要我們去定義一個規則。那便是所謂的“封包規則”。

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

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

            服務器客戶端邏輯描述
                    服務等待一個客戶端的連接,客戶端連接上了以后,服務器向客戶端發送5個數據包,客戶端接收服務器端的數據并解包然后做相應的邏輯處理。

            需要注意的事項
            1.服務器客戶端是阻塞的,而不是非阻塞的套接字,這是為了簡單;
            2.當客戶端收到了5個數據包之后,就主動和服務器斷開連接,這個是硬代碼;
            3.阻塞套接字其實沒有必要這樣處理數據包,主要應用在非阻塞的套接字上;
            4.此段代碼只支持POD數據,不支持變長的情況;
            5.各平臺下字節對齊方式不一樣,如Windows下默認字節對齊為4,這是此方式需要注意的。


            服務器CPP代碼:

            #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代碼:

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


            TCPClient::TCPClient()
            {
                memset( m_cbRecvBuf, 
            0sizeof(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.rar

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

            評論

            # re: 最簡單的TCP網絡封包解包 2011-05-05 10:02 百思寒

            bucuo,收藏了  回復  更多評論   

            # re: 最簡單的TCP網絡封包解包 2011-05-05 13:38 jc_ontheroad

            嘗試一下。  回復  更多評論   

            # re: 最簡單的TCP網絡封包解包 2011-05-05 14:30 finalday

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

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

            # re: 最簡單的TCP網絡封包解包[未登錄] 2011-05-05 15:40 楊粼波

            @finalday

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

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

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

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

            精品无码久久久久久午夜| 色8激情欧美成人久久综合电| 久久久久亚洲AV无码网站| 72种姿势欧美久久久久大黄蕉| 香蕉久久一区二区不卡无毒影院| 久久久久99精品成人片三人毛片| 囯产精品久久久久久久久蜜桃| 久久精品国内一区二区三区| 伊人情人综合成人久久网小说 | 久久久久久亚洲精品无码| 亚洲国产日韩欧美久久| 久久91精品国产91久久小草| 亚洲成av人片不卡无码久久| 国内精品伊人久久久久| 国产偷久久久精品专区| 欧美无乱码久久久免费午夜一区二区三区中文字幕 | 欧美精品九九99久久在观看| 久久se精品一区精品二区| 久久久久久精品成人免费图片| 97精品伊人久久久大香线蕉| 久久久久亚洲av无码专区导航 | 免费一级欧美大片久久网| 99久久人妻无码精品系列蜜桃| 99久久精品免费看国产一区二区三区 | 久久精品国产久精国产思思| 亚洲国产成人久久精品99| 亚洲午夜久久影院| 91精品国产乱码久久久久久| 亚洲精品白浆高清久久久久久 | 狠狠色丁香久久婷婷综合蜜芽五月| 日韩欧美亚洲综合久久影院d3| 国产精品美女久久久m| 久久人人爽人人爽人人片AV不| 狠狠色丁香久久婷婷综合蜜芽五月| 无码精品久久一区二区三区| 久久丝袜精品中文字幕| 久久久久久无码国产精品中文字幕 | 久久伊人精品一区二区三区| 亚洲国产日韩欧美综合久久| 一本久久综合亚洲鲁鲁五月天亚洲欧美一区二区 | 亚洲七七久久精品中文国产|