1. socket阻塞和非阻塞redis
關於阻塞仍是非阻塞,socket通常須要考慮的狀況有如下3種:編程
<1> connect 緩存
<2> read網絡
<3> write併發
當socket進行connect的時候,會有一個三次握手的機制。connect函數也是隻有接收到本身發送的SYN的ACK以後纔會返回。這時候若是網絡環境發生擁堵,RTT時間就會比較長,connect被阻塞住的時間也就會很長(TCP默認超時時間的典型值是75s)。並且,平時業務代碼中,每每不會只創建一個socket鏈接,每一個client會和多個server創建多個鏈接。若是每次調用connect都阻塞這麼長時間,那麼業務代碼會堵到讓人難以接受。這時候給connect調用設置一個本身能夠接受的超時時間就頗有必要了。好比connect函數若是3秒以內尚未鏈接成功,我就終止connect操做,根據業務邏輯來進行下一步操做。connect調用沒有像SO_RCVTIMEO和SO_SNDTIMEO這樣的參數能夠設置超時時間,因此通常的解決辦法是使用select來解決。具體怎麼實現,網上的例子有不少,這裏就再也不細講。主要的操做就是將socket設置爲非阻塞,並註冊到select監控的可寫文件描述符列表。調用select時傳入能夠接受的TIMEOUT。若是select最後的返回值爲0,則說明超過TIMEOUT,socket仍是沒有創建好鏈接。若是返回顯示socket可寫,則說明鏈接創建完成。可是通常還須要作一個二次檢查,調用getsockopt取得套接字的待處理錯誤,若是創建成功,該值將爲0。select是阻塞操做,一樣也會阻塞住線程,可是能夠設置timeout。這樣就繞過了直接調用connect沒法設置TIMEOUT而長時間阻塞線程的狀況。並且這個技術常常被用到並行創建socket鏈接上,由於socket設置非阻塞後,connect會當即返回。這時接連調用屢次connect與多個server發起鏈接,同時把這些socket都由select進行TIMEOUT監控。這樣就作到了並行發起鏈接的效果。異步
read 和 write 由於有SO_RCVTIMEO和SO_SNDTIMEO,在socket爲阻塞的狀況下能夠直接設置TIMEEOUT,因此比較簡單。這裏主要須要注意的地方是,在socket爲阻塞和非阻塞的狀況下,read和write的返回值的差別。socket
(1)socket爲阻塞,且沒有設置SO_RCVTIME和SO_SNDTIMEO。對於read,若是請求的數據長度爲1024,可是socket緩衝區中只有1000的數據,那麼read會當即返回,而且返回值爲1000。若是socket緩衝區中沒有數據,那麼read會一直等待下去,除非對端關閉socket,read會當即返回,而且返回值爲0。read還有一個參數是MSG_WAITALL,在阻塞模式下不等到指定數目的數據不會返回,除非超時時間到,或者對端關閉socket。對於write,若是請求寫入的數據長度爲1024,可是socket緩衝區只有1000的剩餘,則write會一直等待,直到數據所有寫入socket緩衝區,並返回寫入文件的長度。若是出錯,則返回-1。函數
(2)socket爲阻塞,可是設置了SO_RCVTIMEO和SO_SNDTIMEO。對於read和write,若是在超時時間內socket緩衝區中沒有數據到來,或者緩衝區中一直沒有騰出來可讓write寫完數據的空間,那麼read和write都會返回-1,而且將errno設置爲EAGAIN | EWOULDBLOCK 。若是在超時時間內完成了read或者write,則返回值和阻塞時無區別。操作系統
(3) socket爲非阻塞。 對於read,若是socket緩衝區中沒有數據到來,那麼read當即返回-1,而且將errno設置爲EAGAIN | EWOULDBLOCK。對於write,若是請求寫入數據爲1024,而socket緩衝區只有1000的空間,那麼write會當即返回1000。線程
2. 使用TCP和UDP發送0字節數據
TCP和UDP均可以發送0字節數據並正常返回。區別在於TCP發送0字節數據後,對端接收不到。而UDP的對端能夠接收。
緣由是,TCP自己是面向鏈接和麪向流的,若是recv函數返回0,則表明對端已經將socket關閉,應該是這種機制致使設計recv函數時,不能接收0字節數據。並且TCP協議send的時候,是把數據放入TCP緩存中。若是發送的數據是0字節,那麼壓根就不會有數據放在緩存,也不會有數據發送出去,因此對端接收不到。
3. send函數使用注意事項
當給斷開的socket寫數據的時候,寫第一次,對端會返回一個rst報文。當寫第二次時,系統還會給進程發一個SIGPIPE。這個信號的默認處理方式就是結束進程。咱們能夠在send函數的最後一個參數加上MSG_NOSIGNAL。這樣系統就不會發送這個信號給進程了。
4. 同步和異步, 阻塞和非阻塞概念區別
關於這兩組概念之間的區別,感受是由於描述的東西行爲上比較相像,並且這幾個名詞在不一樣的場景下會表明不一樣的意思,因此讓人特別容易混淆。
知乎上這位大神總結的能夠說是很是透徹了 https://www.zhihu.com/question/19732473。
同步和異步關注的點是消息傳遞機制,而阻塞和非阻塞關注的點是程序在等待結果時的狀態。
阻塞和非阻塞在日常調用一些接口的時候,這些接口自己就有實現。好比read和write,會根據socket是否爲阻塞的來進行相應的操做。
而同步和異步與業務邏輯結合的比較緊密,沒有固定的實現方式。
一樣一個業務,好比如今有client A 和 server B, B上有一些數據,A須要定時拖下來,並存儲到redis中。
若是使用同步的方式實現,那麼就是,A的主線程先請求B,從B上把數據拉下來。而後主線程再請求redis,把剛剛拉下來的數據存儲到redis中。
可是若是這個拖數據下來須要的時間很長,好比1個小時。可是A的主線程不想等待,想要當即返回,怎麼辦?這時後就必須使用異步的方式實現這個業務了。
<1> 使用線程池實現
把A從B上拖數據這個操做另起線程,讓新的線程阻塞住拉B的數據,而後主線程返回幹其餘的事。
<2> 使用事件驅動
假設給A中維護一個任務隊列,主線程把A從B上拖數據做爲一個task推入任務隊列中以後就當即返回。同時存儲redis做爲這個task的回調。線程池中的線程會從任務隊列中取task去執行,把數據從B拉下來以後,觸發事件,調用存儲redis的回調。
這兩個實現是我我的比較常見的形式。區別就是,當A須要並行從B拉互不相干的不少份數據時,會有不少個線程同時阻塞在拉數據的操做上。開着線程對操做系統來講是有開銷的。第二種方法雖然也是線程池,可是線程池中線程的個數能夠定製,能夠少開一些線程。對A的主線程來講,把不少個任務放入任務隊列中就能夠返回了,而不是像方法一同樣來多少個任務,就必須開多少個線程以後才能夠返回。可是方法二相對方法一來講,編程會比較複雜,若是併發量不是太大,方法一也是一個很好的選擇。
以上的東西是我的理解和總結,不免有理解錯誤和疏忽之處,歡迎你們改正。若是喜歡本文章,轉載請註明原文出處,感謝配合~