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

            封包和拆包


            對(duì)于基于TCP開發(fā)的通訊程序,有個(gè)很重要的問題需要解決,就是封包和拆包.


            一.為什么基于TCP的通訊程序需要進(jìn)行封包和拆包.

            TCP是個(gè)"流"協(xié)議,所謂流,就是沒有界限的一串?dāng)?shù)據(jù).大家可以想想河里的流水,是連成一片的,其間是沒有分界線的.但一般通訊程序開發(fā)是需要定義一個(gè)個(gè)相互獨(dú)立的數(shù)據(jù)包的,比如用于登陸的數(shù)據(jù)包,用于注銷的數(shù)據(jù)包.由于TCP"流"的特性以及網(wǎng)絡(luò)狀況,在進(jìn)行數(shù)據(jù)傳輸時(shí)會(huì)出現(xiàn)以下幾種情況.
            假設(shè)我們連續(xù)調(diào)用兩次send分別發(fā)送兩段數(shù)據(jù)data1和data2,在接收端有以下幾種接收情況(當(dāng)然不止這幾種情況,這里只列出了有代表性的情況).
            A.先接收到data1,然后接收到data2.
            B.先接收到data1的部分?jǐn)?shù)據(jù),然后接收到data2余下的部分以及data2的全部.
            C.先接收到了data1的全部數(shù)據(jù)和data1的部分?jǐn)?shù)據(jù),然后接收到了data2的余下的數(shù)據(jù).
            D.一次性接收到了data1和data2的全部數(shù)據(jù).

            對(duì)于A這種情況正是我們需要的,不再做討論.對(duì)于B,C,D的情況就是大家經(jīng)常說的"粘包",就需要我們把接收到的數(shù)據(jù)進(jìn)行拆包,拆成一個(gè)個(gè)獨(dú)立的數(shù)據(jù)包.為了拆包就必須在發(fā)送端進(jìn)行封包.
             另:對(duì)于UDP來說就不存在拆包的問題,因?yàn)閁DP是個(gè)"數(shù)據(jù)包"協(xié)議,也就是兩段數(shù)據(jù)間是有界限的,在接收端要么接收不到數(shù)據(jù)要么就是接收一個(gè)完整的一段數(shù)據(jù),不會(huì)少接收也不會(huì)多接收.

            二.為什么會(huì)出現(xiàn)B.C.D的情況.
             "粘包"可發(fā)生在發(fā)送端也可發(fā)生在接收端.
             1.由Nagle算法造成的發(fā)送端的粘包:Nagle算法是一種改善網(wǎng)絡(luò)傳輸效率的算法.簡單的說,當(dāng)我們提交一段數(shù)據(jù)給TCP發(fā)送時(shí),TCP并不立刻發(fā)送此段數(shù)據(jù),而是等待一小段時(shí)間,看看在等待期間是否還有要發(fā)送的數(shù)據(jù),若有則會(huì)一次把這兩段數(shù)據(jù)發(fā)送出去.這是對(duì)Nagle算法一個(gè)簡單的解釋,詳細(xì)的請(qǐng)看相關(guān)書籍.象C和D的情況就有可能是Nagle算法造成的.
                    2.接收端接收不及時(shí)造成的接收端粘包:TCP會(huì)把接收到的數(shù)據(jù)存在自己的緩沖區(qū)中,然后通知應(yīng)用層取數(shù)據(jù).當(dāng)應(yīng)用層由于某些原因不能及時(shí)的把TCP的數(shù)據(jù)取出來,就會(huì)造成TCP緩沖區(qū)中存放了幾段數(shù)據(jù).

            三.怎樣封包和拆包.
               最初遇到"粘包"的問題時(shí),我是通過在兩次send之間調(diào)用sleep來休眠一小段時(shí)間來解決.這個(gè)解決方法的缺點(diǎn)是顯而易見的,使傳輸效率大大降低,而且也并不可靠.后來就是通過應(yīng)答的方式來解決,盡管在大多數(shù)時(shí)候是可行的,但是不能解決象B的那種情況,而且采用應(yīng)答方式增加了通訊量,加重了網(wǎng)絡(luò)負(fù)荷(但是象FTP等協(xié)議采用的就是應(yīng)答方式).再后來就是對(duì)數(shù)據(jù)包進(jìn)行封包和拆包的操作.
                封包:
             封包就是給一段數(shù)據(jù)加上包頭,這樣一來數(shù)據(jù)包就分為包頭和包體兩部分內(nèi)容了(以后講過濾非法包時(shí)封包會(huì)加入"包尾"內(nèi)容).包頭其實(shí)上是個(gè)大小固定的結(jié)構(gòu)體,其中有個(gè)結(jié)構(gòu)體成員變量表示包體的長度,這是個(gè)很重要的變量,其他的結(jié)構(gòu)體成員可根據(jù)需要自己定義.根據(jù)包頭長度固定以及包頭中含有包體長度的變量就能正確的拆分出一個(gè)完整的數(shù)據(jù)包.
                對(duì)于拆包目前我最常用的是以下兩種方式.
                1.動(dòng)態(tài)緩沖區(qū)暫存方式.之所以說緩沖區(qū)是動(dòng)態(tài)的是因?yàn)楫?dāng)需要緩沖的數(shù)據(jù)長度超出緩沖區(qū)的長度時(shí)會(huì)增大緩沖區(qū)長度.
                大概過程描述如下:
                A,為每一個(gè)連接動(dòng)態(tài)分配一個(gè)緩沖區(qū),同時(shí)把此緩沖區(qū)和SOCKET關(guān)聯(lián),常用的是通過結(jié)構(gòu)體關(guān)聯(lián).
                B,當(dāng)接收到數(shù)據(jù)時(shí)首先把此段數(shù)據(jù)存放在緩沖區(qū)中.
                C,判斷緩存區(qū)中的數(shù)據(jù)長度是否夠一個(gè)包頭的長度,如不夠,則不進(jìn)行拆包操作.
                D,根據(jù)包頭數(shù)據(jù)解析出里面代表包體長度的變量.
                E,判斷緩存區(qū)中除包頭外的數(shù)據(jù)長度是否夠一個(gè)包體的長度,如不夠,則不進(jìn)行拆包操作.
                F,取出整個(gè)數(shù)據(jù)包.這里的"取"的意思是不光從緩沖區(qū)中拷貝出數(shù)據(jù)包,而且要把此數(shù)據(jù)包從緩存區(qū)中刪除掉.刪除的辦法就是把此包后面的數(shù)據(jù)移動(dòng)到緩沖區(qū)的起始地址.

                這種方法有兩個(gè)缺點(diǎn).1.為每個(gè)連接動(dòng)態(tài)分配一個(gè)緩沖區(qū)增大了內(nèi)存的使用.2.有三個(gè)地方需要拷貝數(shù)據(jù),一個(gè)地方是把數(shù)據(jù)存放在緩沖區(qū),一個(gè)地方是把完整的數(shù)據(jù)包從緩沖區(qū)取出來,一個(gè)地方是把數(shù)據(jù)包從緩沖區(qū)中刪除.這種拆包的改進(jìn)方法會(huì)解決和完善部分缺點(diǎn).

                下面給出相關(guān)代碼.

            先看包頭結(jié)構(gòu)定義

            #pragma pack(push,1) //開始定義數(shù)據(jù)包, 采用字節(jié)對(duì)齊方式
            /*----------------------包頭---------------------*/
            typedef struct tagPACKAGEHEAD
            {
             BYTE Version;
             WORD Command;
             WORD nDataLen;//包體的長度
            }PACKAGE_HEAD;
            #pragma pack(pop) //結(jié)束定義數(shù)據(jù)包, 恢復(fù)原來對(duì)齊方式

            然后看存放數(shù)據(jù)和"取"數(shù)據(jù)函數(shù).

            /*****************************************************************************
            Description:添加數(shù)據(jù)到緩存
            Input:pBuff[in]-待添加的數(shù)據(jù);nLen[in]-待添加數(shù)據(jù)長度
            Return: 如果當(dāng)前緩沖區(qū)沒有足夠的空間存放pBuff則返回FALSE;否則返回TRUE。
            ******************************************************************************/
            BOOL CDataBufferPool::AddBuff( char *pBuff, int nLen )
            {
             m_cs.Lock();///臨界區(qū)鎖

             if ( nLen < 0 )
             {
              m_cs.Unlock();
              return FALSE;
             }

             if ( nLen <= GetFreeSize() )///判斷剩余空間是否足夠存放nLen長的數(shù)據(jù)
             {
              memcpy(m_pBuff + m_nOffset, pBuff, nLen);
              m_nOffset += nLen;
             }
             else///若不夠則擴(kuò)充原有的空間
             { 
              char *p = m_pBuff;
              m_nSize += nLen*2;//每次增長2*nLen
              m_pBuff = new char[m_nSize];
              memcpy(m_pBuff,p,m_nOffset);
              delete []p;
              memcpy(m_pBuff + m_nOffset, pBuff, nLen);
              m_nOffset += nLen;
              m_cs.Unlock();
              return FALSE;
             }
             m_cs.Unlock();
             return TRUE;
            }

             

            /*****************************************************************************
            Description:獲取一個(gè)完整的包
            Input:Buf[out]-獲取到的數(shù)據(jù);nLen[out]-獲取到的數(shù)據(jù)長度
            Return: 1、當(dāng)前緩沖區(qū)不夠一個(gè)包頭的數(shù)據(jù) 2、當(dāng)前緩沖區(qū)不夠一個(gè)包體的數(shù)據(jù)
            ******************************************************************************/

            int  CDataBufferPool::GetFullPacket( char *Buf, int& nLen )
            {
             m_cs.Lock();

             if ( m_nOffset < m_PacketHeadLen )//當(dāng)前緩沖區(qū)不夠一個(gè)包頭的數(shù)據(jù)
             {
              m_cs.Unlock();
              return 1;
             }
             PACKAGE_HEAD *p = (PACKAGE_HEAD *)m_pBuff;
             if( (m_nOffset-m_PacketHeadLen) < (int)p->nDataLen )//當(dāng)前緩沖區(qū)不夠一個(gè)包體的數(shù)據(jù)
             {
              m_cs.Unlock();
              return 2;
             }
             //判斷包的合法性
            /* int IsIntegrallity = ValidatePackIntegrality(p);
             if( IsIntegrallity != 0 )
             {
              m_cs.Unlock();
              return IsIntegrallity;
             }
            */
             nLen = m_PacketHeadLen+p->nDataLen;
             memcpy( Buf, m_pBuff, nLen );
             m_nOffset -= nLen;
             memcpy( m_pBuff, m_pBuff+nLen, m_nOffset );

             m_cs.Unlock();
             return 0;
            }


                前面提到過這種方法的缺點(diǎn).下面給出一個(gè)改進(jìn)辦法, 即采用環(huán)形緩沖.但是這種改進(jìn)方法還是不能解決第一個(gè)缺點(diǎn)以及第一個(gè)數(shù)據(jù)拷貝,只能解決第三個(gè)地方的數(shù)據(jù)拷貝(這個(gè)地方是拷貝數(shù)據(jù)最多的地方).第2種拆包方式會(huì)解決這兩個(gè)問題.
             環(huán)形緩沖實(shí)現(xiàn)方案是定義兩個(gè)指針,分別指向有效數(shù)據(jù)的頭和尾.在存放數(shù)據(jù)和刪除數(shù)據(jù)時(shí)只是進(jìn)行頭尾指針的移動(dòng).
             用代碼來說明.注:下面的代碼是采用一個(gè)開源的游戲服務(wù)器的代碼,我對(duì)此代碼有所修改.
             
            int CCircularBufferPool::PutData(TCHAR *pData, int len)
            {
             if( len <= 0 )
              return 1;

             EnterCriticalSection(&m_cs);
             while (IsOverFlowCondition(len))///判斷緩沖區(qū)剩余空間是否夠存放len長的數(shù)據(jù)
             {
              BufferResize(len);///若不夠,則擴(kuò)充緩沖區(qū).
             }

             if (IsIndexOverFlow(len))///判斷"尾"指針的位置.
             {
              int FirstCopyLen = m_iBufSize-m_iTailPos;
              int SecondCopyLen = len - FirstCopyLen;
              CopyMemory(m_pBuffer+m_iTailPos, pData, FirstCopyLen);
              if (SecondCopyLen)
              {
               CopyMemory(m_pBuffer, pData+FirstCopyLen, SecondCopyLen);
               m_iTailPos = SecondCopyLen;
              }
              else
               m_iTailPos = 0;
             }
             else
             {
              CopyMemory(m_pBuffer+m_iTailPos, pData, len);
              m_iTailPos += len;
             }

             LeaveCriticalSection(&m_cs);
             return 0;


            }


            void CCircularBufferPool::GetData(TCHAR *pData, int len, bool Delete)
            {
             if (len < m_iBufSize-m_iHeadPos)
             {
              CopyMemory(pData, m_pBuffer+m_iHeadPos, len);
              if(Delete==true)
               m_iHeadPos += len;
             }
             else
             {
              int fc, sc;
              fc = m_iBufSize-m_iHeadPos;
              sc = len - fc;
              CopyMemory(pData, m_pBuffer+m_iHeadPos, fc);
              if (sc) CopyMemory(pData+fc, m_pBuffer, sc);
              if(Delete==true)
               m_iHeadPos = sc;
              if(m_iHeadPos >= m_iBufSize)
               m_iHeadPos = 0;

             }
            }

            //
            //進(jìn)行自定義包的解析
            //
            int CCircularBufferPool::GetFullPacket( TCHAR *Buf, int &nLen )
            {
             EnterCriticalSection(&m_cs);
             if( GetValidCount() < m_PacketHeadLen )//當(dāng)前緩沖區(qū)不夠一個(gè)包頭的數(shù)據(jù)
             {
              LeaveCriticalSection(&m_cs);
              return 1;
             }

             GetData(Buf,m_PacketHeadLen,false);
             PACKAGE_HEAD *p = (PACKAGE_HEAD *)Buf;
             if( (GetValidCount()-m_PacketHeadLen) < (int)p->nDataLen )//當(dāng)前緩沖區(qū)不夠一個(gè)包體的數(shù)據(jù)
             {
              LeaveCriticalSection(&m_cs);
              return 2;
             }

             //判斷包的合法性
             int IsIntegrallity = ValidatePackIntegrality(p);
             if( IsIntegrallity != 0 )
             {
              LeaveCriticalSection(&m_cs);
              return IsIntegrallity;
             }
             
             GetData(Buf,m_PacketHeadLen+p->nDataLen,true);
             nLen = m_PacketHeadLen+p->nDataLen;

             LeaveCriticalSection(&m_cs);


             return 0;
            }

             2.利用底層的緩沖區(qū)來進(jìn)行拆包
             由于TCP也維護(hù)了一個(gè)緩沖區(qū),所以我們完全可以利用TCP的緩沖區(qū)來緩存我們的數(shù)據(jù),這樣一來就不需要為每一個(gè)連接分配一個(gè)緩沖區(qū)了.另一方面我們知道recv或者wsarecv都有一個(gè)參數(shù),用來表示我們要接收多長長度的數(shù)據(jù).利用這兩個(gè)條件我們就可以對(duì)第一種方法進(jìn)行優(yōu)化了.
                  對(duì)于阻塞SOCKET來說,我們可以利用一個(gè)循環(huán)來接收包頭長度的數(shù)據(jù),然后解析出代表包體長度的那個(gè)變量,再用一個(gè)循環(huán)來接收包體長度的數(shù)據(jù).
             相關(guān)代碼如下:
              
             char PackageHead[1024];
             char PackageContext[1024*20];

             int len;
             PACKAGE_HEAD *pPackageHead;
             while( m_bClose == false )
             {
              memset(PackageHead,0,sizeof(PACKAGE_HEAD));
              len = m_TcpSock.ReceiveSize((char*)PackageHead,sizeof(PACKAGE_HEAD));
              if( len == SOCKET_ERROR )
              {
                  break;
              }
              if(len == 0)
              {
                  break;
              }
              pPackageHead = (PACKAGE_HEAD *)PackageHead;
              memset(PackageContext,0,sizeof(PackageContext));
              if(pPackageHead->nDataLen>0)
              {
               len = m_TcpSock.ReceiveSize((char*)PackageContext,pPackageHead->nDataLen);
              }
                    }

             m_TcpSock是一個(gè)封裝了SOCKET的類的變量,其中的ReceiveSize用于接收一定長度的數(shù)據(jù),直到接收了一定長度的數(shù)據(jù)或者網(wǎng)絡(luò)出錯(cuò)才返回.
             
            int winSocket::ReceiveSize( char* strData, int iLen )
            {
             if( strData == NULL )
              return ERR_BADPARAM;
             char *p = strData;
             int len = iLen;
             int ret = 0;
             int returnlen = 0;
             while( len > 0)
             {
              ret = recv( m_hSocket, p+(iLen-len), iLen-returnlen, 0 );
              if ( ret == SOCKET_ERROR || ret == 0 )
              {
               
               return ret;
              }

              len -= ret;
              returnlen += ret;
             }
             
             return returnlen;
            }
             對(duì)于非阻塞的SOCKET,比如完成端口,我們可以提交接收包頭長度的數(shù)據(jù)的請(qǐng)求,當(dāng)GetQueuedCompletionStatus返回時(shí),我們判斷接收的數(shù)據(jù)長度是否等于包頭長度,若等于,則提交接收包體長度的數(shù)據(jù)的請(qǐng)求,若不等于則提交接收剩余數(shù)據(jù)的請(qǐng)求.當(dāng)接收包體時(shí),采用類似的方法.
             下面給出相關(guān)代碼

            enum IOType
            {
             IOInitialize,
             IORead,
             IOWrite,
             IOIdle
            };

            class OVERLAPPEDPLUS
            {
            public:
             OVERLAPPED   m_ol;
             IOType    m_ioType;
             bool         m_bIsPackageHead;//當(dāng)前接收的數(shù)據(jù)是否是包頭數(shù)據(jù)。

             int          m_count;
             WSABUF       m_wsaBuffer;
             int          m_RecvPos;
             char         m_Buffer[1024*8];//此緩沖要盡可能大

             OVERLAPPEDPLUS(IOType ioType) {
              ZeroMemory(this, sizeof(OVERLAPPEDPLUS));
              m_ioType = ioType;
             }
            };
            接收連接后發(fā)出的第一個(gè)請(qǐng)求,請(qǐng)求接收包頭大小的數(shù)據(jù).
             OVERLAPPEDPLUS *pOverlappedPlus =  new OVERLAPPEDPLUS;
             pOverlappedPlus->m_wsaBuffer.buf =  pOverlappedPlus->m_Buffer;
             pOverlappedPlus->m_wsaBuffer.len =  PACKAGE_HEAD_LEN;///包頭的長度
             pOverlappedPlus->m_bIsPackageHead = true;
             pOverlappedPlus->m_RecvPos = 0;
             pOverlappedPlus->m_ioType = IORead;


             DWORD RecvBytes;
                 DWORD Flags;
             Flags = 0;
             if (WSARecv(clientSocket, &(pOverlappedPlus->m_wsaBuffer), 1, &RecvBytes, &Flags,
              &pOverlappedPlus->m_ol, NULL) == SOCKET_ERROR)
             {
              if (WSAGetLastError() != ERROR_IO_PENDING)
              {
               delete pOverlappedPlus;
              }
              else
              {
               ///相關(guān)的錯(cuò)誤處理

              }
             }
             else
             {
              ///相關(guān)的錯(cuò)誤處理

             }


            在GetQueuedCompletionStatus所在的函數(shù)中.
             if( pOverlapPlus->m_ioType== IORead)
             { 
              if( pOverlapPlus->m_wsaBuffer.len == dwIoSize )
              {
               if( pOverlapPlus->m_bIsPackageHead == true )///接收到的是包頭。
               {
                PACKAGE_HEAD *pPackageHead = (PACKAGE_HEAD *)(pOverlapPlus->m_Buffer);

                if(pThis->IsLegalityPackageHead(pPackageHead)==false)///判斷是否是合法的包
                {
                 closesocket(lpClientContext->m_Socket);
                 continue;
                }

                pOverlapPlus->m_bIsPackageHead = false;
                pOverlapPlus->m_wsaBuffer.len = pPackageHead->nDataLen;
                pOverlapPlus->m_RecvPos += dwIoSize;
                pOverlapPlus->m_wsaBuffer.buf = pOverlapPlus->m_Buffer+pOverlapPlus->m_RecvPos;

               }
               else///接收到的是包體
               {

                 

               pOverlapPlus->m_RecvPos += dwIoSize;
               ///這時(shí)pOverlapPlus->m_Buffer里就存放了一個(gè)完整的數(shù)據(jù)包,長度為pOverlapPlus->m_RecvPos
               

               ///繼續(xù)請(qǐng)求 請(qǐng)求下一個(gè)數(shù)據(jù)包的包頭
               pOverlapPlus->m_wsaBuffer.buf =  pOverlapPlus->m_Buffer;
               memset(pOverlapPlus->m_Buffer,0,sizeof(pOverlapPlus->m_Buffer));
               pOverlapPlus->m_wsaBuffer.len =  PACKAGE_HEAD_LEN;
               pOverlapPlus->m_bIsPackageHead = true;
               pOverlapPlus->m_RecvPos = 0;
                 
               }
              }
              else///接收的數(shù)據(jù)還不完整
              {
               pOverlapPlus->m_wsaBuffer.len -= dwIoSize;
               pOverlapPlus->m_RecvPos += dwIoSize;
               pOverlapPlus->m_wsaBuffer.buf = pOverlapPlus->m_Buffer+pOverlapPlus->m_RecvPos;
              }
              pOverlapPlus->m_ioType = IORead;
              state = WSARecv(lpClientContext->m_Socket, &(pOverlapPlus->m_wsaBuffer), 1, &RecvBytes, &Flags,
                &pOverlapPlus->m_ol, NULL);
              if ( state == SOCKET_ERROR)
              {
               if(WSAGetLastError() != ERROR_IO_PENDING)
               {
                  
                   //關(guān)閉套接字 釋放相應(yīng)資源
                 continue;
               }
              }
               
             }

            三:如何判斷包的合法性.
            判斷包的合法性可以結(jié)合下面兩種方式來判斷.但是想100%的判定出非法包,只能通過信息安全中的知識(shí)來判定了,對(duì)這種方法這里不做闡述.
            1.通過包頭的結(jié)構(gòu)來判斷包的合法性.
             最初的時(shí)候我是根據(jù)包頭來判斷包的合法性,比如判斷Command是否超出命令范圍,nDataLen是否大于最大包的長度.但是這種方法無法過濾掉非法包,當(dāng)出現(xiàn)非法包時(shí)我們唯一能做的就是斷開連接,或許這也是最好的處理辦法.
             我們可以給一個(gè)完整的包加上開始和結(jié)束標(biāo)志,標(biāo)志可以是個(gè)整數(shù),也可以是一串字符串.以第一種拆包方式為例來說明.當(dāng)要拆一個(gè)完整包時(shí)我們先從緩沖區(qū)有效數(shù)據(jù)頭指針地址搜索包的開始標(biāo)志,搜索到后并且當(dāng)前數(shù)據(jù)夠一個(gè)包頭數(shù)據(jù),則判斷開始標(biāo)志和包頭是否合法,若合法則根據(jù)代表數(shù)據(jù)長度的變量的值定位到包尾,判斷包尾標(biāo)志是否與我們定義的一致,若一致則這個(gè)包是合法的包.若有一項(xiàng)不一致則繼續(xù)尋找下個(gè)包的開始標(biāo)志,并把下個(gè)合法包的前面的數(shù)據(jù)全部舍棄.
            2.通過邏輯層來判斷包的合法性.
             當(dāng)取出一個(gè)合法的包時(shí),我們還要根據(jù)當(dāng)前數(shù)據(jù)處理的邏輯來判斷包的合法性.比如說在登陸成功后的某段時(shí)間服務(wù)器又收到了同一個(gè)客戶端的登陸包,那我們就可以判斷這個(gè)包是非法的,簡單處理就是斷開連接.

             

            posted on 2008-04-16 09:00 RedLight 閱讀(1438) 評(píng)論(0)  編輯 收藏 引用 所屬分類: 網(wǎng)絡(luò)服務(wù)器開發(fā)

            <2009年1月>
            28293031123
            45678910
            11121314151617
            18192021222324
            25262728293031
            1234567

            導(dǎo)航

            統(tǒng)計(jì)

            公告


            Name: Galen
            QQ: 88104725

            常用鏈接

            留言簿(3)

            隨筆分類

            隨筆檔案

            相冊(cè)

            My Friend

            搜索

            最新評(píng)論

            閱讀排行榜

            評(píng)論排行榜

            久久99热这里只有精品国产| 综合久久精品色| 久久国产精品久久久| 国产成人综合久久综合| 久久国产成人| 亚洲熟妇无码另类久久久| 国产亚洲精品美女久久久| 久久人人爽人人爽人人片AV麻豆| 色综合久久夜色精品国产| 国产成人精品久久免费动漫| 韩国三级中文字幕hd久久精品| 亚洲精品乱码久久久久久按摩| 国产99久久久久久免费看| 久久久久波多野结衣高潮| 久久福利片| 999久久久国产精品| 国产精品99久久99久久久| 无码精品久久一区二区三区| 色综合久久88色综合天天| 久久久噜噜噜久久熟女AA片| 亚洲国产婷婷香蕉久久久久久| 国产午夜久久影院| 久久综合精品国产二区无码| 久久久无码精品亚洲日韩蜜臀浪潮| 久久久久久免费一区二区三区| 97久久婷婷五月综合色d啪蜜芽| 久久国产精品一区| 国内精品久久久久久久影视麻豆 | 久久AAAA片一区二区| 国产婷婷成人久久Av免费高清 | 99久久精品国产麻豆| 少妇高潮惨叫久久久久久 | 国内精品久久久久久久涩爱| 青青草国产精品久久久久| 国产美女久久精品香蕉69| 久久精品亚洲日本波多野结衣 | 国产精品va久久久久久久| 亚洲一本综合久久| yellow中文字幕久久网| 久久久久亚洲AV无码去区首| 久久国产精品国语对白|