Linux之socket套接字編程20160704


介紹套接字之前,我們先看一下傳輸層的協議TCP與UDP:

TCP協議與UDP協議的區別

    首先咱們弄清楚,TCP協議和UCP協議與TCP/IP協議的聯系,很多人犯糊塗了,一直都是說TCP/IP協議與UDP協議的

 

區別,我覺得這是沒有從本質上弄清楚網絡通信!

TCP/IP協議是一個協議簇。里面包括很多協議的。UDP只是其中的一個。之所以命名為TCP/IP協議,因為TCP,IP協議是

 

兩個很重要的協議,就用他兩命名了。

TCP/IP協議集包括應用層,傳輸層,網絡層,網絡訪問層。

其中應用層包括:

超文本傳輸協議(HTTP):萬維網的基本協議.  

文件傳輸(TFTP簡單文件傳輸協議):  

遠程登錄(Telnet),提供遠程訪問其它主機功能,它允許用戶登錄    

internet主機,並在這台主機上執行命令.   

網絡管理(SNMP簡單網絡管理協議),該協議提供了監控網絡設備的方法,以及配置管理,統計信息收集,性能管理及安全管

 

理等.  

域名系統(DNS),該系統用於在internet中將域名及其公共廣播的網絡節點轉換成IP地址.

其次網絡層包括:   

Internet協議(IP)    

Internet控制信息協議(ICMP)   

地址解析協議(ARP)   

反向地址解析協議(RARP) 

最后說網絡訪問層:網絡訪問層又稱作主機到網絡層(host-to-network).網絡訪問層的功能包括IP地址與物理地址硬件

 

的映射,以及將IP封裝成幀.基於不同硬件類型的網絡接口,網絡訪問層定義了和物理介質的連接.

當然我這里說得不夠完善,TCP/IP協議本來就是一門學問,每一個分支都是一個很復雜的流程,但我相信每位學習軟件

 

開發的同學都有必要去仔細了解一番。

下面我着重講解一下TCP協議和UDP協議的區別。

TCP(Transmission Control Protocol,傳輸控制協議)是面向連接的協議,也就是說,在收發數據前,必須和對方建

 

立可靠的連接。一個TCP連接必須要經過三次“對話”才能建立起來,其中的過程非常復雜,只簡單的描述下這三次對

 

話的簡單過程:主機A向主機B發出連接請求數據包:“我想給你發數據,可以嗎?”,這是第一次對話;主機B向主機A

 

發送同意連接和要求同步(同步就是兩台主機一個在發送,一個在接收,協調工作)的數據包:“可以,你什么時候發

 

?”,這是第二次對話;主機A再發出一個數據包確認主機B的要求同步:“我現在就發,你接着吧!”,這是第三次對

 

話。三次“對話”的目的是使數據包的發送和接收同步,經過三次“對話”之后,主機A才向主機B正式發送數據。

詳細點說就是:(文章部分轉載http://zhangjiangxing-gmail-com.iteye.com,主要是這個人講解得很到位,的確很

 

容易使人理解!)

TCP三次握手過程

1 主機A通過向主機B 發送一個含有同步序列號的標志位的數據段給主機B ,向主機B 請求建立連接,通過這個數據段,

主機A告訴主機B 兩件事:我想要和你通信;你可以用哪個序列號作為起始數據段來回應我.

2 主機B 收到主機A的請求后,用一個帶有確認應答(ACK)和同步序列號(SYN)標志位的數據段響應主機A,也告訴主機A兩

 

件事:

我已經收到你的請求了,你可以傳輸數據了;你要用哪佧序列號作為起始數據段來回應我

3 主機A收到這個數據段后,再發送一個確認應答,確認已收到主機B 的數據段:"我已收到回復,我現在要開始傳輸實際數

 

據了

這樣3次握手就完成了,主機A和主機B 就可以傳輸數據了.

3次握手的特點

沒有應用層的數據

SYN這個標志位只有在TCP建產連接時才會被置1

