1. 程式人生 > >套接字與套接字描述符(網路程式設計Linux_C -> 筆記一)

套接字與套接字描述符(網路程式設計Linux_C -> 筆記一)

套接字與套接字描述符

什麼是套接字?

  套接字是一種通訊機制,是支援TCP/IP網路通訊的基本單元。在之前得知套接字可以實現跨越網路的程序通訊(或者說位於不同計算機的程序通訊),但它最初是伯克利UNIX的一部分,用於的是同一主機上程序的通訊。由於它最初是伯克利UNIX的一部分,所以也經常會說伯克利套接字( BSD Socket ) ,當然像Windows就實現了自己的套接字( WinSocket )。

  對套接字最常見的比喻就是插座、插口(這也就是socket的中文意思),就好像一個程序的套接字 “插” 到另一個程序的套接字之後,兩者就可以通訊了: 在這裡插入圖片描述 而套接字具體做了什麼,或者兩個程序的套接字之間到底有什麼、發生了什麼,這些對於程式設計師而言都是透明的(即程式設計師可以不用太關心、甚至完全不用關心其內容將其視為不存在)。

  在TCP/IP協議中,socket的具體位置在應用層和運輸層之間,可以看作是兩個協議層之間的介面: [圖片來自互動百科] 在這裡插入圖片描述 在這裡插入圖片描述

套接字描述符

  套接字描述符可以類比於檔案描述符。事實上對於 一切皆檔案 的UNIX或類UNIX(比如Linux)作業系統而言,套接字就是一種檔案,而套接字描述符就是一種檔案描述符。

  總而言之,套接字描述符就是用來引用套接字的,並且既然套接字也被看作檔案,所以大多檔案操作完全可以用於套接字(但不是全部)。比如使用write和read對套接字進行操作,其 不嚴謹的 簡單的示意圖如下: 在這裡插入圖片描述 例如,程序A向socket_A寫入資料,資料到達socket_B後又由程序B讀取。

建立套接字

  可以通過呼叫socket函式來建立一個套接字:

#include <sys/socket.h>

int socket(int domain, int type, int protocol)
/* 返回值:若呼叫成功,則返回對應的套接字描述符;若出錯則返回-1 */

  第一個引數domain代表的是 (協議)域,也可以叫做地址族、協議族,用於確定通訊的部分特性。它的可取值常見的有以下三種(當然不止這三種):

  • AF_INET
  • AF_INET6
  • AF_UNIX

前兩種分別是IPv4因特網域和IPv6因特網域,用於網路通訊;而AF_UNIX稱為UNIX域,用於本地通訊(所謂本地就是指同一計算機上的程序通訊)。

  “AF” 是 “Address Family” 的縮寫,即地址族。domain引數的可選值還有以 “PF” 開頭的,也就是 “Protocol Family” 協議族、協議域。   我還是非常疑惑於地址族為什麼和協議族是一個東西(求指教…),即使我在《UNIX網路程式設計 卷1:套接字聯網API (第3版)》上找到這麼一段話: "  AF_字首表示地址族,PF_字首表示協議族。歷史上曾有這樣的想法:單個協議族可以支援多個地址族,PF_值用來建立套接字,而AF_值用於套接字地址結構。但實際上,支援多個地址族的協議族從來就未實現過,而且標頭檔案<sys/socket.h>中為一給定協議定義的PF_值總是與此協議的AF_值相等。儘管這種相等關係並不一定永遠成立,但若有人試圖給已有的協議改變這種約定,則許多現存程式碼都將崩潰。"   目前我的理解是,本來地址族和協議族是不同的引數值,認為一個協議族可以對應多個地址族(即一個PF_值可以對應多個AF_值)。但是目前實現的協議裡面,一旦給定一個協議其地址結構也就確定了,還沒有實現有多個地址結構的協議,所以就乾脆將AF_和PF_等同起來了。

  引數type代表套接字的型別,進一步確定通訊特性。常見的型別可取值有下面兩種(同樣地,不止這兩種):

  • SOCK_STREAM
  • SOCK_DGRAW

STREAM指的是位元組流套接字,位元組流要求在交換資料前雙方應建立一個邏輯連線,並且通訊程式分辨不出報文的界限,TCP協議就屬於這類通訊;DGRAW指的是資料報套接字,它不要求通行雙方事先建立連線,UDP就屬於這類通訊。

  最後一個引數protocol指的是協議,一般設為0表示根據前兩個引數的值選擇預設的協議。例如,如果通訊域是AF_INET,套接字型別是SOCK_STREAM,那麼預設的協議就是TCP。

套接字的地址

  要寄郵件就需要知道目的地的地址(準確地說,是郵箱所對應的地址)。在這裡套接字就相當於郵箱,它也要有對應的地址才可以進行資料的傳遞。在這篇筆記只以IPv4因特網域的套接字地址結構為例做簡單的學習。

  IPv4因特網域套接字的地址可以簡單地認為由兩部分組成:IPv4地址和埠。IPv4地址一般都聽過,也曉得它可以用來標示一臺計算機,而埠則可以標示一個具體的程序(如果一個程序只知道把資料發給哪臺計算機,而不知道發給這臺計算機上的哪一個程序,那麼通訊還是無法實現)。IP地址和埠的關係就像郵政編碼和家庭住址的關係 (作為一個沒有生活常識的大學生我也不知道這個比喻對不對…)

  套接字的地址結構定義於標頭檔案<netinet/in.h>中,其中IPv4因特網域套接字的地址結構如下:

#include <netinet/in.h>

/*結構in_addr用來表示一個IPv4地址,讓我疑惑的是為什麼要用一個結構來表示它...*/
struct in_addr {
	in_addr_t s_addr;
};

/*IPv4因特網域套接字的地址結構*/
struct sockaddr_in {
	sa_family_t sin_family;    /*地址族*/
	in_port_t sin_port;  /*埠號*/
	struct in_addr sin_addr;  /*IPv4地址*/
};

之前說過域(或者說協議族)和地址族一開始被設想成不同的東西,但是現有的協議中兩者是一一對應的。所以很明顯,IPv4因特網域套接字的地址結構中,sin_family只能是AF_INET 。