TCP Socket通訊詳細過程

  下面這篇文章是參考"駿馬金龍"博客中
    不可不知的socket和TCP鏈接過程 http://www.javashuo.com/article/p-vjuhpvth-cv.html
  這篇博文對個人啓發很大,但文中比較核心一些東西說明的不是很是詳細,致使整片文章對於初學者仍是
 難度太大,這篇文章引用了部分該文中的內容,主要是爲了可以讓整篇文章能連貫,一便讓更多想要深刻計算機
 原理的人,有更多參考,能更快明瞭一個Client發送一個訪問Web服務器的請求,到底WebServer如何處理接收
 到請求報文,並最終完成整個通訊過程,這篇文章將深刻說明,但因爲本人能力有限,不少理解是本身經過對CPU
 內存,網絡的理解推演出來的,沒有很確鑿的證據證實,也但願大牛路過,多給予指教,不要讓我誤人子弟了。
 很是感謝!同時也很是感謝認真的學習道友"駿馬金龍",讓我能將積累的知識連貫起來,用最簡單的語言盡最大
 可能將這個複雜過程,說的儘量清楚。html

  說明:node

    此篇是TCP深刻刨析的上篇,核心點是說明Clinet和Server如何完成TCP三次握手。
    TCP深刻刨析是下篇,核心點是說明Client和Server是如何完成數據傳輸的。linux

 

  首先來一張TCP socket通訊過程圖編程

  

 socket()函數:
    就是生成一個用於通訊的套接字文件描述符sockfd(socket() creates an endpoint for communication and returns a descriptor)。
  這個套接字描述符能夠做爲稍後bind()函數的綁定對象。
    socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 這是socket函數在內核源碼中定義的函數,從這個結構中可看出來,
  socket函數僅建立了一個socket結構文件,但並無關聯任何IP和端口,其中AF_INET是AddressFamily(地址族)是Inernet
  網絡地址,SOCK_STREAM,是指定socket接受數據格式,共有兩種,一種是stream(流,),另外一種是dgram(數據報),最後
  一個是協議TCP/UDP.不少時候,內核其實根據AF_INET和SOCK_STREAM就能夠推演出應該使用TCP,所以
  IPPROTO_TCP/UDP可省略,但這並非好習慣。

