UNIX網路程式設計 基本TCP套接字程式設計

2021-06-19 17:20:56 字數 3380 閱讀 7460

一、基於tcp協議的網路程式

下圖是基於tcp協議的客戶端/伺服器程式的一般流程:

伺服器呼叫socket()、bind()、listen()完成初始化後,呼叫accept()阻塞等待,處於監聽埠的狀態,客戶端呼叫socket()初始化後,呼叫connect()發出syn段並阻塞等待伺服器應答,伺服器應答乙個syn-ack段,客戶端收到後從connect()返回,同時應答乙個ack段,伺服器收到後從accept()返回。

資料傳輸的過程:

建立連線後,tcp協議提供全雙工的通訊服務,但是一般的客戶端/伺服器程式的流程是由客戶端主動發起請求,伺服器被動處理請求,一問一答的方式。因此,伺服器從accept()返回後立刻呼叫read(),讀socket就像讀管道一樣,如果沒有資料到達就阻塞等待,這時客戶端呼叫write()傳送請求給伺服器,伺服器收到後從read()返回,對客戶端的請求進行處理,在此期間客戶端呼叫read()阻塞等待伺服器的應答,伺服器呼叫write()將處理結果發回給客戶端,再次呼叫read()阻塞等待下一條請求,客戶端收到後從read()返回,傳送下一條請求,如此迴圈下去。

如果客戶端沒有更多的請求了,就呼叫close()關閉連線,就像寫端關閉的管道一樣,伺服器的read()返回0,這樣伺服器就知道客戶端關閉了連線,也呼叫close()關閉連線。注意,任何一方呼叫close()後,連線的兩個傳輸方向都關閉,不能再傳送資料了。如果一方呼叫shutdown()則連線處於半關閉狀態,仍可接收對方發來的資料。

在學習socket api時要注意應用程式和tcp協議層是如何互動的:

*應用程式呼叫某個socket函式時tcp協議層完成什麼動作,比如呼叫connect()會發出syn段

*應用程式如何知道tcp協議層的狀態變化,比如從某個阻塞的socket函式返回就表明tcp協議收到了某些段,再比如read()返回0就表明收到了fin段

補充一下,其實tcp 共有11種狀態,上圖沒有出現的closing 狀態,當雙方同時關閉連線時會出現此狀態,替換掉fin_wait2狀態。

二、基本socket函式

1、socket函式

包含標頭檔案

功能:建立乙個套接字用於通訊

原型:int socket(int domain, int type, int protocol);  

引數domain :指定通訊協議族(protocol family),af_inet、af_inet6、af_unix等

type:指定socket型別,流式套接字sock_stream,資料報套接字sock_dgram,原始套接字sock_raw

protocol :協議型別,ipproto_tcp等;一般由前兩個引數就決定了協議型別,設定為0即可。

返回值:成功返回非負整數, 它與檔案描述符類似,我們把它稱為套介面描述字,簡稱套接字。失敗返回-1

2、bind函式

包含標頭檔案

功能:繫結乙個本地位址到套接字

原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);  

引數sockfd:socket函式返回的套接字

addr:要繫結的位址

addrlen:位址長度

返回值:成功返回0,失敗返回-1

如果乙個tcp客戶或者伺服器未曾呼叫bind**乙個埠,當呼叫connect或listen時,核心就要為相應的套接字選擇乙個臨時埠。讓核心來選擇臨時埠對於tcp客戶來說是正常的,除非應該需要乙個預留埠然而對於tcp伺服器來說卻極為罕見,因為伺服器是通過它們的眾所周知埠被大家認識的。

呼叫bind可以指定ip位址或埠,可以兩者都指定,也可以都不指定。

如果指定埠號為0,那麼核心就在bind被呼叫時選擇乙個臨時埠。然而如果指定ip位址為通配位址,那麼核心將等到套接字已連線(tcp)或已在套接字上發出資料報(udp)時才選擇乙個本地ip位址。

對於ipv4來說,統配位址由常值inaddr_any來指定,其值一般為0.

