linux網路程式設計總結(1)
網路程式設計就是通過網路協議實現計算機間的通訊。那麼就有兩個問題,如何精準的定位主機和如何進行高效的資料傳輸。
IP地址來標識網路中的主機,埠號用來定位主機中的某個程序。通過IP和埠號可以唯一確定網路的某臺計算機中的程序。
套接字
即socket,用於描述IP地址和埠,是一個通訊的控制代碼,程序利用套接字向網路發起請求或回覆請求。
套接字的工作原理
要實現網路間的通訊,至少需要一對套接字,一個運行於客戶端,一個運行於服務端。套接字的連線過程可以分為,
1 服務端監聽 2 客戶端連線請求 3 連線請求確認
服務端監聽:服務端並不定位到具體的客戶端套接字,而是處於等待連線狀態,實時監聽網路的狀態
連線請求:即客戶端套接字向服務端發起連線請求,目標是服務端的套接字,為此客戶端要描述服務端的地址和埠號,然後想服務端套接字發起連線請求。
連線確認;即服務端接收到客戶端的連線請求之後,建立一個新的執行緒與客戶端套接字建立連線,並向客戶端回覆服務端的地址和埠資訊,客戶端確認了此連線,該連線就建立好了,而服務端繼續處於監聽狀態,監聽其他套接字發起的請求。
套接字地址結構
struct in_addr { in_addr_t s_addr; // 32-bit IPv4 address //network byte ordered } struct sockaddr_in { sa_family_t sin_family; //AF_INET in_port_t sin_port; //16-bit TCP or UDP port nummber, network byte ordered struct in_addr sin_addr; //32-bit IPv4 address, network byte ordered char sin_zero[8]; //unused }
sockaddr_in是網路套接字地址結構,大小為16位元組,定義在<netinet/in>標頭檔案中,一般我們在程式中是使用該結構體,但是作為引數傳遞給套接字函式時需要強轉為sockaddr型別,注意該結構體中port和addr成員是網路序的(大端結構)。
struct sockaddr {
sa_family_t sa_family; //address family: AF_XXX value
char sa_data[14]; //protocol-specific address
}
sockaddr是通過套接字地址結構,當作為引數傳遞給套接字函式時,套接字地址結構總是以指標方式來使用,比如bind/accept/connect函式等
網路位元組序轉換在此不多做介紹了。
地址轉換函式:inet_ntop(); inet_pton();
#include <arpa/inet.h>
int inet_pton(int family, const char *strptr, void *addrptr); // 返回:成功為1,輸入不是有效表示式返回0,出錯為-1
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len); // 返回:成功為指向結果的指標,出錯為NULL
這兩個函式對於IPv4和IPv6都適用,p代表表示式(presentation)、n表示數值(numeric)。第一個函式嘗試轉化由strptr指標所指的字串,通過addptr指標存放二進位制結果,成功返回1,如果對指定的family而言輸入的不是有效的表達格式,那麼返回0
inet_ntop進行相反的操作,如果len的值太小,不足以存放表示式結果,則返回一個空指標,並置error為ENOSPC。inet_ntop函式的strptr引數不可以是一個空指標,呼叫者必須為目標儲存單元分配記憶體並制定其大小,呼叫成功時,這個指標就是該函式返回值。
SOCKET函式 int socket(int domain, int type, int protocol);
為了執行網路IO,第一件事就是呼叫SOCKET,建立套接字描述符,指定期望的通訊協議族(比如IPv4、IPv6)和套接字字型別(位元組流、資料報或原始套接字)。
family的值有:
- AF_INET IPv4協議
- AF_INET6 Ipv6協議
- AF_LOCAL Unix協議域
- AF_ROUTE 路由套接字
- AF_KEY 祕鑰套接字
type的值有:
- SOCK_STREAM 位元組流套接字
- SOCK_DGRAM 資料報套接字
- SOCK_SEQPACKET 有序分組套接字
- SOCK_RAW 原始套接字
protocol的值有:
- IPPROTO_CP TCP傳輸協議
- IPPROTO_UDP UDP傳輸協議
返回的socket描述字,標識套接字。返回值存在於協議族(address family,AF_XXX)空間中,但沒有一個具體的地址。如果想要給它賦值一個地址,就必須呼叫bind()函式,否則就當呼叫connect()、listen()時系統會自動隨機分配一個埠。所以需要由bind來確定該套接字所對應的網路地址和埠,用來和伺服器通訊。
BIND函式
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen); 成功返回零,失敗返回-1;
bind即把協議地址和埠號賦予一個套接字,往往服務端需要bind,bind後,即可在bind的埠上監聽服務請求,客戶端往往不需要bind。
為什麼服務端需要Bind,客戶端可以不bind呢?
因為服務端始終是網路連線中的被動方,需要在一個眾所周知的埠上等待連線請求,而且作為服務棄埠應該是固定的,所以需要bind固定埠提供服務。而客戶端是發起方,我們並不關心是哪個埠與伺服器建立了連線,核心會自動分配一個無衝突的埠號,我們自己bind的話,還有可能埠衝突。當需要指定的埠通訊的時候,才需要bind。