bind()函數:
    服務程序經過分析配置文件,從中解析出想要監聽的地址和端口,再加上能夠經過socket()函數生成的套接字sockfd,
  就可使用bind()函數將這個套接字綁定到要監聽的地址和端口組合"addr:port"上。綁定了端口的套接字能夠做爲listen()
  函數的監聽對象。綁定了地址和端口的套接字就有了源地址和源端口(對服務器自身來講是源),再加上經過配置文件中
  指定的協議類型,五元組中就有了其中3個元組。即:{protocal,src_addr,src_port} 可是,常見到有些服務程序能夠
  配置監聽多個地址、端口實現多實例。這實際上就是經過屢次socket()+bind()系統調用生成並綁定多個套接字實現的。
    所謂五元組,即 {protocal, Server_src_addr, Server_src_port, Client_dest_addr, Client_dest_port}
    當還處於監聽狀態時,套接字稱爲監聽套接字,此時它只包含三元組。
    TCP報文結構:緩存

      

      當客戶端與服務器經過三次握手建鏈,同步了TCP保障會話的狀態序列號(Sequence Number),
   窗口大小(Window Size)以及Client的源IP和源端口,這是服務器端的監聽套接字就能夠構建成完整的
   專用鏈接套接字,即五個關鍵元素組成了新的socket。固然Client也會生成本次於Server通訊的專用
   鏈接套接字。注意:是專用鏈接套接字

  補充說明:
    TCP之因此稱爲可靠鏈接就是因爲其包含Sequence number和Acknowledgment number,
   TCP經過它們來實現數據傳輸的確認機制,簡單理解以下:

    #這裏爲方便說明,將漢字,單詞 和 點直接簡單認爲是1個數據.
    #ack=1,是假設Server三次握手最後一次傳遞的seq=0,我要對它發給個人數據作確認
    Client----[我想獲取index.html, seq=7,ack=1]----------------->Server

    Server---[index.html{歡迎來到咱們的網站.},seq=12,ack=8]--->Client
    Server---[{學習園地.},seq=17,ack=8]--------------------------->Client

    這裏爲了方便說明,但並不是徹底準確

    假設Server要傳遞給Client的數據量很大,被拆分紅了多個包,這裏僅以兩個爲例說明,
    第一個報文總長度爲12個字,假設已經滿了,而後又生成了第二個數據包長度5個字,
   這時它們的序列號須要注意,你會發現其實序列號就是傳輸數據量的說明,即我此次給你傳了
     多少數據,假設網絡情況不是很好,第一個包在傳輸過程當中丟失了,Client收到了第二個報文,
     Client解包後,發現這個序列號是13-17,而後,Client就會給Server發送一個ACK報文,這個報文
     僅對13-17段數據作確認,Server收到後,過了最大RTT(數據包從Server到Client直接最大往返時間)
     發現Client仍是沒有對1~12這段數據作確認,因而知道第一個報文在網絡中傳輸時丟包了,而後
     就會僅將1~12這段數據再次發送給Client,當Server在RTT以前收到Client對這段數據的ACK,
    則認爲通訊完成,等待Client後續的請求,若長鏈接超時,Client沒有再次發起請求,則Server將
    主動斷開鏈接,而後進入TCP的四次揮手階段。
    注意:這裏確認13~17這段數據時是這樣的:
      Client--[seq=9,ack=18]------------------------------------>Server
      #Server是發送數據者,它知道本身發了那些數據,Server看到18,對比發送列表,1~12,13~17,
        這個ACK包確認的必定是第二個包,由於第一個包的ACK seq應該是13,而不是17.

  說明:
    實際上一個數據包的大小是由網卡上的MTU值決定,默認MTU是1500個字節,去掉TCP/IP協議棧
    封裝頭部大概是1446個字節,而後還要去掉上層不一樣協議封裝的頭部字節數,剩下才是這個數據包實際
    能裝多少數據,但這個仍是額定數據量,正式往數據包中裝數據還要看窗口大小(window size),它是
  Client和Server之間協商出來的,由於Server或Client均可能由於某些緣由接收不了不少數據,所以爲了
  能通訊,在通訊前是必須互相告知本身一次最多能接受多少數據的。
  MTU(最大傳輸單元): 如今網絡中全部設備都默認是1500字節,一個數據包在網絡中傳輸最大必須
      是1500字節,只要超過就會被網絡設備切片後,從新封裝再發生,但前提是IP包中容許分片
      位是1,即容許分片,不然該數據包將被丟棄。

