Windows網(wǎng)絡踩坑實錄

Windows 開發(fā)日記

背景: 把一個多線程網(wǎng)絡軟件移植到Windows上。

基本簡介:

  1. 軟件類似代理炎疆。轉(zhuǎn)發(fā)上層應用UDP數(shù)據(jù)包,僅添加一些字段在數(shù)據(jù)頭。
  2. 每一條服務端發(fā)過來的都是消息种呐,有邊界,不是字節(jié)流弃甥。
  3. 線程A負責接收數(shù)據(jù)(libpcap抓包)爽室,線程B負責發(fā)送數(shù)據(jù)和處理數(shù)據(jù)。線程A收到的數(shù)據(jù)要傳給線程B處理淆攻。數(shù)據(jù)流向:線程A->線程B->上層應用阔墩。

問題:

  1. Mac/Linux上速度很快,Windows上面速度極其慢瓶珊。非常詭異的是啸箫,通過流量監(jiān)控,發(fā)現(xiàn)軟件下載速度非成∏郏快忘苛,但上層應用收到的數(shù)據(jù)的速度極其慢蝉娜。

過程:

1. UDP

在linux上,線程A和B數(shù)據(jù)收發(fā)是通過unix域套接字扎唾。

在Windows召川,由于消息是有邊界的,所以自然想到了同步方式是UDP胸遇,自然走loopback比較合適荧呐。。沒有選擇命名管道狐榔、完成端口的原因是:軟件的消息隊列用的是libuv坛增,對這兩種SOCK_DGRAM方式的通信不支持。

使用UDP后發(fā)現(xiàn)在Windows上薄腻,軟件速度極其慢收捣。為了比較,在linux上把通信方式也切換為UDP庵楷,發(fā)現(xiàn)linux上速度仍舊很快罢艾。

2. Winpcap不支持某些無線網(wǎng)卡抓包

考慮到有些無線網(wǎng)卡不支持抓包,所以造成沒有速度尽纽。改用有線連接的時候咐蚯,發(fā)現(xiàn)果真能抓數(shù)據(jù)了。但速度仍然很慢弄贿。通過網(wǎng)絡監(jiān)控發(fā)現(xiàn)春锋,軟件的網(wǎng)絡速度非常快差凹,但應用層基本沒速度期奔。

3. Windows loopback上的套接字緩沖很小

線程A發(fā)送一個數(shù)據(jù),我就把cnt1增1危尿,線程B收到一個數(shù)據(jù)我就把cnt2增1. 運行一會兒就發(fā)現(xiàn)呐萌,cnt2 只有cnt1的10%不到。意思是丟包率超過90%谊娇。

由于以前從來沒有在Windows上開發(fā)過肺孤,實在想不通為什么丟包率這么高。

折騰了幾天找不出原因后济欢,網(wǎng)上問了一下赠堵,有人說Windows loopback隊列很小。問題簡單了法褥,把兩個udp socket的buf都增大顾腊,直接設(shè)置成了10M。情況好多了挖胃,但運行久了杂靶,軟件接受數(shù)據(jù)變快以后梆惯,丟包又開始上升。這說明通過簡單的擴大buf size行不通吗垮。

4. 線程A和線程B使用可靠傳輸

既然在loopback下udp丟包率這么高垛吗,自然而然的想到了使用TCP。問題是TCP是面向流的烁登,是沒有消息邊界的怯屉。

TCP消息分界有如下幾種方式,解決這個問題:

  1. 發(fā)送固定大小的長度N饵沧。消息的長度在數(shù)據(jù)的最前端锨络。接收端先讀取長度len(比如4個字節(jié)的int),然后讀取N - 4個字節(jié)狼牺。后一次讀取的len字節(jié)就是數(shù)據(jù)羡儿。數(shù)據(jù)以后的字節(jié)全部丟棄。
  2. 用分界符是钥。比如用x12341234來區(qū)分消息掠归。如果數(shù)據(jù)中本身包含分界符,那么再用一個分界符去轉(zhuǎn)移悄泥。這種在以太網(wǎng)中也有應用虏冻。
  3. 數(shù)據(jù)長度+數(shù)據(jù)組成來發(fā)送,大小不固定弹囚。

