上一篇文章介紹了套接字的建立過程,這篇文章主要討論分配給套接字的IP地址和端口號的相關知識。編程
IP地址和端口號瀏覽器
IP(Internet Protocol,網絡協議)地址是收發網絡數據而分配給計算機的值,端口號則並不是賦予計算機的值,而是爲了區分計算機程序所建立的不一樣套接字而分配給套接字的編號。服務器
網絡地址網絡
IP地址分爲以下兩類,其中,IPv6是爲了應對2010年先後IP地址耗盡的問題而提出的新標準。不過,目前廣泛使用的主要仍是IPv4,IPv6的普及可能還須要一段時間。socket
IPv4(Internet Protocol version 4) 4字節地址族函數
IPv6(Internet Protocol version 6) 16字節地址族oop
IPv4標準的4字節IP地址由網絡地址和主機地址組成,其中網絡地址主要分爲A、B、C、D等類型,結構以下:ui
IPv4地址族spa
IP地址爲何要分爲網絡地址和主機地址?網絡地址是爲了區分不一樣網絡而設置的一部分IP地址,試想,若是4字節的IP地址全都是主機地址,那麼計算機間的通訊尋路將會變得異常低效。而網絡地址的加入將通訊尋路過程分爲了兩個部分,首先鎖定有效的網絡地址,再查找主機地址,有效提升了通訊效率。例如,某主機向203.211.172.103和203.211.217.202傳輸數據,其中,203.211.172和203.211.217爲網絡地址。從圖中能夠看出,網絡地址的節點是由路由器或交換機組成的,接收數據的路由器再根據數據中的主機地址向目標主機傳輸數據。操作系統
上面提到的網絡地址是203.211.172和203.211.217,那麼如何區分它們的類型?咱們只須要關注IP地址的第一個字節的數值便可:
A類地址首字節範圍:0~127 0000 0000 ~ 01111 11111
B類地址首字節範圍:128~191 1000 0000 ~ 10111 11111
C類地址首字節範圍:192~223 1100 0000 ~ 11011 11111
另外,根據上面不一樣類型網絡地址所佔的字節數,可以知道A類地址是最寶貴的,由於它擁有的IP地址資源最爲豐富。一個A類地址至關於256個B類地址,一樣的,一個B類地址至關於256個C類地址。
端口號
IP地址用於區分計算機,只要有IP地址就可以向目標主機傳輸數據,但僅此並不能肯定具體程序。好比,此時計算機中正運行着聊天程序和瀏覽器程序,那麼該計算機如何決定將接收到的網絡數據發給哪一個程序呢?
計算機中通常都配有NIC(Network Interface Card,網絡接口卡)數據傳輸設備,經過NIC向計算機內部傳輸數據時須要用到IP地址。而操做系統負責將內部數據具體分配給套接字,這時就須要用到端口號來做爲區分。整個數據的分配過程以下:
端口號就是同一操做系統中爲了區分不一樣套接字而設置的,所以沒法將一個端口號分配給不一樣的套接字(因爲TCP套接字和UDP套接字不會共用端口號,能夠重複)。端口號由16位組成,範圍0~65535,但0~1023是特定程序使用的知名端口號,因此應當使用此範圍以外的值。
地址信息的表示
以前的文章中有提到sockaddr_in和sockaddr,下面給出相關的結構體定義
//IPv4地址結構 struct sockaddr_in { sa_family_t sin_family; //地址族 uint16_t sin_port; //16位端口號,網絡序保存 struct in_addr sin_addr; //32位IP地址,網絡序保存 char sin_zero[8]; //保留 } //通用地址結構 struct sockaddr { sa_family_t sin_family; //地址族 char sa_data[14]; //地址信息 } struct in_addr { in_addr_t s_addr; //32位IPv4地址 }
以上結構體定義中涉及到的數據類型如uint16_t、in_addr_t等,可參考POSIX(Portable Operating System Interface,可移植操做系統接口)。之因此有這些定義,是考慮到移植性的結果,如uint16_t,不管是在32位系統中仍是在64位系統中其對應數據類型均是16位無符號整形。POSIX中的一些數據類型定義以下:
以前的文章中有提到sockaddr_in和sockaddr的區別的問題,結合上面的結構體定義可知,sockaddr_in是IPv4地址信息專用的結構體;而sockaddr則是兼容除IPv4地址信息以外的結構體(不過從地址信息字節大小14個字節來看,彷佛並不兼容IPv6)。
網絡字節序與地址轉換
字節序,簡單來講就是一個數據的高低字節在內存中的存儲及解析方式。字節序分爲以下兩種方方式:
大端序(Big Endian):高位字節存放在低位地址
小端序(Little Endian):低位字節存放在高位地址
單字節數據的大小端存儲方式並無區別,多字節數據的大小端存儲方式則大相徑庭。如一個4字節整型值0x12345678的高字節數據0x12,大端存儲時在內存的低地址端,而小端存儲則正好相反:
大端序存儲和小端序存儲
爲何計算機通訊須要字節序轉換?若是發生數據交換的兩臺計算機有不一樣的字節序存儲方式會發生什麼?
不一樣字節序的計算機發生數據交換時引起的錯誤
正如上圖所發生的那樣,大端序系統的數據傳輸到小端序系統,原來的數據0x1234被錯誤地解析爲0x3412。這顯然不是咱們所指望獲得的結果,所以當須要在不一樣的計算機間傳輸數據時,咱們要時刻銘記字節序的轉換。而考慮到程序的可移植性,即便咱們已知通訊雙方主機擁有相同字節序,咱們仍需作字節序轉換的操做。進一步,當有數據須要保存而且有可能在其餘主機上恢復時,咱們也須要考慮字節序轉換的操做。
實際上,在網絡編程中,咱們只須要考慮向sockaddr_in結構體變量填充數據時的字節序轉換,而真正傳輸數據的字節序轉換是底層機制自動完成的。
網絡地址的初始化
sockaddr_in中保存的地址信息成員爲32位整型值,而咱們熟悉的是點分十進制(Dotted Decimal Notation)這種字符串表示方法,所以須要作相應的裝換。下面介紹的一些函數將幫助咱們完成這些裝換
#include <arpa/inet.h> in_addr_t inet_addr(const char *string); -> 成功時返回32位大端序整型值,失敗時返回INADDR_NONE
#include <arpa/inet.h> int inet_aton(const char *string, struct in_addr *addr); -> 成功時返回1(True),失敗時返回0(Flase)
#include <arpa/inet.h> char *inet_ntoa(struct in_addr addr); -> 成功時返回轉換的字符串首地址,失敗時返回-1
函數inet_addr和inet_aton在功能上徹底相同,不過函數inet_aton能夠直接返回in_addr結構體,即直接傳入sockaddr_in中地址成員sin_addr的地址便可保存地址值。函數inet_ntoa則剛好與inet_aton相反,完成網絡地址從32位整型值到點分十進制的字符串的轉換。不過函數inet_ntoa所返回的字符串只是存儲在內部臨時的內存空間中,一旦下次被調用,則以前的轉換字符串將被覆蓋。所以,對於函數inet_ntoa的返回值咱們應及時copy到本身的存儲空間中。
網絡地址初始化
struct sockaddr_in serv_addr; char *serv_ip = "192.168.158.10"; char *serv_port = "8080"; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(serv_ip); serv_addr.sin_port = htons(atoi(serv_port));
sockaddr_in地址在綁定到socket套接字時,須要先初始化並賦值。memset將sockaddr_in內存空間的全部值置0,主要是將sockaddr_in沒有使用到的第4個保留參數sin_zero置0,不然可能會引發一些未知的錯誤(好比在與sockaddr結構體轉換的過程引入的垃圾值所帶來的影響)。
INADDR_ANY
以前的系列文章中提到過關於INADDR_ANY地址的思考,INADDR_ANY的做用是能夠自動獲取運行服務器端計算機的IP地址。若同一計算機中已分配多個IP地址(多宿主(Multi-homed)計算機,通常路由器屬於這一類),則只要端口號一致,就能夠從不一樣IP地址接收數據。所以,服務器端會優先考慮這種方式。
建立服務器端套接字爲什麼須要配置IP地址?如上所述,同一計算機中能夠分配多個IP地址,實際IP地址的個數與計算機中安裝的NIC數量相等,所以,即便是服務器端也須要決定接收那個IP傳來的數據。而若是服務器端只有一個NIC,則可直接使用INADDR_ANY便可。
關於127.0.0.1地址
127.0.0.1是迴環地址(loopback address),指的是計算機自身的IP地址。若是服務器端和客戶端都運行在同一臺計算機中,則使用該地址替換計算機的實際IP地址仍可正常運行。