listen()函數:
  int listen(int sockfd, int backlog); 這是內核源碼中listen函數的定義,sockfd 就是bind函數關聯後的套接字文件描述符。
  backlog:
    Linux Kernel2.2之前,backlog 用於設置上圖中未完成和已完成隊列的最大總長度(其實是隻有一個隊列,
    但分爲兩種狀態),實際目前這種是BSD衍生的一種套接字類型,它採用了一個隊列,在這單個隊列中存放
    3次握手過程當中的全部鏈接,可是隊列中的每一個鏈接分爲兩種狀態:syn-recv和established。服務器

  Linux Kernel2.2開始,這個參數只表示已完成隊列(accept queue)的最大長度,而/proc/sys/net/ipv4/tcp_max_syn_backlog
  則用於設置未完成隊列(syn queue/syn backlog)的最大長度。/proc/sys/net/core/somaxconn則是硬限制已完成隊列
  的最大長度,默認爲128,若是backlog參數大於somaxconn,則backlog會被截短爲該硬限制值。
  參考下圖: 此圖來自http://www.javashuo.com/article/p-emznafsw-bk.html網絡

    

     說明: 圖中提到的分片,實際應該稱爲IP報文,不要與IP分片混淆。


  下面說明listen函數,已httpd爲參考來講明,這樣更便於說明問題;另外下面說明並不必定徹底正確,多數是根據個人理解
 推演出來的,因此下面說明僅供參考,但願大牛路過,能給予指點,不要讓我誤人子弟了。
    當httpd進程被啓動後,它經過讀取配置文件,獲取到要監聽的地址和端口,完成socket和bind後,就進入到listen函數了,
   listen系統調用會向內核管理的socket set(集合)中註冊本身的監聽套接字,而後返回,此時做爲httpd的perforce模型的話,
   它將調用select()函數,此函數會發起系統調用來獲取內核管理的socket set,並檢查其中本身所關心的socket是否處於就緒態,
   便是否變爲可讀,可寫,異常; 若非就緒態,就繼續過會兒在檢查,這期間httpd將處於阻塞狀態的,直到httpd所分配的CPU時間片
   都耗盡,CPU會將httpd從CPU上轉入內存處於睡眠,等待下次被調度到CPU上執行;
   假設如今有用戶發起訪問,則大體過程以下:
   首先,服務器網卡接收到數據流後,會馬上給CPU發送中斷信號,CPU收到後,會當即將手頭正在處理的事務
  所有掛起,並當即檢測網卡上是否有DMA芯片,如有就直接發送指令告訴DMA芯片,你將數據流複製到指定的DMA_ZONE的
  核心內存區中的指定地址段中,而後DMA芯片就開始複製,CPU從新恢復掛起的進程,繼續處理,當網卡DMA芯片再次發送
  中斷告訴CPU我複製完了,此時CPU將當即掛載正在處理的進程,並激活內核,內核得到CPU控制權後,啓動TCP/IP協議棧
  驅動處理接收到的數據包,解封裝後,發現是一個要訪問本機80套接字的SYN請求,因而內核檢查socket set發現有這樣的監聽
  套接字,因而內核將SYN數據複製到Kernel buffer中,進行進一步處理(如判斷SYN是否合理),而後準備SYN+ACK數據完成
  後通過TCP/IP協議棧驅動封裝IP頭,鏈路層幀頭,最終這個數據被寫入到send buffer中,並當即被複制到網卡傳送出去,同時
  內核還會在鏈接未完成隊列(syn queue)中爲這個鏈接建立一個新項目,並設置爲SYN_RECV狀態。
  接着內核進入睡眠,CPU檢查被掛起的進程的時間片是否耗盡,沒有就將其調度到CPU上繼續執行,不然繼續將其它用戶空間
  的進程調度到CPU上執行,當Client收到Server的響應後,迴應了ACK報文,Server的網卡收到後,又會繼續上面的動做,CPU
  會再次掛起正在處理的進程,並將數據接收進來,複製到指定的kernel buffer中(注:DMA_Zone也是Kernel buffer的一部分,
  這裏不嚴格區分它們的區別) 接着CPU會喚醒內核,又它調度TCP/IP協議棧驅動處理收到的數據包,當解封裝後,發現是一個
  ACK報文,而且數據段大小爲0,這時內核會去檢查未完成鏈接隊列,若找到與該ClientInfo(客戶端信息)一致的鏈接信息,則
  將該鏈接從未完成鏈接列表中刪除,而後在已完成鏈接隊列中插入該鏈接信息,並標記狀態爲ESTABLISHED,接着將內核中
  維護的socket set中80監聽套接字的狀態更爲爲可讀,隨後內核讓出CPU,進入睡眠,CPU繼續將掛起任務載入CPU上執行,
  若CPU時間片用完,則將其轉入內存進入睡眠,繼續下一個用戶空間的進程,假設此時調度httpd進程到CPU上執行,它依然
  是發起select系統調用,此時內核被喚醒,httpd被掛起,內核將根據select的要求,返回內核中socket set的所有狀態集,
  而後,內核進入睡眠,CPU將httpd調入CPU上執行,此時select開始遍歷獲取到的socket set集合,當找到本身監聽的
  socket狀態爲可讀時,它將當即解除阻塞,並調用accept()系統調用,此時內核再次被喚醒,而後根據accept的要求,將
  已完成鏈接隊列中與本身創建鏈接的ClientInfo信息取出來,並刪除隊列中的信息,而後根據監聽套接字,生成一個新的
  專用鏈接套接字,接着將該套接字註冊到內核管理的socket set中,最後將該專用鏈接套接字返回,接着內核進入睡眠,
  CPU再次調度httpd進入CPU上執行,httpd獲取到專用鏈接套接字的文件描述符後,將其分配其中一個子進程,由子進程
  來完成與該用戶的後續數據交互,主進程則繼續監控監聽套接字。
  若此時httpd的CPU時間片用完了,CPU將會把httpd轉入內核睡眠,而後繼續其它用戶空間的進程;假如此刻Client
  請求網站主頁的數據包到達Server網卡了,網卡依然會採用上面的動做,內核依然會被喚醒,而後內核會調度TCP/IP協議
  處理數據包,當解封裝後,發現這個數據包是ACK報文,而且數據段大於0,此時內核知道這是一個已經完成的鏈接請求數據包,
  因而根據請求報文中的 {源IP,源Port,目標IP,目標Port,協議} 去遍歷查找socket set,若找到對應的專用socket,則將數據
  拷貝到socket buffer中,並將專用socket的狀態設置爲可讀,而後,內核進入睡眠,CPU繼續掛起的任務,當httpd的子進程
  被調到到CPU上執行時,它經過select系統調用去檢查本身監聽的專用套接字時,發現本身關心的套接字爲可讀,因而當即
  解除阻塞,調用recvform系統調用,讀取數據,此時內核會被喚醒,完成將socket buffer中的數據拷貝到該進程的的內存
  空間中,而後內核進入睡眠,CPU將httpd子進程調度到CPU上繼續執行,httpd子進程讀取數據,分析後知道用戶要請求
  網站主頁資源,因而再次發起系統調用,獲取磁盤中存儲的主頁數據,此時內核被喚醒,而後內核調度磁盤驅動,若該磁盤
  上面有DMA芯片,則內核會直接告訴DMA芯片,你將磁盤中指定柱面,指定扇區,指定磁道上的數據複製到指定的DMA_ZOME
  中指定的內存區中,而後,內核進入睡眠,CPU繼續調度其餘進程到CPU上執行。 可是若磁盤上沒有DMA芯片,那麼內核
  將自行調度磁盤驅動讀取磁盤數據,並等待磁盤驅動完成數據從磁盤拷貝到內核kernel buffer中,再這期間kernel將被阻塞,
  直到數據拷貝完成,而後,內核進入睡眠,CPU再將httpd子進程調度到CPU上執行,若時間片用完,則再將其調度到內存
  睡眠,不然就繼續讓httpd子進程執行,假如此時時間片沒有耗盡,httpd子進程將會再次發起系統調用,讓內核將kernel buffer
  中的數據拷貝到本身的內存空間中,因而CPU再次將其掛起,內核完成拷貝後,再次進入睡眠,httpd子進程再次被調到
  CPU上執行,而後httpd子進程開始將數據封裝上http首部,構建成響應報文後,再次發起系統調用,讓內核將數據複製到
  send buffer中,此時httpd子進程被掛起,內核開始將進程內存空間中的數據拷貝到內核內存空間中的send buffer中,準備
  調度TCP/IP協議棧驅動對報文作TCP首部,IP首部,鏈路層幀頭等封裝,最終這個數據包構建完成,被內核發往網卡的緩衝區
  中等待發送,固然若網卡上有DMA芯片,內核依然可以讓DMA芯片來複制數據,完成發生,本身就去睡眠。若沒有就只能本身幹。
  以上描述就是HTTP通訊的一個縮影。【文末有兩段代碼,可合起來看,基本就是相似httpd的代碼實現,摘錄僅爲了方便理解
  不至於太空洞,而感受沒有依據似的。】
  注:
  上面描述了select()系統函數,這是最簡單的一種多路複用I/O調用,還有poll和epoll,這兩個系統調用,也屬於多路複用I/O
 模型,但poll和select基本相似,因爲能力有限,對它們之間的區別理解很淺薄,僅知道它們在獲取socket set時,彷佛select
 是用列表方式,而poll是鏈表方式,彷佛於此有關,致使poll沒有最大1024的限制,而select由於在kernel編譯時,就設置其
 最大值爲1024,即只能同時接收1024個鏈接,但具體理解不深,如有大牛知道,也但願能分享博文。
  下圖是select模型系統調用說明圖:併發

    

         關於epoll個人理解也不是很深入,僅作如下說明,但理解poll和epoll的前提,仍是要先理解select.
  下圖是epoll這種基於事件驅動的I/O模型工做示意圖:異步

    

    簡圖:socket

      

 更高級的網絡I/O模型是AIO,其實現原理很便於說明,但細節我理解不深:
  異步非阻塞I/O模型
   相對於同步IO,異步IO不是順序執行。用戶進程進行aio_read系統調用以後,不管內核數據是否準備好,都會直接
  返回,不會阻塞用戶進程,而後用戶進程能夠繼續接受新的鏈接請求。等到socket數據準備好了,內核會直接複製
  socket buffer中的數據複製到用戶進程空間後,內核纔會找到用戶進程留下的聯繫方式(即:通知信號)向進程發送通知。
  能夠看到IO的兩個階段,進程都是非阻塞的。 Linux 內核提供了AIO庫函數的實現,可是用的不多。目前有不少開源
  的異步IO庫,例如libevent、libev、libuv。異步過程以下圖所示:
  【注意:這裏方式實現起來極其複雜,可是Nginx是徹底支持這個方式的。】

    

    簡圖:

      