struct sockaddr_in servaddr;  

servaddr.sin_addr.s_addr = htonl(inaddr_any);  

其實無論是網路位元組序還是主機位元組序,inaddr_any的值(為0)都是一樣的,因此使用htonl並非必需。

為了得到核心選擇的臨時埠值,必須呼叫函式getsockname來返回協議位址。

從bind函式返回的乙個常見錯誤時eaddrinuse(「address already in use",位址已使用),後面的部落格會討論so_reuseaddr和so_reuseport這兩個套接字選項。

注意:埠號必須不小於1024,除非該程序具有相應的特權(即為超級使用者)。

3、listen函式

包含標頭檔案

功能:將套接字用於監聽進入的連線

原型:int listen(int sockfd, int backlog);  

引數sockfd:socket函式返回的套接字

backlog:規定核心為此套接字排隊的最大連線個數

返回值:成功返回0,失敗返回-1

一般來說,listen函式應該在呼叫socket和bind函式之後,呼叫函式accept之前呼叫。

listen函式把乙個未連線的套接字轉換成乙個被動套接字,指示核心應接受指向該套接字的連線請求,呼叫listen導致套接字從close狀態轉換到listen狀態。

為了理解其中的backlog引數,對於給定的監聽套接字,核心要維護兩個佇列:

如下圖所示:

伺服器處於listen狀態時收到客戶端syn 分節(connect)時在未完成佇列中建立乙個新的條目,然後用三路握手的第二個分節即伺服器的syn 響應及對客戶端syn的ack,此條目在第三個分節到達前(客戶端對伺服器syn的ack)一直保留在未完成連線佇列中,如果三路握手完成,該條目將從未完成連線佇列搬到已完成連線佇列尾部。當程序呼叫accept時,從已完成佇列中的頭部取出乙個條目給程序,當已完成隊列為空時程序將睡眠,直到有條目在已完成連線佇列中才喚醒。

backlog被規定為兩個佇列總和的最大值,大多數實現預設值為5。

一旦佇列滿,系統會拒絕多餘連線請求,所以backlog的值應該基於伺服器期望負載和接受連線請求與啟動服務的處理能力來選擇。

當客戶端發起connect而導致傳送syn分節給伺服器端握手,如果這時兩個佇列都是滿的,tcp就忽略此分節,並且不發rst,這將導致客戶端tcp重發syn(超時),伺服器端忽略syn而不發rst響應的原因是如果發rst ,客戶端connect將立即返回錯誤,強制客戶端程序處理這種情況,而不是讓tcp的正常重傳機制來處理。實際上所有源自berkeley的實現都是忽略新的syn分節。

還有,backlog為0 時在linux上表明允許不受限制的連線數,這是乙個缺陷,因為它可能會導致syn flooding(拒絕服務型攻擊)。

linux 系統tcp /ip協議棧有個選項可以設定未鏈結佇列大小:tcp_max_syn_backlog

UNIX網路程式設計3 基本TCP套接字程式設計

include int socket int family,int type,int protocal 若成功則返回非負描述符,若失敗則返回 1socket函式成功時返回的為套接字描述符 socket descriptor,簡稱sockfd socket函式的family常值 socket函式的ty...

UNIX網路程式設計 TCP套接字選項

一直想寫一下tcp通訊的事,套接字選項 so debug 當給乙個tcp套接字開啟本選項時,核心將為tcp在該套接字傳送和接收的所有分組保留詳細跟蹤資訊。so dontroute 本選項規定外出的分組將繞過底層協議的正常路由機制。so keepalive 本選項的功用是檢測對端主機是否崩潰或變得不可...

UNIX網路程式設計 第4章 基本TCP套接字程式設計

4.1 概述 本章講解編寫乙個完整的tcp客戶 伺服器程式所需要的基本套接字函式,為下一章編寫客戶 伺服器程式及其擴充套件做準備。基本tcp客戶 伺服器程式的而套接字呼叫流程圖如下 4.2 socket函式 為了執行網路i o,乙個程序必須做的第一件事情就是呼叫socket函式 include in...