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

            libjingle源碼解析(5)-【PseudoTcp】建立UDP之上的TCP(3):對成塊數據流的處理

            轉載自:http://blog.csdn.net/leehark/article/details/7671462

            PseudoTcp對成塊數據流的處理

            上一篇談論了TCPPTCP交互數據流的處理方法。這一篇談論另一個數據流--成塊數據流。成塊數據流主要采用滑動窗口協議和慢啟動算法來控制成塊數據的流量。

            滑動窗口

                滑動窗口允許發送方在停止并等待確認前可以連續發送多個分組。因此發送方不必每發一個就停下來等待,這樣可以加速數據的傳輸。這個Nagle算法沖突么?不會,因為成塊數據流的分組都是滿載傳輸的,根據Nagle算法,當等待發送數據的大小和窗口大小都大于MSS時,會立即發送。

                如果發送方一直傳輸數據會出現經常丟包的現象,特別是快的發送方發給慢的接收方。當接收方還沒有處理數據,發送方就接連發來了數據會填滿接收方的緩沖區,從而后續的數據將被丟棄,為了減少網絡上丟包的次數,用一種機制來限制發送方傳輸數據。

            因此出現了滑動窗口,如下圖:


                滑動窗口分為4個部分:

                    上圖1~3為發送并確認的數據段

                    上圖4~6為已經發送,但是沒有被確認的數據段

                    上圖7~9為可用的窗口,即滑動窗口,發送方還可以發送的數據段空間

                    上圖10以上為不能夠發送。


                當接收方確認數據后,滑動窗口兩邊不斷的向右移動。

                    窗口合攏:當發送方發送數據并等待確認時,滑動窗口的左邊向右移動。

                    窗口張開:當接收方收到數據并確認且釋放緩沖區數據時,右邊向右移動。 

                    窗口收縮:當接收方的緩沖區大小變小時,右邊向左移動,但不建議使用這種方式。

                滑動窗口時通過窗口大小來更新。當接收方收到數據后,重新計算接收緩沖區的大小,并通告發送方。如果通告窗口大小為0,則發送方不能再發送數據,等到窗口大小為非0,這樣可以有效的避免因接收方緩沖區滿導致的分組的丟失。

                那么PTCP是怎么實現的呢?

                PTCP通過m_rbuf_len來標示接收緩沖區大小。如果緩沖區大小小于65536時,m_rwnd_scale0m_rcv_wnd標示窗口大小,而大于65535時,通過如下算法來調整m_rbuf_lenm_rwnd_scale。調整后根據緩沖區中可用空間來更新窗口大小m_rcv_wnd 。為什么選擇65535為界限呢?因為在PTCP的頭部中window字段的長度為16bit,只能支持窗口打小范圍0~65535(包含65535)。

            1. void  
            2. PseudoTcp::resizeReceiveBuffer(uint32 new_size) {  
            3.   uint8 scale_factor = 0;  
            4.   //處理大于65536字節的緩沖區,更新scale_factor  
            5.   while (new_size > 0xFFFF) {  
            6.     ++scale_factor;  
            7.     new_size >>= 1;  
            8.   }  
            9.   new_size <<= scale_factor;//當緩沖區大小大于65535時,大小會被調整  
            10.   bool result = m_rbuf.SetCapacity(new_size);//更新緩沖區  
            11.   m_rbuf_len = new_size;//更新緩沖區大小  
            12.   m_rwnd_scale = scale_factor;//更新窗口擴大因子  
            13.   m_ssthresh = new_size;  
            14.   size_t available_space = 0;  
            15.   m_rbuf.GetWriteRemaining(&available_space);  
            16.   m_rcv_wnd = available_space;//更新可用窗口大小  
            17. }  

                 當PTCP三次握手時,通過PTCP選項TCP_OPT_WND_SCALE來通告對方m_rwnd_scale的大小。

            1. void  
            2. PseudoTcp::queueConnectMessage() {  
            3.   talk_base::ByteBuffer buf(talk_base::ByteBuffer::ORDER_NETWORK);  
            4.   buf.WriteUInt8(CTL_CONNECT);  
            5.   if (m_support_wnd_scale) {//判斷窗口擴大選項是否開啟  
            6.     buf.WriteUInt8(TCP_OPT_WND_SCALE);//增加窗口擴大選項  
            7.     buf.WriteUInt8(1);  
            8.     buf.WriteUInt8(m_rwnd_scale);//窗口擴大擴大因子  
            9.   }  
            10.   m_snd_wnd = buf.Length();  
            11.   queue(buf.Data(), buf.Length(), true);  
            12. }  

                PTCP接收窗口擴大因子對應的控制包之后,通過parseOptions方法來解析此包如下:

            1. void  
            2. PseudoTcp::parseOptions(const char* data, uint32 len) {  
            3.   std::set<uint8> options_specified;  
            4.   talk_base::ByteBuffer buf(data, len);  
            5.   while (buf.Length()) {  
            6.     uint8 kind = TCP_OPT_EOL;  
            7.     buf.ReadUInt8(&kind);  
            8.     if (kind == TCP_OPT_EOL) {//判斷是否到了緩沖區末  
            9.       break;  
            10.     } else if (kind == TCP_OPT_NOOP) {//空選項  
            11.       continue;  
            12.     }  
            13.     UNUSED(len);  
            14.     uint8 opt_len = 0;  
            15.     buf.ReadUInt8(&opt_len);  
            16.     if (opt_len <= buf.Length()) {  
            17.       applyOption(kind, buf.Data(), opt_len);//更新選項對應的值  
            18.       buf.Consume(opt_len);  
            19.     } else {  
            20.       return;  
            21.     }  
            22.     options_specified.insert(kind);  
            23.   }  
            24.   if (options_specified.find(TCP_OPT_WND_SCALE) == options_specified.end()) {  
            25.     if (m_rwnd_scale > 0) {  
            26.       resizeReceiveBuffer(DEFAULT_RCV_BUF_SIZE);//如果對端不支持窗口擴大因子,且本端的緩沖區大小超過了65535,則改為60K,因為必須兩端都支持窗口擴大因子才能使用m_swnd_scale。  
            27.       m_swnd_scale = 0;  
            28.     }  
            29.   }  
            30. }  

                接收方調整窗口大小,如下:

                窗口合攏:當接收方收到數據時,會從窗口大小里減去把接收緩沖區消耗的數據大小。

            1. bool PseudoTcp::process(Segment& seg) {  
            2.     ......  
            3.       uint32 nOffset = seg.seq - m_rcv_nxt;  
            4.       talk_base::StreamResult result = m_rbuf.WriteOffset(seg.data, seg.len,  
            5.                                                           nOffset, NULL);  
            6.       ASSERT(result == talk_base::SR_SUCCESS);  
            7.       UNUSED(result);  
            8.       if (seg.seq == m_rcv_nxt) {//如果當前收到的分組恰好是下一個需要的分組  
            9.         m_rbuf.ConsumeWriteBuffer(seg.len);//消耗接收緩沖區  
            10.         m_rcv_nxt += seg.len;//更新下一個需要的分組  
            11.         m_rcv_wnd -= seg.len;//更新窗口大小,減去剛才消耗的緩沖區  
            12.         bNewData = true;  
            13.         RList::iterator it = m_rlist.begin();  
            14.         while ((it != m_rlist.end()) && (it->seq <= m_rcv_nxt)) {  
            15.           if (it->seq + it->len > m_rcv_nxt) {  
            16.             sflags = sfImmediateAck; // (Fast Recovery)  
            17.             uint32 nAdjust = (it->seq + it->len) - m_rcv_nxt;  
            18.             m_rbuf.ConsumeWriteBuffer(nAdjust);  
            19.             m_rcv_nxt += nAdjust;//之前收到的分組包含了下一個需要的seq number,調整m_rcv_nxt   
            20.             m_rcv_wnd -= nAdjust;//m_rcv_nxt增加了,且接收緩沖區被填充了,窗口大小也隨之更新。  
            21.           }  
            22.           it = m_rlist.erase(it);  
            23.         }  
            24.       } else {//拿到的分組不是所需要的,但是有效的分組  
            25.         RSegment rseg;  
            26.         rseg.seq = seg.seq;  
            27.         rseg.len = seg.len;  
            28.         RList::iterator it = m_rlist.begin();  
            29.         while ((it != m_rlist.end()) && (it->seq < rseg.seq)) {  
            30.           ++it;  
            31.         }  
            32.         m_rlist.insert(it, rseg);//更新接收分組列表,當收到下一個所需要的分組時,重組恢復所用。  
            33.       }  
            34. ......  
            35. }  

                窗口張開當應用層調用Recv來獲取PTCP接收的數據時,PTCP會把此部分數據清除,騰空緩沖區并擴大窗口大小。

            1. int PseudoTcp::Recv(char* buffer, size_t len) {  
            2.  ......  
            3.   talk_base::StreamResult result = m_rbuf.Read(buffer, len, &read, NULL);  
            4. ......  
            5.   size_t available_space = 0;  
            6.   m_rbuf.GetWriteRemaining(&available_space);//獲取接收緩沖區可用空間  
            7.   if (uint32(available_space) - m_rcv_wnd >=  
            8.       talk_base::_min<uint32>(m_rbuf_len / 2, m_mss)) {  
            9.     bool bWasClosed = (m_rcv_wnd == 0); // !?! Not sure about this was closed business  
            10.     m_rcv_wnd = available_space;//更新窗口大小,此為窗口張開過程  
            11.     if (bWasClosed) {  
            12.       attemptSend(sfImmediateAck);//如果窗口大小從0變為有可用空間時,立即通告對方可以繼續發送數據。  
            13.     }  
            14.   }  
            15.   return read;  
            16. }  

                通告窗口大小給對方:

            1. IPseudoTcpNotify::WriteResult PseudoTcp::packet(uint32 seq, uint8 flags,  
            2.                                                 uint32 offset, uint32 len) {  
            3.   ASSERT(HEADER_SIZE + len <= MAX_PACKET);  
            4.   uint32 now = Now();  
            5.   uint8 buffer[MAX_PACKET];  
            6.   long_to_bytes(m_conv, buffer);  
            7.   long_to_bytes(seq, buffer + 4);  
            8.   long_to_bytes(m_rcv_nxt, buffer + 8);  
            9.   buffer[12] = 0;  
            10.   buffer[13] = flags;  
            11.   short_to_bytes(static_cast<uint16>(m_rcv_wnd >> m_rwnd_scale), buffer + 14);//這里會把窗口擴大因子也算進去  
            12. ......  
            13. }  

                當發送方收到接收方發送的窗口大小后,可發送大小計算為窗口大小減去已經發送但未被確認的數據大小。

            1. void PseudoTcp::attemptSend(SendFlags sflags) {  
            2. ......  
            3.     uint32 nWindow = talk_base::_min(m_snd_wnd, cwnd);//接收方窗口大小  
            4.     uint32 nInFlight = m_snd_nxt - m_snd_una;//已經發送但未被確認的數據大小  
            5. uint32 nUseable = (nInFlight < nWindow) ? (nWindow - nInFlight) : 0;//發送方可發送數據大小  
            6. ......  
            7. }  

            慢啟動

                當接收方和發送方之間存在多個路由器和速率較慢的鏈路時,一些中間的路由器必須緩存分組。一開始發送方向接收方發送多個分組,可能會把緩存填滿,這會嚴重降低TCP的吞吐量。

                TCP通過慢啟動算法解決上述問題:首先設置擁塞窗口cwnd1,當發送方每收到一個ACK擁塞窗口加1個報文段。發送方取擁塞窗口和通告窗口的最小值為發送上限。擁塞窗口是發送方使用的流量控制,而通告窗口時接收方使用的流量控制。

                發送方首先發送一個報文段,當收到ACK時,cwnd變為2,可以發送2個報文段,當收到2ACKcwnd變為4,發送方可以發送4個報文段,依次類推,慢啟動算法是指數增長的。

                PTCP實現慢啟動算法如下:

                Cwnd初始值為2MSS,當收到ACKcwnd增加一個MSS

            1. Bool PseudoTcp::process(Segment& seg) {  
            2. ......  
            3.   // Check if this is a valuable ack  
            4.   if ((seg.ack > m_snd_una) && (seg.ack <= m_snd_nxt)) {  
            5. if (m_dup_acks >= 3) {  
            6. ......  
            7. }else{  
            8.       m_dup_acks = 0;  
            9.       // Slow start, congestion avoidance  
            10.       if (m_cwnd < m_ssthresh) {  
            11.         m_cwnd += m_mss;//當收到有效的ACK時,cwnd增加一個MSS。  
            12.       } else {  
            13.         m_cwnd += talk_base::_max<uint32>(1, m_mss * m_mss / m_cwnd);  
            14.       }  
            15. }  
            16.   }  
            17. ......  
            18. }  


                當發送方發送數據時,取窗口大小為通告窗口(m_snd_wnd和擁塞窗口(cwnd)的最小值,然后減去已經發送的未被確認的大小為當前可發送數據大小(nUseable )。

            1. void PseudoTcp::attemptSend(SendFlags sflags) {  
            2. ......  
            3.   while (true) {  
            4.     uint32 cwnd = m_cwnd;  
            5.     if ((m_dup_acks == 1) || (m_dup_acks == 2)) { // Limited Transmit  
            6.       cwnd += m_dup_acks * m_mss;  
            7.     }  
            8.     uint32 nWindow = talk_base::_min(m_snd_wnd, cwnd);//取窗口大小為通告窗口和擁塞窗口的最小值  
            9.     uint32 nInFlight = m_snd_nxt - m_snd_una;  
            10.     uint32 nUseable = (nInFlight < nWindow) ? (nWindow - nInFlight) : 0;//減去已經發送的未被確認的大小為當前可發送數據大小  
            11.     size_t snd_buffered = 0;  
            12.     m_sbuf.GetBuffered(&snd_buffered);  
            13.     uint32 nAvailable =  
            14.         talk_base::_min(static_cast<uint32>(snd_buffered) - nInFlight, m_mss);//已經緩存的數據中可發送數據大小  
            15. ......  
            16. }  

            posted on 2013-09-01 14:07 楊粼波 閱讀(490) 評論(0)  編輯 收藏 引用

            人妻无码久久精品| 久久精品国产精品亚洲人人| 久久这里只有精品视频99| 一级女性全黄久久生活片免费 | 亚洲国产精品久久久天堂| 久久久久人妻一区精品性色av| av国内精品久久久久影院| 久久久久国产精品三级网| 久久精品国产亚洲AV嫖农村妇女| 久久久久黑人强伦姧人妻| 精品熟女少妇AV免费久久| 7777精品久久久大香线蕉| 久久青青草原精品国产| 久久精品国产亚洲7777| 成人久久精品一区二区三区| 国内精品久久久久久不卡影院| 欧美丰满熟妇BBB久久久| 97久久精品人人澡人人爽| 国产精品久久久久蜜芽| 欧洲成人午夜精品无码区久久 | 久久青青草原亚洲av无码app| 日本高清无卡码一区二区久久 | 久久免费精品视频| 久久亚洲精品无码VA大香大香| 国产亚洲欧美成人久久片| 久久丫忘忧草产品| 99久久国产免费福利| 久久精品国产精品亚洲毛片| 亚洲国产另类久久久精品黑人| 狠狠久久综合伊人不卡| 2020最新久久久视精品爱| 久久福利青草精品资源站| 亚洲精品无码成人片久久| 久久精品国产第一区二区| 久久久久香蕉视频| 国产精品青草久久久久福利99 | 性做久久久久久久久老女人| 久久99国产精品成人欧美| 国产AV影片久久久久久| 久久国产成人精品国产成人亚洲| 国产伊人久久|