1. <tt id="5hhch"><source id="5hhch"></source></tt>
    1. <xmp id="5hhch"></xmp>

  2. <xmp id="5hhch"><rt id="5hhch"></rt></xmp>

    <rp id="5hhch"></rp>
        <dfn id="5hhch"></dfn>

      1. TCP/IP網(wǎng)絡編程中socket的行為

        時間:2020-10-06 15:44:26 TCP/IP 我要投稿

        TCP/IP網(wǎng)絡編程中socket的行為

          想要熟練掌握Linux下的TCP/IP網(wǎng)絡編程,至少有三個層面的知識需要熟悉:

          1. TCP/IP協(xié)議(如連接的建立和終止、重傳和確認、滑動窗口和擁塞控制等等)

          2. Socket I/O系統(tǒng)調(diào)用(重點如read/write),這是TCP/IP協(xié)議在應用層表現(xiàn)出來的行為。

          3. 編寫Performant, Scalable的服務器程序。包括多線程、IO Multiplexing、非阻塞、異步等各種技術(shù)。

          關(guān)于TCP/IP協(xié)議,建議參考Richard Stevens的《TCP/IP Illustrated,vol1》(TCP/IP詳解卷1)。

          關(guān)于第二層面,依然建議Richard Stevens的《Unix network proggramming,vol1》(Unix網(wǎng)絡編程卷1),這兩本書公認是Unix網(wǎng)絡編程的圣經(jīng)。

          至于第三個層面,UNP的書中有所提及,也有著名的C10K問題,業(yè)界也有各種各樣的框架和解決方案,本人才疏學淺,在這里就不一一敷述。

          本文的重點在于第二個層面,主要總結(jié)一下Linux下TCP/IP網(wǎng)絡編程中的read/write系統(tǒng)調(diào)用的行為,知識來源于自己網(wǎng)絡編程的粗淺經(jīng)驗和對《Unix網(wǎng)絡編程卷1》相關(guān)章節(jié)的總結(jié)。由于本人接觸Linux下網(wǎng)絡編程時間不長,錯誤和疏漏再所難免,望看官不吝賜教。

          一. read/write 的語義:為什么會阻塞?

          先從write說起:

          #include <unistd.h>

          ssize_t write(int fd, const void *buf, size_t count);

          首先,write成功返回,只是buf中的數(shù)據(jù)被復制到了kernel中的TCP發(fā)送緩沖區(qū)。至于數(shù)據(jù)什么時候被發(fā)往網(wǎng)絡,什么時候被對方主機接收,什么時候被對方進程讀取,系統(tǒng)調(diào)用層面不會給予任何保證和通知。

          write在什么情況下會阻塞?當kernel的該socket的發(fā)送緩沖區(qū)已滿時。對于每個socket,擁有自己的send buffer和receive buffer。從Linux 2.6開始,兩個緩沖區(qū)大小都由系統(tǒng)來自動調(diào)節(jié)(autotuning),但一般在default和max之間浮動。

          # 獲取socket的發(fā)送/接受緩沖區(qū)的大。海ê竺娴闹凳窃谖以贚inux 2.6.38 x86_64上測試的結(jié)果)

          sysctl net.core.wmem_default       #126976

          sysctl net.core.wmem_max        #131071

          sysctl net.core.wmem_default       #126976

          sysctl net.core.wmem_max           #131071

          已經(jīng)發(fā)送到網(wǎng)絡的數(shù)據(jù)依然需要暫存在send buffer中,只有收到對方的ack后,kernel才從buffer中清除這一部分數(shù)據(jù),為后續(xù)發(fā)送數(shù)據(jù)騰出空間。接收端將收到的數(shù)據(jù)暫存在receive buffer中,自動進行確認。但如果socket所在的進程不及時將數(shù)據(jù)從receive buffer中取出,最終導致receive buffer填滿,由于TCP的滑動窗口和擁塞控制,接收端會阻止發(fā)送端向其發(fā)送數(shù)據(jù)。這些控制皆發(fā)生在TCP/IP棧中,對應用程序是透明的,應用程序繼續(xù)發(fā)送數(shù)據(jù),最終導致send buffer填滿,write調(diào)用阻塞。

          一般來說,由于接收端進程從socket讀數(shù)據(jù)的速度跟不上發(fā)送端進程向socket寫數(shù)據(jù)的速度,最終導致發(fā)送端write調(diào)用阻塞。

          而read調(diào)用的行為相對容易理解,從socket的receive buffer中拷貝數(shù)據(jù)到應用程序的buffer中。read調(diào)用阻塞,通常是發(fā)送端的數(shù)據(jù)沒有到達。

          二. blocking(默認)和nonblock模式下 read/write 行為的區(qū)別:

          將socket fd設(shè)置為nonblock(非阻塞)是在服務器編程中常見的做法,采用blocking IO并為每一個client創(chuàng)建一個線程的模式開銷巨大且可擴展性不佳(帶來大量的切換開銷),更為通用的做法是采用線程池+Nonblock I/O+Multiplexing(select/poll,以及Linux上特有的epoll)。

          // 設(shè)置一個文件描述符為nonblock

          int set_nonblocking(int fd)

          {

          int flags;

          if ((flags = fcntl(fd, F_GETFL, 0)) == -1)

          flags = 0;

          return fcntl(fd, F_SETFL, flags | O_NONBLOCK);

          }

          幾個重要的結(jié)論:

          1. read總是在接收緩沖區(qū)有數(shù)據(jù)時立即返回,而不是等到給定的read buffer填滿時返回。

          只有當receive buffer為空時,blocking模式才會等待,而nonblock模式下會立即返回-1(errno = EAGAIN或EWOULDBLOCK)

          2. blocking的write只有在緩沖區(qū)足以放下整個buffer時才返回(與blocking read并不相同)

          nonblock write則是返回能夠放下的字節(jié)數(shù),之后調(diào)用則返回-1(errno = EAGAIN或EWOULDBLOCK)

          對于blocking的write有個特例:當write正阻塞等待時對面關(guān)閉了socket,則write則會立即將剩余緩沖區(qū)填滿并返回所寫的字節(jié)數(shù),再次調(diào)用則write失。╟onnection reset by peer),這正是下個小節(jié)要提到的:

          三. read/write對連接異常的反饋行為:

          對應用程序來說,與另一進程的TCP通信其實是完全異步的過程:

          1. 我并不知道對面什么時候、能否收到我的數(shù)據(jù)

          2. 我不知道什么時候能夠收到對面的數(shù)據(jù)

          3. 我不知道什么時候通信結(jié)束(主動退出或是異常退出、機器故障、網(wǎng)絡故障等等)

          對于1和2,采用write() -> read() -> write() -> read() ->…的序列,通過blocking read或者nonblock read+輪詢的方式,應用程序基于可以保證正確的處理流程。

          對于3,kernel將這些事件的“通知”通過read/write的結(jié)果返回給應用層。

          假設(shè)A機器上的一個進程a正在和B機器上的進程b通信:某一時刻a正阻塞在socket的read調(diào)用上(或者在nonblock下輪詢socket)

          當b進程終止時,無論應用程序是否顯式關(guān)閉了socket(OS會負責在進程結(jié)束時關(guān)閉所有的文件描述符,對于socket,則會發(fā)送一個FIN包到對面)。

          ”同步通知“:進程a對已經(jīng)收到FIN的socket調(diào)用read,如果已經(jīng)讀完了receive buffer的剩余字節(jié),則會返回EOF:0

          ”異步通知“:如果進程a正阻塞在read調(diào)用上(前面已經(jīng)提到,此時receive buffer一定為空,因為read在receive buffer有內(nèi)容時就會返回),則read調(diào)用立即返回EOF,進程a被喚醒。

          socket在收到FIN后,雖然調(diào)用read會返回EOF,但進程a依然可以其調(diào)用write,因為根據(jù)TCP協(xié)議,收到對方的FIN包只意味著對方不會再發(fā)送任何消息。 在一個雙方正常關(guān)閉的流程中,收到FIN包的一端將剩余數(shù)據(jù)發(fā)送給對面(通過一次或多次write),然后關(guān)閉socket。

          但是事情遠遠沒有想象中簡單。優(yōu)雅地(gracefully)關(guān)閉一個TCP連接,不僅僅需要雙方的應用程序遵守約定,中間還不能出任何差錯。

          假如b進程是異常終止的,發(fā)送FIN包是OS代勞的`,b進程已經(jīng)不復存在,當機器再次收到該socket的消息時,會回應RST(因為擁有該socket的進程已經(jīng)終止)。a進程對收到RST的socket調(diào)用write時,操作系統(tǒng)會給a進程發(fā)送SIGPIPE,默認處理動作是終止進程,知道你的進程為什么毫無征兆地死亡了吧:)

          from 《Unix Network programming, vol1》 3rd Edition:

          “It is okay to write to a socket that has received a FIN, but it is an error to write to a socket that has received an RST.”

          通過以上的敘述,內(nèi)核通過socket的read/write將雙方的連接異常通知到應用層,雖然很不直觀,似乎也夠用。

          這里說一句題外話:

          不知道有沒有同學會和我有一樣的感慨:在寫TCP/IP通信時,似乎沒怎么考慮連接的終止或錯誤,只是在read/write錯誤返回時關(guān)閉socket,程序似乎也能正常運行,但某些情況下總是會出奇怪的問題。想完美處理各種錯誤,卻發(fā)現(xiàn)怎么也做不對。

          原因之一是:socket(或者說TCP/IP棧本身)對錯誤的反饋能力是有限的。

          考慮這樣的錯誤情況:

          不同于b進程退出(此時OS會負責為所有打開的socket發(fā)送FIN包),當B機器的OS崩潰(注意不同于人為關(guān)機,因為關(guān)機時所有進程的退出動作依然能夠得到保證)/主機斷電/網(wǎng)絡不可達時,a進程根本不會收到FIN包作為連接終止的提示。

          如果a進程阻塞在read上,那么結(jié)果只能是永遠的等待。

          如果a進程先write然后阻塞在read,由于收不到B機器TCP/IP棧的ack,TCP會持續(xù)重傳12次(時間跨度大約為9分鐘),然后在阻塞的read調(diào)用上返回錯誤:ETIMEDOUT/EHOSTUNREACH/ENETUNREACH

          假如B機器恰好在某個時候恢復和A機器的通路,并收到a某個重傳的pack,因為不能識別所以會返回一個RST,此時a進程上阻塞的read調(diào)用會返回錯誤ECONNREST

          恩,socket對這些錯誤還是有一定的反饋能力的,前提是在對面不可達時你依然做了一次write調(diào)用,而不是輪詢或是阻塞在read上,那么總是會在重傳的周期內(nèi)檢測出錯誤。如果沒有那次write調(diào)用,應用層永遠不會收到連接錯誤的通知。

          write的錯誤最終通過read來通知應用層,有點陰差陽錯?

          四. 還需要做什么?

          至此,我們知道了僅僅通過read/write來檢測異常情況是不靠譜的,還需要一些額外的工作:

          1. 使用TCP的KEEPALIVE功能?

          cat /proc/sys/net/ipv4/tcp_keepalive_time

          7200

          cat /proc/sys/net/ipv4/tcp_keepalive_intvl

          75

          cat /proc/sys/net/ipv4/tcp_keepalive_probes

          9

          以上參數(shù)的大致意思是:keepalive routine每2小時(7200秒)啟動一次,發(fā)送第一個probe(探測包),如果在75秒內(nèi)沒有收到對方應答則重發(fā)probe,當連續(xù)9個probe沒有被應答時,認為連接已斷。(此時read調(diào)用應該能夠返回錯誤,待測試)

          但在我印象中keepalive不太好用,默認的時間間隔太長,又是整個TCP/IP棧的全局參數(shù):修改會影響其他進程,Linux的下似乎可以修改per socket的keepalive參數(shù)?(希望有使用經(jīng)驗的人能夠指點一下),但是這些方法不是portable的。

          2. 進行應用層的心跳

          嚴格的網(wǎng)絡程序中,應用層的心跳協(xié)議是必不可少的。雖然比TCP自帶的keep alive要麻煩不少,但有其最大的優(yōu)點:可控。

        【TCP/IP網(wǎng)絡編程中socket的行為】相關(guān)文章:

        TCP/IP網(wǎng)絡協(xié)議簡介12-11

        TCP/IP傳輸層12-11

        TCP/IP、Http的區(qū)別10-04

        TCP/IP協(xié)議棧網(wǎng)絡層常見協(xié)議匯總12-11

        TCP/IP協(xié)議是什么10-11

        對TCP/IP網(wǎng)絡協(xié)議的深入淺出歸納10-04

        OSI七層與TCP/IP五層網(wǎng)絡架構(gòu)詳解10-04

        查找本地IP/網(wǎng)絡IP/對方IP地址圖文教程11-20

        TCP/IP三次握手四次揮手過程11-13

        国产高潮无套免费视频_久久九九兔免费精品6_99精品热6080YY久久_国产91久久久久久无码

        1. <tt id="5hhch"><source id="5hhch"></source></tt>
          1. <xmp id="5hhch"></xmp>

        2. <xmp id="5hhch"><rt id="5hhch"></rt></xmp>

          <rp id="5hhch"></rp>
              <dfn id="5hhch"></dfn>