第一種方式厨相,比較容易,但容易浪費帶寬鸥鹉。

第二種方式蛮穿,比較費時間,每一次都要遍歷數(shù)據(jù)宋舷,不可取绪撵。

第三種方式瓢姻,邏輯比較復雜祝蝠,比如讀取消息可能不完整,讀取了幾個消息幻碱。任何一次出錯會造成后面所有的數(shù)據(jù)都出錯绎狭。要求正確率100%。

最終選擇了第三種方式褥傍。然后再也沒有出現(xiàn)軟件速度飛快儡嘶,上層APP沒速度的情況了。

5. 實現(xiàn)

下面是部分代碼恍风。

發(fā)送端:

int PacketStream::Send(int nread, const rbuf_t &rbuf) {
    if (nread > 0) {
        char buf[MAX_BUF_SIZE];
        *(COUNT_TYPE *)buf = nread;
        char *p = buf + COUNT_SIZE;
        memcpy(p, rbuf.base, nread);
        assert(nread + COUNT_SIZE <= MAX_BUF_SIZE);
        return doSend(buf, COUNT_SIZE + nread);
    }
    return nread;
}

int PacketStream::doSend(const char *buf, int nread) {
    if (nread > 0) {
//        LOGD << "nread: " << nread;
        assert(nread < OM_MAX_PKT_SIZE);
        int nret = send(mWriteSock, buf, nread, 0);
        LOGE_IF(nret < 0) << "send error: " << strerror(errno) << ", nret: " << nret;
        return nret;
    }
    return nread;
}

接收端:

int PacketStream::rawInput(int nread, const char *buf) {
    if (nread > 0) {
        assert(mNextLen <= MAX_BUF_SIZE);

        if (nread > (RSOCK_UV_MAX_BUF + MAX_BUF_SIZE)) {
            LOGE << "nread too large: " << nread;
            assert(0);
        }

        const int totLen = nread + mBufLen; // data in buf and in mTempBuf, not including length(4) of mBufLen
        //LOGD << "mNextLen: " << mNextLen << ", mBufLen: " << mBufLen << ", nread: " << nread << ", totLen: " << totLen;

        if (totLen > mNextLen) {    // totLen > mNextLen. copy two buf into one
            char *pbuf = mLargeBuf; // mLargeBuf is large enough to store both mTempBuf and buf
            int left = totLen;
            if (mNextLen > 0) { // if nextLen valid. prepend it to buf.
                *(COUNT_TYPE*)pbuf = mNextLen;
                pbuf += COUNT_SIZE;
                left += COUNT_SIZE; // DON'T FORGET THIS !!!!
            }

            memcpy(pbuf, mTempBuf, mBufLen);
            pbuf += mBufLen;
            memcpy(pbuf, buf, nread);

            const char *p = mLargeBuf;
            int nret = 0;
            
            std::vector<int> head;

            // now mLargeBuf always starts with len information
            while (left >= 0) {
                if (left < COUNT_SIZE) {    // left is only part of int, prevent invalid memory access.
                    markTempBuf(0, p, left);
                  //  LOGD << "mNextLen: " << mNextLen << ", left: " << left << ", mBufLen: " << mBufLen;
                    break;
                }
                
                COUNT_TYPE nextLen = *(COUNT_TYPE *)p;
                //LOGD << "nextLen: " << nextLen << ", left: " << left;
                head.push_back(nextLen);
                assert(nextLen > 0 && nextLen < MAX_BUF_SIZE);                
                p += COUNT_SIZE;
                left -= COUNT_SIZE;
                if (nextLen <= left) {
                    nret = Input(nextLen, new_buf(nextLen, p, nullptr));                    
                    left -= nextLen;
                    p += nextLen;       
                    //LOGD << "nret: " << nret << ", nextLen: " << nextLen << ", left: " << left << ", p - mLargeBuf: " << (p - mLargeBuf);
                    if (left >= COUNT_SIZE) {
                        COUNT_TYPE temp = *(COUNT_TYPE*)p;
                        if (temp <= 0 || temp > OM_MAX_PKT_SIZE) {
                            LOGE << "temp error: " << temp;
                        }
                    }
                } else {
                    markTempBuf(nextLen, p, left);
                    //LOGD << "nextLen: " << nextLen << ", left: " << left;
                    break;
                }
            }

            return nret;
        } else if (totLen < mNextLen) {    // if not enough data. just copy
            memcpy(mTempBuf + mBufLen, buf, nread);
            mBufLen += nread;
            //LOGD << "less than: mNextLen: " << mNextLen << ", mBufLen: " << mBufLen << ", nread: " << nread;
            return 0;
        } else {    //  equal: (totLen == mNextLen)
            memcpy(mTempBuf + mBufLen, buf, nread);
            //            LOGD << "input: " << totLen;
            int n = Input(totLen, new_buf(totLen, mTempBuf, nullptr));
            markTempBuf(0, nullptr, 0);
            //LOGD << "euqal, after input: mNextLen: " << mNextLen << ", mBufLen: " << mBufLen << ", nread: " << nread;
            return n;
        }
    } else if (nread == UV_EOF) {
        markTempBuf(0, nullptr, 0); // todo: when error occurs, should close this synconn and create a new one
        LOGE << "nread = EOF";
    } else if (nread < 0) {
        LOGE << "error: " << uv_strerror(nread);
        assert(0);  // todo: when error occurs, should close this synconn and create a new one        
    }
    return nread;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蹦狂,一起剝皮案震驚了整個濱河市誓篱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌凯楔,老刑警劉巖窜骄,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異摆屯,居然都是意外死亡邻遏,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進店門虐骑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來准验,“玉大人,你說我怎么就攤上這事廷没『ィ” “怎么了?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵腕柜,是天一觀的道長济似。 經(jīng)常有香客問我,道長盏缤,這世上最難降的妖魔是什么砰蠢? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮唉铜,結(jié)果婚禮上台舱,老公的妹妹穿的比我還像新娘。我一直安慰自己潭流,他們只是感情好竞惋,可當我...
    茶點故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著灰嫉,像睡著了一般拆宛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上讼撒,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天浑厚,我揣著相機與錄音,去河邊找鬼根盒。 笑死钳幅,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的炎滞。 我是一名探鬼主播敢艰,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼册赛!你這毒婦竟也來了钠导?” 一聲冷哼從身側(cè)響起震嫉,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎牡属,沒想到半個月后责掏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡湃望,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年换衬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片证芭。...
    茶點故事閱讀 40,769評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡瞳浦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出废士,到底是詐尸還是另有隱情叫潦,我是刑警寧澤,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布官硝,位于F島的核電站矗蕊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏氢架。R本人自食惡果不足惜傻咖,卻給世界環(huán)境...
    茶點故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望岖研。 院中可真熱鬧卿操,春花似錦、人聲如沸孙援。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拓售。三九已至窥摄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間础淤,已是汗流浹背崭放。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留值骇,地道東北人莹菱。 一個月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓移国,卻偏偏與公主長得像吱瘩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子迹缀,可洞房花燭夜當晚...
    茶點故事閱讀 45,781評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 29,418評論 8 265
  • 從三月份找實習到現(xiàn)在使碾,面了一些公司蜜徽,掛了不少,但最終還是拿到小米票摇、百度拘鞋、阿里、京東矢门、新浪盆色、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,278評論 11 349
  • 1.這篇文章不是本人原創(chuàng)的祟剔,只是個人為了對這部分知識做一個整理和系統(tǒng)的輸出而編輯成的隔躲,在此鄭重地向本文所引用文章的...
    SOMCENT閱讀 13,077評論 6 174
  • 相信你已經(jīng)注意到了這樣的趨勢:我們在公眾號、今日頭條等平臺上閱讀的文章標題越來越長了物延,甚至出現(xiàn)了在標題上寫完了整個...
    再來一次打開閱讀 213評論 0 0
  • A4紙宣旱,一分鐘,標題叛薯,內(nèi)容不超過40個字浑吟。方法不超過3條如绸。麥肯錫驳庭,柔性屏的電子記錄本
    關(guān)注成長_李逍遙閱讀 147評論 0 0