考慮以下情況:
一旦用戶端連接配接到伺服器,伺服器将約定的檔案傳輸給用戶端,用戶端收到後發送字元串「Thank you」給伺服器端。
此處「Thank you」的傳遞是多餘的,這隻是用來模拟用戶端斷開連接配接前還有資料要傳輸的情況。此時程式實作的難度并不小,因為傳輸檔案的伺服器端隻需連續傳輸檔案資料即可,而用戶端無法知道需要接收資料到何時。用戶端也沒辦法無休止的調用輸入函數,因為這有可能導緻程式阻塞。
是否可以讓伺服器和用戶端約定一個代表檔案尾的字元?
這種方式也有問題,因為這意味這檔案中不能有與約定字元相同的内容。為了解決該問題,服務端應最後向用戶端傳遞 EOF 表示檔案傳輸結束。用戶端通過函數傳回值接受 EOF ,這樣可以避免與檔案内容沖突。那麼問題來了,服務端如何傳遞 EOF?
斷開輸出流時向主機傳輸 EOF。
當然,調用 close 函數的同時關閉 I/O 流,這樣也會向對方發送 EOF 。但此時無法再接受對方傳輸的資料。換言之,若調用 close 函數關閉流,就無法接受用戶端最後發送的字元串「Thank you」。這時需要調用 shutdown 函數,隻關閉伺服器的輸出流。這樣既可以發送 EOF ,同時又保留了輸入流。下面實作收發檔案的伺服器端/用戶端。
__fd:需要斷開的套接字檔案描述符。
__how:傳遞斷開方式資訊。
SHUT_RD =不再接收;
SHUT_WR =不再傳輸;
SHUT_RDWR =不再接收和傳輸。
recv讀取sockfd上的資料,buf和len參數分别指定讀緩沖區的位置和大小,flags參數通常設定為0即可。recv 成功時傳回實際讀取到的資料的長度,它可能小于我們期望的長度len。是以我們可能要多次調用recv,才能讀取到完整的資料。recv 可能傳回0,這意昧着通信對方已經關閉連接配接了。recv 出錯時傳回-1并設定errno。
send往sockfd上寫入資料,buf和len參數分别指定寫緩沖區的位置和大小。send成功時傳回實際寫人的資料的長度,失敗則傳回-1并設定ermo。
flags參數為資料收發提供了額外的控制,它可以取表所示選項中的一個或幾個的邏輯或。
recvfrom讀取sockfd上的資料,buf 和len參數分别指定讀緩沖區的位置和大小。因為UDP通信沒有連接配接的概念,是以我們每次讀取資料都需要擷取發送端的socket位址,即參數src_ addr 所指的内容,addrlen 參數則指定該位址的長度。
sendto往sockfd.上寫人資料,buf 和len參數分别指定寫緩沖區的位置和大小。dest addr參數指定接收端的socket位址,addrlen 參數則指定該位址的長度。這兩個系統調用的flags參數以及傳回值的含義均與send/recv系統調用的flags 參數及傳回值相同。值得一提的是,recvfrom/sendto 系統調用也可以用于面向連接配接(STREAM)的socket的資料讀寫,隻需要把最後兩個參數都設定為NULL以忽略發送端/接收端的socket位址(因為我們已經和對方建立了連接配接,是以已經知道其socket位址了)。
msg_name成員指向--個socket位址結構變量。它指定通信對方的socket位址。對于面向連接配接的TCP協定,該成員沒有意義,必須被設定為NULL,這是因為對資料流socket而言,對方的位址已經知道。
msg_namelen成員則指定了msg_name 所指socket位址的長度。
由上可見,iovec結構體封裝了一塊記憶體的起始位置和長度。msg_iovlen指定這樣的iovec結構對象有多少個。對于recvmsg而言,資料将被讀取并存放在msg_ jiovlen 塊分散的記憶體中,這些記憶體的位置和長度則由msg_iov指向的數組指定,這稱為分散讀(satter read);對于sendmsg而言,msg_iovlen 塊分散記憶體中的資料将被-并發送,這稱為集中寫(gather write)。
msg_ control 和msg_ controllen 成員用于輔助資料的傳送。
msg_ fags成員無須設定,它會複制recvmsg/sendmsg的flags參數的内容以影響資料讀寫過程。recvmsg 還會在調用結束前,将某些更新後的标志設定到msg. flags 中。recvmsg/sendmsg的flags 參數以及傳回值的含義均與send/recv的flags參數及傳回值相同。
在實際應用中,我們通常無法預期帶外資料何時到來。好在Linux核心檢測到TCP緊急标志時,将通知應用程式有帶外資料需要接收。核心通知應用程式帶外資料到達的兩種常見方式是: 1O複用産生的異常事件和SIGURG信号。但是,即使應用程式得到了有帶外資料需要接收的通知,還需要知道帶外資料在資料流中的具體位置,才能準确接收帶外資料。這- -點可通過如下系統調用實作:
sockatmark判斷sockfd是否處于帶外标記,即下一個被讀取到的資料是否是帶外資料。如果是,sockatmark 傳回I,此時我們就可以利用帶MSG_0OB标志的reev調用來接收帶外資料。如果不是,則sockatmark傳回0。
getsockname擷取sockfd對應的本端socket位址,并将其存儲于address參數指定的記憶體中,該socket位址的長度則存儲于address_len參數指向的變量中。如果實際socket位址的長度大于address所指記憶體區的大小,那麼該socket位址将被截斷。getsockname 成功時返
回0,失敗傳回-1并設定errno。
getpeemame擷取sockfd對應的遠端socket位址,其參數及傳回值的含義與getsockname的參數及傳回值相同。
sockfd參數指定被操作的目标socket。level 參數指定要操作哪個協定的選項(即屬性),比如IPv4、IPv6、 TCP等。option_ name 參數則指定選項的名字。我們在表中列舉了socket通信中幾個比較常用的socket 選項。option_ value 和option_ len 參數分别是被操作選項的值和長度。不同的選項具有不同類型的值,如表中“資料類型”一列所示。
值得指出的是,對伺服器而言,有部分socket選項隻能在調用listen系統調用前針對業聽socket設定才有效。這是因為連接配接socket隻能由accept調用傳回,而accept從listen聽隊列中接受的連接配接至少已經完成了TCP三向交握的前兩個步驟(因為listen監聽隊列的連接配接至少已進入SYN_ RCVD狀态,這說明伺服器已經往接受連接配接上發送出了TCP同步封包段。但有的socket選項卻應該在TCP同步封包段中設定,比如TCP最大封包段選項。對這種情況,Linux給開發人員提供的解決方案是:對監聽socket設定這些socket選項,那麼accept傳回的連接配接socket将自動繼承這些選項。這些socket選項包括: SO_DEBUG、SO_DONTROUTE、SO_KEEPALIVE、SO_LINGER、SO_OOBINLINE、SO_RCVBUF、SO_RCVLOWAT、sO_SNDBUF、SO_SNDLOWAT、TCP_MAXSEG和TCP_NODELAY。而對用戶端而言,這些socket選項則應該在調用connect函數之前設定,因為connect調用成功傳回之後,TCP三向交握已完成。
IP位址比域名發生變更的機率要高,是以利用IP位址編寫程式并非上策。
h_name:該變量中存有官方域名(Official domain name)。官方域名代表某一首頁,但實際上,一些著名公司的域名并沒有用官方域名注冊。
h_aliases:可以通過多個域名通路同一首頁。同一IP可以綁定多個域名,是以,除官方域名外還可以指定其他域名。這些資訊可以通過 h_aliases 獲得。
h_addrtype:gethostbyname 函數不僅支援 IPV4 還支援 IPV6 。是以可以通過此變量擷取儲存在- - h_addr_list 的IP位址族資訊。若是 IPV4 ,則此變量中存有 AF_INET。
h_length:儲存IP位址長度。若是 IPV4 位址,因為是 4 個位元組,則儲存4;IPV6 時,因為是 16 個位元組,故儲存16。
h_addr_list:這個是最重要的的成員。通過此變量以整數形式儲存域名相對應的IP位址。另外,使用者比較多的網站有可能配置設定多個IP位址給同一個域名,利用多個伺服器做負載均衡,此時可以通過此變量擷取IP位址資訊。
__name:傳入的域名。
調用 gethostbyname 函數後,傳回的結構體變量如圖:
addr: 含有IP位址資訊的 in_addr 結構體指針。為了同時傳遞 IPV4 位址之外的全部資訊,該變量的類型聲明為 char 指針;
len: 向第一個參數傳遞的位址資訊的位元組數,IPV4時為 4 ,IPV6 時為16;
family: 傳遞位址族資訊,ipv4 是 AF_INET ,IPV6是 AF_INET6。
getaddrinfo函數既能通過主機名獲得IP位址(内部使用的是gethostbyname函數),也能通過服務名獲得端口号(内部使用的是getservbyname函數)。它是否可重人取決于其内部調用的gethostbyname和getservbyname函數是否是它們的可重人版本。該函數的定義如下:
hostname參數可以接收主機名,也可以接收字元串表示的IP位址(IPv4 采用點分十進制字元串,IPv6則采用十六進制字元串)。同樣,service 參數可以接收服務名,也可以接收字元串表示的十進制端口号。hints參數是應用程式給getaddrinfo的一個提示,以對getaddrinfo的輸出進行更精确的控制。hints 參數可以被設定為NULL,表示允許getaddrinfo回報任何可用的結果。result 參數指向一個連結清單,該連結清單用于存儲getaddrinfo 回報的結果。
addrinfo結構體中,ai_ protocol 成員是指具體的網絡協定,其含義和socket系統調用的第三個參數相同,它通常被設定為0。ai_fags 成員可以取表中的标志的按位或。當我們使用hints參數的時候,可以設定其ai_flags,ai_family,ai_socktype和ai_protocol四個字段,其他字段則必須被設定為NULL。
getaddrinfo 将隐式地配置設定堆記憶體(可以通過valgrind等工具檢視),因為res指針原本是沒有指向一塊合法記憶體的,是以,getaddrinfo 調用結束後,我們必須使用如下配對函數來釋放這塊記憶體。
getnameinfo函數能通過socket位址同時獲得以字元串表示的主機名(内部使用的是gethostbyaddr函數)和服務名(内部使用的是getservbyport函數)。它是否可重人取決于其内部調用的gethostbyaddr和getservbyport 函數是否是它們的可重人版本。該函數的定義getnameinfo将傳回的主機名存儲在host參數指向的緩存中,将服務名存儲在serv參數指向的緩存中,hostlen和servlen參數分别指定這兩塊緩存的長度。flags參數控制getnameinfo的行為,它可以接收表的選項。

getnameinfo和getaddrinfo函數成功時傳回0,失敗時傳回錯誤碼,可能的錯誤碼如表:
Linux下strerror函數能将數值錯誤碼error轉換成易讀的字元串形式,同樣下面的函數可将表錯誤碼轉換成字元串形式。