每個協議族都有本身的套接字地址結構,這些地址結構以 sockaddr_ 開頭,後跟協議族特定的後綴,如 TCP/IPV4 協議族對應的套接字地址結構爲 sockaddr_in.編程
sockaddr_in,TCP/IPV4對應的套接字地址結構,內部以網絡字節序存放着IP地址與端口號.具體結構以下:網絡
struct sockaddr_in{ sa_family_t sin_family; in_port_t sin_port; /* 端口號 */ struct in_addr sin_addr; /* IP 地址 */ }; struct in_addr{ in_addr_t s_addr; };
通用地址結構,其名爲 sockaddr;其存在的惟一意義就是將指向特定於協議的套接字地址結構的指針進行強制類型轉換.以下:dom
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);/* bind() 函數的原型中使用的是 struct sockaddr 類型,因此再將 TCP 套接字綁定到一個地址上時須要進行強制類型轉換, */ struct sockaddr_in local_addr; /* 填充 local_addr */ bind(sockfd,(struct sockaddr*)&local_addr,sizeof(local_addr));
MSB,most significant bit,最高有效位;LSB,least significant bit,最低有效位;對於整數 123456,能夠認爲最高有效位是 1,最低有效位是 6.如下函數用於將整數在主機字節序以及網絡字節序之間進行轉換.異步
主機字節序,網絡字節序轉換接口以下:socket
uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);將二進制形式(網絡字節序)的IP地址(包括IPV4,IPV6)轉換爲字符串表示形式(對於IPV4來講,字符串表示形式即點分十進制).函數
af 若爲 AF_INET,則代表 src 的實際類型爲 struct in_addr*,即IPV4地址;若爲 AF_INET6,則代表爲IPV6地址.ui
src 指向着二進制形式的IP地址.spa
dst,size 緩衝區的起始地址以及緩衝區的字節長度.當 af 爲 AF_INET 時,size 的值至少應該大於或等於 INET_ADDRSTRLEN.
.net
返回值 若爲 0,則代表 af 參數的值不是合法值或者緩衝區長度太短.線程
int inet_pton(int af, const char *src, void *dst);將字符串表示的IP地址(包括IPV4,IPV6)轉換爲二進制形式(網絡字節序).
af 若爲 AF_INET,則 dst 的實際類型是 struct in_addr* 類型,即 IPV4 地址.
src 指向着字符串表示的 IP 地址
dst 轉換後的二進制形式的IP地址將存放在 dst 指向的緩衝區中.
返回值
若爲-1,則代表 af 參數的值不合法.
若爲0,則代表 src 指向的字符串格式不正確.
若爲1,則代表結構正確.
介紹了幾個基本接口,利用這些接口能夠完成基本的TCP套接字編程;
int socket(int domain, int type, int protocol);建立一個套接字,套接字是協議的接口,在利用某協議進行通訊以前,必須建立該協議對應的套接字.
domain,type,protocol 肯定套接字所對應的協議,如
domain=AF_INET,type=SOCK_STREAM,protocol=IPPROTO_TCP,則代表套接字關聯的協議是 TCP 協議
domain=AF_INET,type=SOCK_DGRAM,protocol=IPPROTO_UDP,則代表套接字關聯的協議是 UDP 協議
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);在指定的端口下面建立一個TCP/UDP端點,並將該端點與套接字 sockfd 關聯起來,至此當從 sockfd 讀取時,至關於讀取關聯端點的接受緩衝區,當寫入 sockfd 時,至關於寫入關聯端口的發送緩衝區.
addr 將根據 addr 中保存的 IP 地址與端口號爲初始化端點的本地IP地址,本地端口號.其中
sin_addr,若爲 INETADDR_ANY,則表示通配地址;不然必須是主機網絡接口之一.
sin_port,若爲0,則內核隨機選擇一個未用的端口號.
以 INETADDR_ 開頭的地址使用主機字節序進行保存,如:
struct sockaddr_in local_addr; local_addr.sin_family = AF_INET; local_addr.sin_port = htons(3333); local_addr.sin_addr.s_addr = htonl(INETADDR_ANY);/* 將主機字節序轉換爲網絡字節序 */
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);首先若 sockfd 還沒有關聯過 TCP 端點(即未調用 bind() ),則爲該 sockfd 關聯一個 TDP 端點(此時端點的本地 IP 地址爲 addr->sin_addr 的出口地址,本地端口號爲隨機),而後向指定的IP地址:端口號發起三次握手,直至三次握手失敗或者 sockfd 進入 ESTABLISHED 狀態後纔會返回.
若三次握手失敗,則套接字 sockfd 再也不可以使用,此時應該調用 close(sockfd) 來關閉套接字,並釋放底層資源.
調用以下接口,開始監聽,此時 TCP 端點如圖:
int sockfd = socket(AF_INET,SOCK_STREAM,0); struct sockaddr_in dst_addr; bind(sockfd,makeSockaddr(&dst_addr,"*",8080),sizeof(dst_addr)); listen(sockfd,7);
當TCP收到一個SYN數據包時,會新建一個TCP端點,以下圖,而後在該TCP端點上執行三次握手,待三次握手完成後,將該端點加入已完成鏈接隊列.
int listen(int sockfd, int backlog);爲已完成隊列分配內存空間,使其能夠存放 backlog 個端點;而且讓 sockfd 關聯的端點進入 LISTEN 狀態,開始監聽.
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);若已完成連接隊列爲空,則會主動放棄CPU,睡眠一段時間再重試;若已完成連接隊列不爲空,則從已完成連接隊列中移除一個端點,而且將端點的遠端IP地址與遠端端口號複製到 addr 指向的緩衝區中,而後爲該端點建立一個套接字描述符(即該套接字描述符與端點關聯),並返回該描述符.
addr 輸出參數,若不爲0,則將(被移除的)端點的遠程IP地址,遠端端口號複製到 addr 指向的緩衝區中.
addrlen 輸入/輸出參數,做爲輸入參數,addrlen 是 addr 指向緩衝區的字節長度;做爲輸出參數,內核將佔用的實際字節長度存放在 *addrlen 中.
返回值 一個新的文件描述符.
int shutdown(int sockfd, int how);禁止套接字描述符 sockfd 可讀,或可寫的能力.即此後 sockfd 將不可讀,或者不可寫.
how 能夠取 SHUT_RD,SHUT_WR,SHUT_RDWR
SHUT_RD 此時套接字描述符 sockfd 再也不可讀,下一次調用 read() 將返回 0.
SHUT_WR 此時套接字描述符 sockfd 再也不可寫,下一次調用 write() 將觸發信號 SIGPIPE.在 TCP 中,此時會發送 FIN 報文到另外一端.
SHUT_RDWR 即 sockfd 再也不可讀,也再也不可寫,
int close(fd);將 fd 標記爲已關閉,而後當即返回.標記爲已關閉,意味着當 fd 關聯的 TCP 端點中發送緩衝區的數據發送完畢後,執行四次揮手,而且釋放底層資源.
fd,也算是文件描述符,因此'標記爲已關閉'這項動做將在引用計數爲0時纔會實施,參見'打開的文件'
TCP鏈接是一個雙向鏈接,發送 FIN 僅是終止一端到另外一端的單向鏈接,而發送 RST 則是終止雙向鏈接.如:
TCP端點的四元組,本地IP地址:本地端口 遠程IP地址,遠程端口.
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 返回套接字 sockfd 關聯的TCP端點的遠程IP地址,遠程端口.
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 返回套接字 sockfd 關聯的TCP端點的本地IP地址,本地端口.
Unix上具備5種IO模型:阻塞式IO,非阻塞式IO,IO複用,信號驅動式IO,異步IO;在 Unix 中,一次讀取操做能夠分爲2步,以下,
等待數據準備好(在讀取套接字時,這一步意味着從網絡中等待數據,將數據複製到內核空間)
將數據從內核空間複製到進程空間.
根據讀取操做來理解5種IO模型:
阻塞式IO,當數據還沒有準備好時,此時將掛起進程直至數據準備完畢.
非阻塞式IO,當數據還沒有準備好時,出錯返回,而不是掛起進程.
IO複用,由一個特殊的函數來監控數據是否準備完成,該函數會掛起進程直至數據準備完畢.
信號驅動式IO,由內核爲咱們監視數據是否準備完畢,即內核會在數據準備完畢後發送 SIGIO 信號通知進程.
異步IO,內核會爲咱們監視數據是否準備完畢,而且在數據準備完畢後自動將數據由內核空間拷貝到進程空間,在拷貝完成後以某種機制通知咱們.
同步IO/異步IO
同步IO操做,該操做會阻塞請求進程,直至IO操做完成.
異步IO操做,該操做不會阻塞請求進程,即當一個進程請求異步IO操做時,該異步IO操做並不會阻塞進程
在 Unix 五種IO模型中,前四種都屬於同步IO操做,由於他們都須要將數據從內核空間拷貝到進程空間,而這一步將阻塞進程,第五種IO模型爲異步IO操做.
IO複用,即提供一個特殊的函數,該函數能夠監視多個文件描述符;並掛起進程,直至文件描述符上指定的條件知足.該特殊函數能夠是 select(),poll(),
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);該函數會掛起當前進程(線程),直至 readfd,writefds,exceptfds 中至少一個文件描述符達到指定的條件,或者超時.
nfds 爲 readfds,writefds,exceptfds 中最大文件描述符的值+1;即內核會檢測 readfds,writefds,exceptfds 集合中位於 [0,nfds) 範圍內的文件描述符.
readfds 對於該集合內的文件描述符來講,知足條件意味着:
對該文件描述符調用 read() 再也不阻塞(包括 read() 返回-1,0,>0的狀況),此時文件描述符上可能有準備好的數據,或者出錯,或者到達了文件結束符.
該文件描述符是一個監聽套接字,而且已完成鏈接隊列不爲0,即對該監聽套接字調用 accept() 不會阻塞.
writefds 對於該集合內的文件描述符來講,知足條件意味着:
對該文件描述符調用 write() 再也不阻塞,此時包括 write() 出錯返回,正常返回,或者觸發了信號 SIGPIPE.
該文件描述符關聯的 TCP 端點已完成三次握手,此時包括三次握手成功,或者三次握手失敗
exceptfds 對於該集合內的文件描述符來講,知足條件意味着:
該文件描述符關聯的 TCP 端點收到了帶外數據,
timeout 指定了最大超時時間,該參數有三種狀況:
若 timeout==0,則 select() 會一直阻塞下去,直至至少一個文件描述符知足條件.
若 timeout->tv_sec == 0&&timeout->tv_usec==0,此時 select() 檢查 readfds,writefds,exceptfds 內的文件描述符後當即返回,不會阻塞.
其餘狀況,此時 select() 會掛起進程,直至超時或者至少一個文件描述符知足條件.
返回值 若爲 -1,則表示出現了某種錯誤;不然爲知足條件的文件描述符個數(若 readfds,writefds 中均可以一個文件描述符 fd,且該文件描述符知足了可讀可寫,則此時 select() 返回 2),此時 readfds,writefds,exceptfds 中仍存在的文件描述符代表它們知足了相應的條件,未知足條件的描述符將被移除.
fd_set 文件描述符集合,存放着多個文件描述符,常見的操做以下:
void FD_CLR(int fd, fd_set *set);從文件描述符集合中移除文件描述符 fd.
int FD_ISSET(int fd, fd_set *set);若返回值非0,則表示在文件描述符集合 set 中存在 fd.
void FD_SET(int fd, fd_set *set);將文件描述符 fd 加入到集合 set 中.
void FD_ZERO(fd_set *set);清空文件描述副集合 set.
不介紹 poll().