另外說明:
  1. 助理:即DMA機制
    就拿硬盤來講,CPU須要讀磁盤中的一段數據時,它發現磁盤支持DMA,則CPU會受權給DMA容許訪問系統總線,
  並告訴它將磁盤中的那部分數據放到內存中指定的地方,接着CPU就無論了,由DMA來完成數據搬運,並在完成時,
  向CPU發中斷 報告完成。
   這裏須要注意:
    1. CPU經過32根線才完成了訪問4G的內存空間,那DMA要訪問內存也須要32根線嗎?
      固然不是,DMA的總線是很窄的,所以爲了讓DMA可訪問內存,系統在設計時,就將RAM中低地址中
     的一段空間預留個DMA使用,它一般是16M;其中RAM的起始區中第一個1M區域是固定給BIOS使用的,
     由於CPU在製造時,就設定了只要開機,CPU首先去讀取RAM中0地址開始的連續的1M區域,來完成
     處理BIOS映射到裏面的指令,實現開機自檢。DMA設計時也是會去訪問內存中固定的地址區域,實現
     高效傳輸。但須要注意的是,每次系統要DMA工做前,都會事先騰出DMA將訪問的內存區域。

    2.當前httpd, Nginx等Web服務器都已經有更先進的技術,如sendfile,mmap,這些機制,可以讓上面繁瑣
    磁盤數據拷貝過程變得更加高效,
    這裏不展開說明,僅簡單說明以下:

    Nginx支持Sendfile方式響應靜態網頁:Linux中支持 sendfile 和 Sendfile64
    正常狀況下:當用戶發來請求後,內核收到網卡中斷處理數據流,判斷爲http數據流,告知將該數據流將給監聽在
      80 Socket的應用程序這時,Nginx的Master進程監聽到鏈接請求,並將該鏈接請求將給Worker進程,來創建
      Http會話響應用戶,經過解析請求發現用戶請求的是靜態網頁,接着Worker請求向內核發起I/O請求,內核爲
      該請求準備Buffer,並向磁盤請求數據,一般由DMA(直接內存訪問)控制芯片接收內核請求(請求中一般會包含
      讓DMA將數據Copy到那段Buffer空間),並從磁盤中讀取數據,並Copy到Buffer中,完成後,向內核發送中斷信號,
      通知內核數據準備完成,接着內核將數據copy到Nginx的進程內存空間(注:Nginx默認採用epoll,信號驅動I/O
      模型)完成後,通知Worker進程Worker進程對數據作處理後,接着又向內核發起請求,要求內核將處理後的
      數據封裝http頭,TCP頭, IP頭, 並最終發給用戶。

   Sendfile方式:在這種方式下,當網卡接收到數據流後,發送中斷給內核,內核處理後通知網卡將數據發給80 Socket
      上監聽的應用程序接着,Nginx的Master進程監聽到鏈接請求,並負責向該請求分配Worker進程,來創建
      HTTP會話鏈接,Worker進程分析該請求後,是要請求靜態網頁數據,接着向內核發起I/O請求,並告知
      內核這是請求靜態頁面的,你直接將數據封裝HTTP包響應用戶便可,不需在把數據給我了。接着內核向
      磁盤請求數據,獲得數據後,直接將用戶請求的數據封裝HTTP頭,TCP頭,IP頭,數據鏈接層頭,
      完成後,直接響應用戶。

  對比兩種方式不難發現,Sendfile方式更高效,由於它去掉了I/O請求中兩次COPY的過程,在高併發的場景中是很是高效的。
  注意:sendfile:僅支持很小的文件直接在內核封裝並響應用戶,而sendfile64則支持更大的文件在內核中直接封裝並響應用戶。
  【 注意:內核任什麼時候候與進程交換並傳遞數據時,採用的方式都時Copy,除非指定使用共享內存。】

    

  

mmap和常規文件操做的區別

  這段是摘自參考文章,這篇博文詳細介紹了MMAP:
    http://www.javashuo.com/article/p-ctyjjbvt-m.html
    對linux文件系統不瞭解的朋友,請參閱我以前寫的博文《從內核文件系統看文件讀寫過程》,咱們首先簡單的回顧一下   常規文件系統操做(調用read/fread等類函數)中,函數的調用過程:     一、進程發起讀文件請求。     二、內核經過查找進程文件符表,定位到內核已打開文件集上的文件信息,從而找到此文件的inode。     三、inode在address_space上查找要請求的文件頁是否已經緩存在頁緩存中。若是存在,則直接       返回這片文件頁的內容。     四、若是不存在,則經過inode定位到文件磁盤地址,將數據從磁盤複製到頁緩存。以後再次發起       讀頁面過程,進而將頁緩存中的數據發給用戶進程。     總結來講,常規文件操做爲了提升讀寫效率和保護磁盤,使用了頁緩存機制。這樣形成讀文件時須要    先將文件頁從磁盤拷貝到頁緩存中,因爲頁緩存處在內核空間,不能被用戶進程直接尋址,因此還須要將    頁緩存中數據頁再次拷貝到內存對應的用戶空間中。這樣,經過了兩次數據拷貝過程,才能完成進程對       文件內容的獲取任務。寫操做也是同樣,待寫入的buffer在內核空間不能直接訪問,必需要先拷貝至內核      空間對應的主存,再寫回磁盤中(延遲寫回),也是須要兩次數據拷貝。   下面是個人理解,僅供參考:     而使用mmap操做文件中,建立新的虛擬內存區域和創建文件磁盤地址和虛擬內存區域映射這兩步,沒有      任何文件拷貝操做。而以後訪問數據時發現內存中並沒有數據而發起的缺頁異常的過程(即:從物理內存的緩衝區      中查找是否有須要的已打開文件,若無),則會經過已經創建好的映射關係,只使用一次數據拷貝,就從磁盤    中將數據傳入內存的用戶空間中,供進程使用。     總而言之,常規文件操做須要從磁盤到頁緩存再到用戶主存的兩次數據拷貝。而mmap操控文件,只須要    從磁盤到用戶主存的一次數據拷貝過程。說白了,mmap的關鍵點是實現了用戶空間和內核空間的數據直接    交互而省去了空間不一樣,數據不通的繁瑣過程。所以mmap效率更高。 下面這部分代碼是我從網上摘錄的,僅爲方便理解socket,bind,listen和select#include <sys/socket.h>#include <stdio.h>#include <string.h>#include <netinet/in.h>#include <stdlib.h>#include <arpa/inet.h>int main(int argc,char** argv){ int ret; int listenfd = socket(AF_INET,SOCK_STREAM,0); if (listenfd == -1) { printf("socket error\n"); return -1; } struct sockaddr_in serveraddr; memset(&serveraddr,0,sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); serveraddr.sin_port = htons((unsigned short)(atoi(argv[1]))); ret = bind(listenfd,(const sockaddr*)&serveraddr,sizeof(serveraddr)); if (ret == -1) {   printf("bind error,ret = %d\n",ret);   return -1; } int backlog = atoi(argv[2]); ret = listen(listenfd, backlog); printf("backlog = %d,ret =%d\n",backlog,ret); if (ret == -1) {   printf("listen error,ret = %d\n",ret);   return -1; } for(;;) {}    #這部分,應該可填充下面這段代碼,因此可參考下面的代碼。【我並非很懂】 return 0;}============這是另外一篇中的部分代碼摘錄============= /* 假定已經創建UDP鏈接,具體過程不寫,簡單,固然TCP也同理,主機ip和port都已經給定,要寫的文件已經打開 sock=socket(...); bind(...); fp=fopen(...); */ while(1) {   FD_ZERO(&fds); //每次循環都要清空集合,不然不能檢測描述符變化   FD_SET(sock,&fds); //添加描述符   FD_SET(fp,&fds); //同上   maxfdp=sock>fp?sock+1:fp+1; //描述符最大值加1   switch(select(maxfdp,&fds,&fds,NULL,&timeout)) //select使用   {     case -1: exit(-1);break; //select錯誤,退出程序     case 0:break; //再次輪詢     default:     if(FD_ISSET(sock,&fds)) //測試sock是否可讀,便是否網絡上有數據     {       recvfrom(sock,buffer,256,.....);//接受網絡數據       if(FD_ISSET(fp,&fds)) //測試文件是否可寫       fwrite(fp,buffer...);//寫入文件       buffer清空;     }// end if break;   }// end switch  }//end while }//end main 仔細結合上面對listen,select函數的說明,在參考這個兩段代碼,再去細細思考整個TCP通訊過程,相信你會有本身的理解。本人對C編程並不熟悉,但大概能看明白,所以不作說明,請自行選看。

相關文章
相關標籤/搜索