握手完成后SYN標志位被置0

 

TCP建立連接要進行3次握手,而斷開連接要進行4次

 

1 當主機A完成數據傳輸后,將控制位FIN置1,提出停止TCP連接的請求

2  主機B收到FIN后對其作出響應,確認這一方向上的TCP連接將關閉,將ACK置1

3 由B 端再提出反方向的關閉請求,將FIN置1

4 主機A對主機B的請求進行確認,將ACK置1,雙方向的關閉結束.

由TCP的三次握手和四次斷開可以看出,TCP使用面向連接的通信方式,大大提高了數據通信的可靠性,使發送數據端

和接收端在數據正式傳輸前就有了交互,為數據正式傳輸打下了可靠的基礎

名詞解釋

ACK  TCP報頭的控制位之一,對數據進行確認.確認由目的端發出,用它來告訴發送端這個序列號之前的數據段

都收到了.比如,確認號為X,則表示前X-1個數據段都收到了,只有當ACK=1時,確認號才有效,當ACK=0時,確認號無效,這時

 

會要求重傳數據,保證數據的完整性.

SYN  同步序列號,TCP建立連接時將這個位置1

FIN  發送端完成發送任務位,當TCP完成數據傳輸需要斷開時,提出斷開連接的一方將這位置1

TCP的包頭結構:

源端口 16位

目標端口 16位

序列號 32位

回應序號 32位

TCP頭長度 4位

reserved 6位

控制代碼 6位

窗口大小 16位

偏移量 16位

校驗和 16位

選項  32位(可選)

這樣我們得出了TCP包頭的最小長度,為20字節。

 

UDP(User Data Protocol,用戶數據報協議)

(1) UDP是一個非連接的協議,傳輸數據之前源端和終端不建立連接,當它想傳送時就簡單地去抓取來自應用程序的

 

數據,並盡可能快地把它扔到網絡上。在發送端,UDP傳送數據的速度僅僅是受應用程序生成數據的速度、計算機的能

 

力和傳輸帶寬的限制;在接收端,UDP把每個消息段放在隊列中,應用程序每次從隊列中讀一個消息段。

(2) 由於傳輸數據不建立連接,因此也就不需要維護連接狀態,包括收發狀態等,因此一台服務機可同時向多個客戶

 

機傳輸相同的消息。

(3) UDP信息包的標題很短,只有8個字節,相對於TCP的20個字節信息包的額外開銷很小。

(4) 吞吐量不受擁擠控制算法的調節,只受應用軟件生成數據的速率、傳輸帶寬、源端和終端主機性能的限制。

(5)UDP使用盡最大努力交付,即不保證可靠交付,因此主機不需要維持復雜的鏈接狀態表(這里面有許多參數)。

(6)UDP是面向報文的。發送方的UDP對應用程序交下來的報文,在添加首部后就向下交付給IP層。既不拆分,也不合

 

並,而是保留這些報文的邊界,因此,應用程序需要選擇合適的報文大小。

我們經常使用“ping”命令來測試兩台主機之間TCP/IP通信是否正常,其實“ping”命令的原理就是向對方主機發送

 

UDP數據包,然后對方主機確認收到數據包,如果數據包是否到達的消息及時反饋回來,那么網絡就是通的。

UDP的包頭結構:

源端口 16位

目的端口 16位

長度 16位

校驗和 16位

 

小結TCP與UDP的區別:

1.基於連接與無連接;

2.對系統資源的要求(TCP較多,UDP少);

3.UDP程序結構較簡單;

4.流模式與數據報模式 ;

5.TCP保證數據正確性,UDP可能丟包,TCP保證數據順序,UDP不保證。TCP失敗就重傳,UDP不會

 

 

接下來,我們再看看工作在應用層的套接字編程:

