socket是對TCP/IP協(xié)議族的封裝,并對網(wǎng)絡編程開發(fā)人員提供可用的接口,可以說,任何網(wǎng)絡編程都離不開socket,要想熟練運用網(wǎng)絡編程技術,必須掌握socketAPI.本篇文章便是對socketAPI的介紹.
套接字地址結構
IPV4套接字地址結構
typedef __uint32_t in_addr_t;
struct in_addr {
in_addr_t s_addr; /* 32位的IP地址 */
}; /* 網(wǎng)絡子節(jié)序 */
struct sockaddr_in {
__uint8_t sin_len; /* 結構體的長度 */
sa_family_t sin_family; /* AF_INET協(xié)議族 */
in_port_t sin_port; /* 16位的端口號,網(wǎng)絡子節(jié)序 */
struct in_addr sin_addr; /* 32位IP地址 */
char sin_zero[8]; /* unused */
};
- 長度字段sin_len表示struct sockaddr_in的長度,在使用的時候,并不是必須顯示的對該字段賦值,它是一個無符號8位整形,即unsigned char類型
- sin_family是一個_uint8_t類型,表示套接字地址結構的協(xié)議族,比如AF_INET表示internet協(xié)議族。
- sin_port 是一個_unint16_t十六位無符號整形,表示TCP協(xié)議中的端口號, 是網(wǎng)絡子節(jié)序。
- sin_addr 是一個32位無符號整形的IP地址,將它設置成結構體類型,是為了考慮內(nèi)存對齊.
- sin_zero字段,也不需要顯示設置,基本不用.
通用套接字地址結構
strict sockaddr{
__uint8_t ss_len;
sa_family_t sa_family;
char sa_data[14];
}
當套接字地址結構作為一個參數(shù)傳遞進任何套接字函數(shù)時,套接字地址結構總是以指針的形式來傳遞,然而以這樣的指針作為參數(shù)之一的任何套接字函數(shù),必須處理來自任何協(xié)議族的套接字地址結構。因此ANSIC便創(chuàng)造了通用套接字地址結構,當時候IPV4套接字地址結構傳遞時,需要進行強制轉換.
套接字函數(shù)
socket函數(shù)
#inclucde<sys/socket.h>
int socket(int family,int type,int protocol);
- socket函數(shù)在成功時,返回一個非負整數(shù),稱之為套接字描述符,失敗時返回一個負數(shù),錯誤信息在error中.
- family表示協(xié)議族,比如AF_INET表示IPV4協(xié)議,AF_INET6表示IPV6協(xié)議,AF_LOCAL表示Unix表示域協(xié)議等.
- type表示套接字類型,SOCK_STREAM表示TCP套接字,SOCK_DGRAM表示數(shù)據(jù)報套接字(UDP).protocol一般設置為0表示family和type組合的系統(tǒng)默認值.
connection函數(shù)
#include<sys/socket.h>
int connection(int sockfd,const struct *sockaddr,socklen_t addrlen);
- sockfd是一個套接字描述符,由socket函數(shù)指定,第二個和第三個參數(shù)分別指定套接字結構和套接字的長度.sockaddr必須包含服務端的IP地址和端口號.
- TCP客戶端用connection函數(shù)來建立與TCP服務端的連接,因此connection函數(shù)是一個客戶端使用的函數(shù),如果是TCP套接字,調用connection函數(shù)將激發(fā)TCP的三次握手過程,只有當建立成功或者是失敗時該函數(shù)才返回,因此,在連接過程中,程序時阻塞的.
- 客戶程序調用connection內(nèi)核會確定客戶端的IP地址,并且會分配一個臨時端口號.作為源端口.
- 錯誤信息有以下幾種情況
1.若TCP客戶沒有收到SYN分節(jié)的響應,則會返回ETIMEDOUT錯誤,舉例來說,4.4BSD內(nèi)核發(fā)送一個SYN,如果沒有響應,則等待6s再發(fā)送一個,如果還沒有響應,則等待24秒發(fā)送一個,若總共等待了75秒還沒有響應則返回ETIMEOUT錯誤.
2.若對客戶的SYN的響應是RST(復位),則表明該服務器主機,在指定的端口沒有進程,等待連接(比如服務器進程未運行),這是一種硬錯誤,客戶一接收到RST則立馬返回ECONNREFUSED.
3.若客戶發(fā)出的SYN在中間的某個路由器上引發(fā)了一個destination unreachable的ICMP錯誤,該錯誤會保存在內(nèi)核中,然后會按照第一種情況來處理,如果,75秒還是沒有響應,則返回EHOSTUNREACH - 當connection函數(shù)返回失敗后必須關閉當前套接字描述符,并重新調用socket,然后重新連接.
bind函數(shù)
#include<sys/socket.h>
int bind(int sockfd,const struct sockaddr *myaddr,sickle_t addrlen);
- bind函數(shù)通常用于設置本機的地址和端口號,對于客戶端來說,不一定必須調用該函數(shù),如果不調用該函數(shù),客戶端在和服務端通信的時候,會讀取本機的地址和設置一個臨時的端口號,和服務器通信。
- 服務端需要調用該函數(shù),本機的地址通常設置為INADDR_ANY,表示內(nèi)核自己設置本機IP,通常要明確設置端口號,不設置端口號是很罕見的。bind函數(shù)返回的一個常見的錯誤是:EADDRINUSE(端口地址被占用).
listen函數(shù)
#include<sys/socket.h>
int listen(ins sockfd,int backlog);
- listen函數(shù)僅由TCP服務器調用,當socket函數(shù)創(chuàng)建一個套接字描述符時,它被假設為一個主動套接字,即它將調用connect函數(shù),listen函數(shù)把一個未連接的套接字,轉換成一個被動套接字,它指示內(nèi)核應該接受連接請求,到接字狀態(tài)從CLOSED狀態(tài)轉換成LISTEN狀態(tài).
- backlog規(guī)定了內(nèi)核應為相應套接字排隊的最大連接數(shù).
- 內(nèi)核為任何一個給定的監(jiān)聽套接字維護了兩個隊列:
(1)未完成連接隊列,當客戶程序,發(fā)送請求連接(第一次握手)到達時,內(nèi)核會將這個過程放入該隊列
(2)已完成連接隊列,當客戶程序完成了三次握手時,內(nèi)核會將未完成隊列移到已完成隊列中去.以上的處理,是內(nèi)核處理,服務器的進程無需插手.當服務器進程調用accept時,內(nèi)核會將已完成隊列的對頭的信息,返回給進程,如果該隊列為空,進程會進入睡眠(阻塞),直到有隊列不為空才返回. - backlog表示未連接隊列和已連接隊列之和的最大值,注意!不要將backlog設置為0.
accept函數(shù)
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr * clientaddr,socklen_t *addrlen);
- accept由服務器調用,如果accept返回成功,其返回值是由內(nèi)核自動生成的一個全新描述符,代表與客戶端的TCP連接(此時連接已經(jīng)完成了,可以進行數(shù)據(jù)交互了),比如我們要發(fā)送數(shù)據(jù),則調用write(fd,buffer,strlen(buffer));此處的fd便是由accept返回的,他就是一個連接區(qū)分TCP連接的標記(不然服務器怎么知道把數(shù)據(jù)返回給哪個socket連接).
- 此函數(shù)最多返回三個值,一個是函數(shù)的返回值,clidetaddr是一個指針類型的,因此傳一個地址過去,clientaddr便會返回客戶端的套接字地址結構信息,比如客戶端的ip地址和端口號,addrlen表示客戶端套接字地址結構的長度,如果對客戶端的地址結構不敢興趣,第二個參數(shù)和第三個參數(shù)可以傳NULL.
總結
本文對tcp套接字的API的各個函數(shù)進行了介紹,上面介紹的函數(shù)是理解TCP套接字編程的基礎,下一篇文章,將會運用本章所介紹的函數(shù),編寫一個客戶服務端程序.