日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

EISCONN的故事

發(fā)布時(shí)間:2023/12/10 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 EISCONN的故事 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

在這春風(fēng)明媚的日子里,有位T同學(xué)很苦惱。忙碌了一整天,有個(gè)BUG愣是定位不出來(lái)。簡(jiǎn)單描述呢,現(xiàn)象是這樣子的:

第一次處理是正常的,但是后續(xù)的處理就是報(bào)錯(cuò)。sendto()調(diào)用錯(cuò)誤碼是 EISCONN(已被連接)。

憂傷的問(wèn)題

當(dāng)然,代碼BUG的范圍也很快確定了,就是新加入的statsd-client-cpp工具庫(kù)里。代碼量不到兩百行,失敗的地方就是在sendto()的執(zhí)行里(代碼看這兒)。一看錯(cuò)誤碼(EISCONN,比較少見(jiàn)),說(shuō)“socket已經(jīng)被連接”——但咱這明明是UDP協(xié)議啊,無(wú)連接無(wú)connect的!

T同學(xué)咨詢了周圍的大師們,翻閱了《Unix網(wǎng)絡(luò)編程》(沒(méi)錯(cuò),這書(shū)在上次的故事里也出場(chǎng)了!),里面說(shuō):

sendto()函數(shù)的執(zhí)行流大約是這樣子的:

  • 連接套接字;
  • 輸出數(shù)據(jù)報(bào)文;
  • 斷開(kāi)套接字連接;
  • 無(wú)語(yǔ)了。按照圣經(jīng)里說(shuō)法,連接都是被斷開(kāi)了的啊!還怎么會(huì)報(bào)錯(cuò)“已被連接”?!!

    失敗的補(bǔ)救

    T同學(xué)比較耿直,對(duì)付BUG比較直接粗暴:報(bào)啥錯(cuò)誤就解決啥錯(cuò)誤!思路有倆:

  • 人肉斷開(kāi)它!
    這個(gè)實(shí)際上是行不通的(只能整個(gè)兒socket關(guān)閉銷毀掉,不能只斷開(kāi))

  • 連接了也繼續(xù)發(fā)!
    雖然一般情況下UDP協(xié)議的程序都是不管連接直接發(fā)sendto()的,但是先連接后再write()也是可行的。

  • 于是按照方法2搞起!然而,實(shí)踐證明這是不給力的,UDP對(duì)端根本沒(méi)有收到任何數(shù)據(jù)!

    正確的思路

    這種頭痛醫(yī)頭、瞎試幾次的做法,本質(zhì)上就不能算正確的方法。我們的思路應(yīng)該是:

  • 收集現(xiàn)象
  • 分析原因
  • 驗(yàn)證方案
  • 解決問(wèn)題
  • 上面折騰了這么久,基本上還是只有一個(gè)錯(cuò)誤碼和代碼出錯(cuò)的位置,現(xiàn)象數(shù)據(jù)太少了。T同學(xué)冷靜下來(lái)后,開(kāi)始祭出大殺器strace工具程序!
    strace程序能夠捕抓系統(tǒng)調(diào)用(system call),并把這些調(diào)用接口的時(shí)間、參數(shù)、返回值、耗時(shí)等等都記錄下來(lái);輸出信息可讀性相當(dāng)?shù)母?#xff08;比起gdb爽多了),能反映出系統(tǒng)最底層的運(yùn)行狀況,是后臺(tái)開(kāi)發(fā)程序員的居家旅行必備的強(qiáng)(zhuang)大(bi)工具。

    神奇的零

    具體strace的過(guò)程就不多說(shuō)了。調(diào)整進(jìn)程數(shù)量、執(zhí)行strace、查看log、研究執(zhí)行狀況:

    strace32 -s 10086 -o /tmp/Strace.log -tt -p $(pidof -s get_api_key)

    終于發(fā)現(xiàn)一個(gè)關(guān)鍵現(xiàn)象:每次的連接socket()返回值都是0!
    這里稍微解釋一下,為什么零值會(huì)是很特殊:socket()返回值表示的是文件描述符;在POSIX標(biāo)準(zhǔn)里,有三個(gè)特殊的文件描述符值0、1、2,分別是STDIN(標(biāo)準(zhǔn)輸入)、STDOUT(標(biāo)準(zhǔn)輸出)、STDERR(標(biāo)準(zhǔn)錯(cuò)誤)。所以默認(rèn)情況下,零值都會(huì)作為STDIN(標(biāo)準(zhǔn)輸入)使用;只有當(dāng)程序主動(dòng)關(guān)閉了STDIN時(shí),系統(tǒng)才會(huì)分配0值給socket()使用。

    所以這時(shí)候思路有兩個(gè)了:

  • 假如系統(tǒng)分配的0值是正確的,那么得尋找0值后來(lái)被“弄壞”(已連接)的出現(xiàn)地方;
  • 假如系統(tǒng)分配的0值是錯(cuò)誤的,那么一定是某處BUG“失誤”把STDIN釋放掉了;
  • 這種“辯證”的思路,讓我忽然想起了《擼擼姐的超本格事件簿》里的給出各種偽解答搞笑助手:每次破案分析都至少有正反兩個(gè)思路,看起來(lái)毫無(wú)盲點(diǎn),但總是被擼擼姐指出第三種情況!哈哈哈!!!不過(guò)在現(xiàn)實(shí)場(chǎng)景里,正反兩面都深入思考的做法,一般幫助很大的。

    誰(shuí)弄壞了0

    所以0是被誰(shuí)弄壞的?怎么弄壞的?

    為了深入探究這個(gè)問(wèn)題,得先了解一下程序運(yùn)行的環(huán)境。這個(gè)工具庫(kù)之前已經(jīng)在現(xiàn)網(wǎng)的服務(wù)器里跑了很久,也有兩個(gè)簡(jiǎn)約的單元測(cè)試。都沒(méi)有發(fā)現(xiàn)過(guò)問(wèn)題。這次是在往CGI接口里使用,運(yùn)行環(huán)境是QZHTTP+FastCGI。

    所以正常情況下,STDIN應(yīng)該是CGI與qzhttp收包程序之間的連接,用來(lái)傳遞HTTP請(qǐng)求報(bào)文。而分析到的一個(gè)現(xiàn)象是,CGI程序的功能完全正常!也就是說(shuō)程序之間傳遞數(shù)據(jù)(STDIN)是正常的……等等,STDIN(0值)不是分配給我們用嗎??為毛還不影響功能啊啊啊???

    秘訣就是——dup2(a, b)!!這個(gè)函數(shù)能夠把一個(gè)文件描述符的信息拷貝到另一個(gè)描述符里!所以文章最初出現(xiàn)的“EISCONN”錯(cuò)誤的原因是:

    原本UDP無(wú)連接的socket,被人用dup2()“篡改”了,于是就變成了另外的文件描述符,出現(xiàn)了“已連接”的狀態(tài)。

    寫(xiě)個(gè)小程序驗(yàn)證了一下,果然能夠隨便拷貝到STDIN描述符里!!!而再次strace抓包查看,也發(fā)現(xiàn)了明顯的dup調(diào)用:

    可以清晰看到對(duì)STDIN、STDOUT都做了dup2()操作!!!太邪惡了!!!火速在萬(wàn)能的StackOverflow.com上也找到了一篇關(guān)于0值的問(wèn)答,里面有人提到了如何解決這個(gè)問(wèn)題!那就是把0、1、2預(yù)留下來(lái)給系統(tǒng)!老子不陪你們玩零了!!(傲嬌地離去!)

    另一個(gè)思路

    等等,剛才怎么這么快就進(jìn)入了“解決問(wèn)題”的節(jié)奏??? T同學(xué)的案例雖然因?yàn)槠聊徊粔蜷L(zhǎng)被滾動(dòng)掉了,但是我們要分析所有的現(xiàn)象找到原因啊!

    于是進(jìn)入剛才的第二個(gè)思路:是誰(shuí)把STDIN給關(guān)閉了??

    深入strace的log,發(fā)現(xiàn)close(0)首次出現(xiàn)的位置沒(méi)有太特別,距離后來(lái)socket()和sendto()調(diào)用很遙遠(yuǎn),暫時(shí)發(fā)現(xiàn)不了什么(后來(lái)細(xì)想,考慮時(shí)序其實(shí)是個(gè)誤導(dǎo))。

    不過(guò),因?yàn)槭切乱氲膸?kù)導(dǎo)致了這個(gè)問(wèn)題,基本上猜測(cè)要么就是庫(kù)代碼有BUG,要么是庫(kù)和QZHTTP不兼容。重新閱讀代碼中與close()有關(guān)的片段,嘗試梳理一下思路.果然發(fā)現(xiàn)一個(gè)問(wèn)題:

    d->sock是個(gè)關(guān)鍵的值,而且沒(méi)有初始化!一般來(lái)說(shuō)變量沒(méi)有初始化,會(huì)是一個(gè)隨機(jī)的值;但是我們的場(chǎng)景里,StatsdClient對(duì)象是一個(gè)單體實(shí)例,以static限定符實(shí)現(xiàn)的——所以是有初始值0的——?jiǎng)偤糜|發(fā)了BUG。

    解決

    再次核對(duì)代碼邏輯和strace的log,腦補(bǔ)了一下執(zhí)行的流程,基本上就確認(rèn)BUG的原因就是這里了。

    所以啊,在構(gòu)造函數(shù)里增加一個(gè)初始化?d->sock = -1;?問(wèn)題解決了。

    寫(xiě)到這里的時(shí)候,忽然想起上兩周給趙總用這個(gè)工具庫(kù),他也出現(xiàn)了一些詭異問(wèn)題。當(dāng)時(shí)他的現(xiàn)象是:隨機(jī)出現(xiàn)accept錯(cuò)誤。現(xiàn)在看來(lái)BUG原因都是一個(gè),只不過(guò)趙總的使用方式是臨時(shí)變量,沒(méi)初始化的值就是隨機(jī)值,因此偶然觸發(fā)故障(他當(dāng)時(shí)引起的是accept失敗,也是描述符問(wèn)題)。而這次是必現(xiàn)的問(wèn)題,定位起來(lái)輕松一些。

    思考

    所以,要保持良好的代碼編寫(xiě)習(xí)慣。順手初始化了,就不會(huì)有這么糾結(jié)的問(wèn)題了。

    總結(jié)

    以上是生活随笔為你收集整理的EISCONN的故事的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。