套接字是一種使用標准UNIX文件描述符(file descriptor)與其他程序通信的方式。套接字可以看作是處於不同主機之間的兩個程序的通信連接端點。一方面程序將要傳輸的信息寫入套接字中,而另一方面則通過讀取套接字內的數據來獲得傳輸的信息。

 

圖1  套接字通信示意圖

圖1所示為使用套接字進行通信的示意圖。假設存在兩台主機A與B,在主機A中存在進程C,主機B中存在進程D,當進程C需要將數據送到進程D時,首先將數據寫到套接字中,而進程D可以通過讀取套接字來獲得進程C發送的信息。

在網絡中,不同計算機是通過IP地址來區分的,也就 是說,要將數據由主機A發送到主機B,只要知道主機B的IP地址就可以確定數據要發送的目的地。但是,在主機A與B中不可能只有進程C和進程D兩個進程。 主機B在收到主機A發送來的數據后,如何才能確定該數據是發送給進程D?因此,還需要某種標識信息,用於描述網絡通信數據發往的進程。TCP/IP協議提 出了協議端口的概念,用於標識通信的進程。

當進程與某個端口綁定后,操作系統會將收到的給該端 口的數據送往該進程。與文件描述符類似,每個端口都有被稱為端口號的整數類型的標識符,該標識符用於區分不同的端口。不同協議可以使用相同的端口號進行數 據傳輸。例如,TCP使用了344的端口號,UDP同樣可以使用344端口號進行數據傳輸。

端口號為一個16位的無符號整數,其取值范圍為0~65535。低於256的端口被作為系統的保留端口號,主要用於系統進程的通信,不在這一范圍的端口號被稱為自由端口號,可以由進程自由使用。

主要看一下客戶端的:

         retval = connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));

         //(struct sockaddr*)&servaddr里 是服務器的地址,表示連接到服務器的某一個地址

套接字

ret = getaddrinfo(pSrvName, NULL, &hostInfo, &pAddrInfo);

getaddrinfo函數能夠處理名字到地址以及服務到端口這兩種轉換,返回的是一個sockaddr結構的鏈表而不是一個地址清單。這些sockaddr結構隨后可由套接口函數直接使用

int getaddrinfo( const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result );

參數說明

hostname:一個主機名或者地址串(IPv4的點分十進制串或者IPv6的16進制串)

service:服務名可以是十進制的端口號,也可以是已定義的服務名稱,如ftp、http等

hints:可以是一個空指針,也可以是一個指向某個addrinfo結構體的指針,調用者在這個結構中填入關於期望返回的信息類型的暗示。舉例來說:指定的服務既可支持TCP也可支持UDP,所以調用者可以把hints結構中的ai_socktype成員設置成SOCK_DGRAM使得返回的僅僅是適用於數據報套接口的信息。

result:本函數通過result指針參數返回一個指向addrinfo結構體鏈表的指針。

返回值:0——成功,非0——出錯

在getaddrinfo函數之前通常需要對以下6個參數進行以下設置:nodename、servname、hints的ai_flags、ai_family、ai_socktype、ai_protocol

通常服務器端在調用getaddrinfo之前,ai_flags設置AI_PASSIVE,用於bind;主機名nodename通常會設置為NULL,返回通配地址[::]。

如果nodename是本機名,servname為NULL,則根據操作系統的不同略有不同,地址列表加以返回

 

 

struct sockaddr{ 

unsigned short sa_family;

char sa_data[14]

};

sa_family:用於指定地址族,如果是TCP/IP通信,該值取PF_INET。sa_data:用於保存套接字的IP地址和端口號信息

struct sockaddr_in {

   short int sin_family;

   unsigned short int sin_port;

   struct in_addr sin_addr;

   unsigned char sin_zero[8];

  };

l     sin_family:用於指定地址族。

l     sin_port:套接字通信的端口號。

l     sin_addr:通信的IP地址。

l     sin_zero[8]:用以填充0,保持與struct sockaddr同樣大小。

由於sockaddr數據結構與sockaddr_in數據結構的大小是相同的,指向sockaddr_in的指針可以通過強制轉換,轉換成指向sockaddr結構的指針。

 

int socket(int domain, int type, int protocol);

socket函數用於創建通信的套接字,並返回該套接字的文件描述符。參數domain指定了通信域,該參數用於選擇通信協議族參數type用於指定套接字的類型。套接字類型除了前面提到的流套接字、數據報套接字及原始套接字外,還有其他的幾種類型參數protocol用於指定套接字使用的通信協議。正常情況下,對於給定的協議族,只有單一的協議支持特定的套接字類型。這時,只要將protocol參數設置為0即可

 

int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);

connect函數將使用參數sockfd中的套接字連接到參數serv_addr中指定的服務器。參數addrlen為serv_addr指向的內存空間大小。

如果參數sockfd的類型為SOCK_DGRAM,serv_addr參數為數據報發往的地址,且將只接收該地址的數據報。如果sockfd的類型為SOCK_STREAM或SOCK_SEQPACKET,調用該函數將連接serv_addr中的服務器地址。

ssize_t send(int s, const void *buf, size_t len, int flags);

send函數用於將信息發送到指定的套接字文件描述 符中。該函數只能用於已經建立連接的socket通信中,即只用於面向連接的通信中。參數s為要發送數據的套接字文件描述符。buf參數為指向要發送數據 的指針。len為要發送數據的長度。flag參數可以包含如下的參數

ssize_t recv(int s, void *buf, size_t len, int flags);

recv函數用於從指定套接字中獲取發送的消息。與send函數一樣,該函數只能用於已經建立連接的socket通信中,即只用於面向連接的通信中。參數s為要讀取信息的套接字文件描述符。buf參數為指向要保存數據緩沖區的指針。而len為該緩存的最大長度。

 

 

使用套接字除了可以實現網絡間不同主機間的通信外,還可以實現同一主機的不同進程間的通信,且建立的通信是雙向的通信。這里所指的使用套接字實現進程間通信,是由將通信域指定為PF_UNIX來實現的

 

AF 表示ADDRESS FAMILY 地址族,PF 表示PROTOCOL FAMILY 協議族,但這兩個宏定義是一樣的,所以使用哪個都沒有關系。Winsock2.h中#define AF_INET 2,#define PF_INET AF_INET,所以在windows中AF_INET與PF_INET完全一樣。而在Unix/Linux系統中,在不同的版本中這兩者有微小差別。對於BSD,是AF,對於POSIX是PF。UNIX系統支持AF_INET,AF_UNIX,AF_NS等,而DOS,Windows中僅支持AF_INET,它是網際網區域。

AF_INET(又稱 PF_INET)是 IPv4 網絡協議的套接字類型,AF_INET6 則是 IPv6 的;而 AF_UNIX 則是 Unix 系統本地通信。選擇AF_INET 的目的就是使用 IPv4 進行通信。因為 IPv4 使用 32 位地址,相比 IPv6 的 128 位來說,計算更快,便於用於局域網通信。而且 AF_INET 相比 AF_UNIX 更具通用性,因為 Windows 上有 AF_INET 而沒有 AF_UNIX。

 

2.

SOCKET是進程間通信的一種方式,這個時候socket的創建、綁定、連接時的參數是與網絡上不同主機間的通信不同的,比如對於socketaddr的使用,進程間通信使用的是sockaddr_un。而主機間的通信使用的是sockadd_in

 

connect_fd = socket(PF_UNIX, SOCK_STREAM, 0);

if(connect_fd < 0)

{

perror("client create socket failed");

return 1;}

//set server sockaddr_un

srv_addr.sun_family = AF_UNIX;

strcpy(srv_addr.sun_path, UNIX_DOMAIN);

//connect to server

ret = connect(connect_fd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));

 

至於服務器的,參考下圖(流套接字通信示意圖):

 

數據報套接字(udp)示意圖:

附筆者筆記:

1.網絡通信tcpudp

 

2.套接字編程

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM