1、套接字概述
1.1、套接字定義
套接字最早是由BSD(伯克利軟件套件)在1982年引入的通信機制,目前已被廣泛移植到主流的操作系統中。
對於應用開發人員來說,套接字(socket)是一個抽象層,是一種特殊的I/O接口,獨立於具體協議的網絡編程接口,也是一種文件描述符。應用程序可以通過它發送或接收數據,可對其進行像對文件一樣的打開、讀寫和關閉等操作。
套接字允許應用程序將I/O插入到網絡中,並與網絡中的其他應用程序進行通信。
也就是說套接字是相對於網絡通信來說的,網絡套接字就是IP地址與端口的組合(套接字 = IP地址+TCP/UDP+端口號,套接字應該是成對的,本地(服務器)+ 外地(客戶端))。
Socket是一種常用的進程之間通信機制,不僅能實現本地不同進程之間的通信,而且通過網絡能夠在不同主機的進程之間進行通信。
對於網絡通信而言,每一個socket都可用網絡地址結構{協議、本地地址、本地端口}來表示。Socket通過一個專門的函數創建,並返回一個整形的socket描述符。隨后各種操作都是通過socket描述符來實現的。
socket 的原意是“插座”,在計算機通信領域,socket 被翻譯為“套接字”,它是計算機之間進行通信的一種約定或一種方式。通過 socket 這種約定,一台計算機可以接收其他計算機的數據,也可以向其他計算機發送數據。
我們把插頭插到插座上就能從電網獲得電力供應,同樣,為了與遠程計算機進行數據傳輸,需要連接到因特網,而 socket 就是用來連接到因特網的工具。
Socket 的典型應用就是 Web 服務器和瀏覽器:瀏覽器獲取用戶輸入的 URL,向服務器發起請求,服務器分析接收到的 URL,將對應的網頁內容返回給瀏覽器,瀏覽器再經過解析和渲染,就將文字、圖片、視頻等元素呈現給用戶。
套接字就是:一台主機的IP地址和端口號,套接字對就是互傳信息兩台主機的IP和端口號。在兩台主機connect時,就是通過對應的套接字聯系起來的。
對客戶來說:需要明確自己要連接的服務器IP和端口號,而自己的IP和端口號一般由內核默認了,會在連接后傳給服務器。
對服務器來說:需要明確自己監聽的本機的端口就行,本機的IP可由宏INADDR_ANY經轉換得到默認的IP給套接字結構。至於來自客戶的IP和端口可以不用管,接收任何主機的連接。
我們所說的 socket 編程,是站在傳輸層的基礎上,所以可以使用 TCP/UDP 協議,但是不能干「訪問網頁」這樣的事情,因為訪問網頁所需要的 http 協議位於應用層。
1.2、簡述
TCP/IP模型中的傳輸層實現端到端的通信,因此,每一個傳輸層連接有兩個端點。那么,傳輸層連接的端點是什么呢?不是主機,不是主機的IP地址,不是應用進程,也不是傳輸層的協議端口。傳輸層連接的端點叫做套接字(socket)。根據RFC793的定義:端口號拼接到IP地址就構成了套接字。所謂套接字,實際上是一個通信端點,每個套接字都有一個套接字序號,包括主機的IP地址與一個16位的主機端口號,即形如(主機IP地址:端口號)。例如,如果IP地址是210.37.145.1,而端口號是23,那么得到套接字就是(210.37.145.1:23)。
總之,套接字Socket =(IP地址:端口號),套接字的表示方法是點分十進制的IP地址后面寫上端口號,中間用冒號或逗號隔開。每一個傳輸層連接唯一地被通信兩端的兩個端點(即兩個套接字)所確定。
套接字可以看成是兩個網絡應用程序進行通信時,各自通信連接中的一個端點。通信時,其中的一個網絡應用程序將要傳輸的一段信息寫入它所在主機的Socket中,該Socket通過網絡接口卡的傳輸介質將這段信息發送給另一台主機的Socket中,使這段信息能傳送到其他程序中。因此,兩個應用程序之間的數據傳輸要通過套接字來完成。
在網絡應用程序設計時,由於TCP/IP的核心內容被封裝在操作系統中,如果應用程序要使用TCP/IP,可以通過系統提供的TCP/IP的編程接口來實現。在Windows環境下,網絡應用程序編程接口稱作Windows Socket。為了支持用戶開發面向應用的通信程序,大部分系統都提供了一組基於TCP或者UDP的應用程序編程接口(API),該接口通常以一組函數的形式出現,也稱為套接字(Socket)。
1.3、發展
Socket最初是加利福尼亞大學Berkeley分校為Unix系統開發的網絡通信接口。后來隨着TCP/IP網絡的發展,Socket成為最為通用的應用程序接口,也是在Internet上進行應用開發最為通用的API。
Windows系統流行起來之后,由Microsoft聯合了其他幾家公司在Berkeley Sockets的基礎之上進行了擴充(主要是增加了一些異步函數,並增加了符合Windows消息驅動特性的網絡事件異步選擇機制),共同制定了一套Windows下的網絡編程接口,即Windows Sockets規范。Windows Sockets規范是一套開放的、支持多種協議的Windows下的網絡編程接口,包括1.1版和2.0版兩個版本。其中1.1版只支持TCP/IP協議,而2.0版可以支持多協議,2.0版有良好的向后兼容性。當前Windows下的Internet軟件絕大部分都是基於Windows Socks開發的
1.4、UNIX/Linux中的socket和windows中的socket
(1)、UNIX/Linux 中的 socket 是什么?
在 UNIX/Linux 系統中,為了統一對各種硬件的操作,簡化接口,不同的硬件設備也都被看成一個文件。對這些文件的操作,等同於對磁盤上普通文件的操作。
在UNIX/Linux 中,一切皆文件。
為了表示和區分已經打開的文件,UNIX/Linux 會給每個文件分配一個 ID,這個 ID 就是一個整數,被稱為文件描述符(File Descriptor)。例如:
通常用 0 來表示標准輸入文件(stdin),它對應的硬件設備就是鍵盤;
通常用 1 來表示標准輸出文件(stdout),它對應的硬件設備就是顯示器。
UNIX/Linux 程序在執行任何形式的 I/O 操作時,都是在讀取或者寫入一個文件描述符。一個文件描述符只是一個和打開的文件相關聯的整數,它的背后可能是一個硬盤上的普通文件、FIFO、管道、終端、鍵盤、顯示器,甚至是一個網絡連接。
請注意,網絡連接也是一個文件,它也有文件描述符!
我們可以通過 socket() 函數來創建一個網絡連接,或者說打開一個網絡文件,socket() 的返回值就是文件描述符。有了文件描述符,我們就可以使用普通的文件操作函數來傳輸數據了,例如:
用 read() 讀取從遠程計算機傳來的數據;
用 write() 向遠程計算機寫入數據。
只要用 socket() 創建了連接,剩下的就是文件操作了。
(2)Window 系統中的 socket 是什么?
Windows 也有類似“文件描述符”的概念,但通常被稱為“文件句柄”。
與 UNIX/Linux 不同的是,Windows 會區分 socket 和文件,Windows 就把 socket 當做一個網絡連接來對待,因此需要調用專門針對 socket 而設計的數據傳輸函數,針對普通文件的輸入輸出函數就無效了。
1.5、為什么需要socket
普通的I/O操作過程:打開文件>>>>>讀/寫文件>>>>>關閉文件
同一台主機上的兩個進程可以通過管道、信號、共享內存等進行通信,那么兩個不在同一台主機上的進程怎么進行通信呢?
TCP/IP協議被集成到操作系統的內核中,這就引入了新型的“I/O”操作,當兩個不在一台主機上的進程要進行通信是,這時就需要socket
2、套接字類型
套接字類型指的是套接字的數據傳輸方式,通過socket函數的第二個參數傳遞,只有這樣才能決定創建的套接字的數據傳輸方式
套接字(socket)分為很多種,比如 DARPA Internet 地址(Internet 套接字)、本地節點的路徑名(Unix套接字)、CCITT X.25地址(X.25 套接字)等。這里只講第一種套接字——Internet 套接字,它是最具代表性的,也是最經典最常用的。根據數據的傳輸方式,可以將 Internet 套接字分成兩種類型,流式套接字和數據報套接字。通過 socket() 函數創建連接時,必須告訴它使用哪種數據傳輸方式。
2.1、三種常用的套接字
為了滿足不同的通信程序對通信質量和性能的要求,一般的網絡系統提供了三種不同類型的套接字,以供用戶在設計網絡應用程序時根據不同的要求來選擇。這三種套接為流式套接字(SOCK-STREAM)、數據報套接字(SOCK-DGRAM)和原始套接字(SOCK-RAW)。
(1)、流式套接字(SOCK_STREAM)-----面向連接的套接字
流格式套接字(Stream Sockets)也叫“面向連接的套接字”,在代碼中使用 SOCK_STREAM 表示。它提供了一種可靠的、面向連接的雙向數據傳輸服務,實現了數據無差錯、無重復的發送,保證數據傳輸的可靠性和按序收發。如果數據損壞或丟失,可以重新發送。流格式套接字有自己的糾錯機制。流式套接字內設流量控制,被傳輸的數據看作是無記錄邊界的字節流。在TCP/IP協議簇中,使用TCP協議來實現字節流的傳輸,當用戶想要發送大批量的數據或者對數據傳輸有較高的要求時,可以使用流式套接字。
流失套接字特點:
- 傳輸過程中數據不會丟失
- 按序傳輸數據
- 傳輸的數據不存在數據邊界
可以將 SOCK_STREAM 比喻成一條傳送帶,只要傳送帶本身沒有問題(不會斷網),就能保證數據不丟失;同時,較晚傳送的數據不會先到達,較早傳送的數據不會晚到達,這就保證了數據是按照順序傳遞的。
為什么流格式套接字可以達到高質量的數據傳輸呢?這是因為它使用了 TCP 協議(The Transmission Control Protocol,傳輸控制協議),TCP 協議會控制你的數據按照順序到達並且沒有錯誤。
TCP 用來確保數據的正確性,IP(Internet Protocol,網絡協議)用來控制數據如何從源頭到達目的地,也就是常說的“路由”。
那么,“數據的發送和接收不同步”該如何理解呢?
假設傳送帶傳送的是水果,接收者需要湊齊 100 個后才能裝袋,但是傳送帶可能把這 100 個水果分批傳送,比如第一批傳送 20 個,第二批傳送 50 個,第三批傳送 30 個。接收者不需要和傳送帶保持同步,只要根據自己的節奏來裝袋即可,不用管傳送帶傳送了幾批,也不用每到一批就裝袋一次,可以等到湊夠了 100 個水果再裝袋。
流格式套接字的內部有一個緩沖區(也就是字符數組),通過 socket 傳輸的數據將保存到這個緩沖區。接收端在收到數據后並不一定立即讀取,只要數據不超過緩沖區的容量,接收端有可能在緩沖區被填滿以后一次性全部讀取,也可能分成好幾次讀取。也就是說,在面向連接的套接字中,read函數和write函數的調用次數並無太大意義。所以說面向連接的套接字不存在數據邊界。
也就是說,不管數據分幾次傳送過來,接收端只需要根據自己的要求讀取,不用非得在數據到達時立即讀取。傳送端有自己的節奏,接收端也有自己的節奏,它們是不一致的。
那么接收數據的緩沖區被接收的數據填滿會怎樣,之后傳遞的數據是否會丟失呢?
首先調用read函數從緩沖區讀取部分數據,因此,緩沖區並不總是滿的。但如果read函數讀取速度比接收數據的速度慢,則緩沖區有可能被填滿。此時套接字無法再接收數據,但即使這樣也不會丟失數據,因為這時傳輸端套接字將停止傳輸。也就是說,面向連接的套接字會根據接收端的狀態進行數據傳輸,如果傳輸出錯還會提供重傳服務。因此,面向連接的套接字除特殊情況外不會發生數據丟失。
流格式套接字有什么實際的應用場景嗎?瀏覽器所使用的 http 協議就基於面向連接的套接字,因為必須要確保數據准確無誤,否則加載的 HTML 將無法解析。
(2)、數據報套接字(SOCK_DGRAM)-----面向消息的套接字
數據報格式套接字(Datagram Sockets)也叫“無連接的套接字”,在代碼中使用 SOCK_DGRAM 表示。它提供了一種無連接、不可靠的雙向數據傳輸服務。數據通過相互獨立的報文進行傳輸,是無序的,並且保留了記錄邊界,不提供可靠性保證。數據在傳輸過程中可能會丟失或重復,並且不能保證在接收端按發送順序接收數據。計算機只管傳輸數據,不作數據校驗,如果數據在傳輸中損壞,或者沒有到達另一台計算機,是沒有辦法補救的。也就是說,數據錯了就錯了,無法重傳。在TCP/IP協議簇中,使用UDP協議來實現數據報套接字。在出現差錯的可能性較小或允許部分傳輸出錯的應用場合,可以使用數據報套接字進行數據傳輸,這樣通信的效率較高。
因為數據報套接字所做的校驗工作少,所以在傳輸效率方面比流格式套接字要高。
可以將 SOCK_DGRAM 比喻成高速移動的摩托車快遞,它有以下特征:
- 強調快速傳輸而非傳輸順序
- 傳輸的數據可能丟失也可能損毀
- 限制每次傳輸的數據大小
- 數據的發送和接收是同步的(有的教程也稱“存在數據邊界”)
面向消息的套接字比面向連接的套接字具有更快的傳輸速度,但無法避免數據丟失或損毀。另外,每次傳輸的數據大小具有一定限制,並存在數據邊界。存在數據邊界意味着接收數據的次數應和傳輸次數相同。總之,數據報套接字是一種不可靠的、不按順序傳遞的、以追求傳輸速度為目的的套接字。
數據報套接字也使用 IP 協議作路由,但是它不使用 TCP 協議,而是使用 UDP 協議(User Datagram Protocol,用戶數據報協議)。
QQ 視頻聊天和語音聊天就使用 SOCK_DGRAM 來傳輸數據,因為首先要保證通信的效率,盡量減小延遲,而數據的正確性是次要的,即使丟失很小的一部分數據,視頻和音頻也可以正常解析,最多出現噪點或雜音,不會對通信質量有實質的影響。
(3)、原始套接字(SOCK_RAM)
該套接字允許對較低層協議(如IP或ICMP)進行直接訪問,它功能強大使用較為不便,常用於網絡協議分析,檢驗新的網絡協議實現,也可用於測試新配置或安裝的網絡設備。
2.2、面向連接和無連接的套接字的區別
流格式套接字(Stream Sockets)就是“面向連接的套接字”,它基於 TCP 協議;數據報格式套接字(Datagram Sockets)就是“無連接的套接字”,它基於 UDP 協議。
這給大家造成一種印象,面向連接就是可靠的通信,無連接就是不可靠的通信,實際情況是這樣嗎?另外,不管是哪種數據傳輸方式,都得通過整個 Internet 網絡的物理線路將數據傳輸過去,從這個層面理解,所有的 socket 都是有物理連接的呀,為什么還有無連接的 socket 呢?
從字面上理解,面向連接好像有一條管道,它連接發送端和接收端,數據包都通過這條管道來傳輸。當然,兩台計算機在通信之前必須先搭建好管道。無連接好像沒頭蒼蠅亂撞,數據包從發送端到接收端並沒有固定的線路,愛怎么走就怎么走,只要能到達就行。每個數據包都比較自私,不和別人分享自己的線路,但是,大家最終都能殊途同歸,到達接收端。
上圖是一個簡化的互聯網模型,H1 ~ H6 表示計算機,A~E 表示路由器,發送端發送的數據必須經過路由器的轉發才能到達接收端。
假設 H1 要發送若干個數據包給 H6,那么有多條路徑可以選擇,比如:
路徑①:H1 --> A --> C --> E --> H6
路徑②:H1 --> A --> B --> E --> H6
路徑③:H1 --> A --> B --> D --> E --> H6
路徑④:H1 --> A --> B --> C --> E --> H6
路徑⑤:H1 --> A --> C --> B --> D --> E --> H6
數據包的傳輸路徑是路由器根據算法來計算出來的,算法會考慮很多因素,比如網絡的擁堵狀況、下一個路由器是否忙碌等。
(1)、無連接的套接字
對於無連接的套接字,每個數據包可以選擇不同的路徑,比如第一個數據包選擇路徑④,第二個數據包選擇路徑①,第三個數據包選擇路徑②……當然,它們也可以選擇相同的路徑,那也只不過是巧合而已。
每個數據包之間都是獨立的,各走各的路,誰也不影響誰,除了迷路的或者發生意外的數據包,最后都能到達 H6。但是,到達的順序是不確定的,比如:
第一個數據包選擇了一條比較長的路徑(比如路徑⑤),第三個數據包選擇了一條比較短的路徑(比如路徑①),雖然第一個數據包很早就出發了,但是走的路比較遠,最終還是晚於第三個數據包達到。
第一個數據包選擇了一條比較短的路徑(比如路徑①),第三個數據包選擇了一條比較長的路徑(比如路徑⑤),按理說第一個數據包應該先到達,但是非常不幸,第一個數據包走的路比較擁堵,這條路上的數據量非常大,路由器處理得很慢,所以它還是晚於第三個數據包達到了。
還有一些意外情況會發生,比如:
第一個數據包選擇了路徑①,但是路由器C突然斷電了,那它就到不了 H6 了。
第三個數據包選擇了路徑②,雖然路不遠,但是太擁堵,以至於它等待的時間太長,路由器把它丟棄了。
總之,對於無連接的套接字,數據包在傳輸過程中會發生各種不測,也會發生各種奇跡。H1 只負責把數據包發出,至於它什么時候到達,先到達還是后到達,有沒有成功到達,H1 都不管了;H6 也沒有選擇的權利,只能被動接收,收到什么算什么,愛用不用。
無連接套接字遵循的是「盡最大努力交付」的原則,就是盡力而為,實在做不到了也沒辦法。無連接套接字提供的沒有質量保證的服務。
(2)、面向連接的套接字
面向連接的套接字在正式通信之前要先確定一條路徑,沒有特殊情況的話,以后就固定地使用這條路徑來傳遞數據包了。當然,路徑被破壞的話,比如某個路由器斷電了,那么會重新建立路徑。
這條路徑是由路由器維護的,路徑上的所有路由器都要存儲該路徑的信息(實際上只需要存儲上游和下游的兩個路由器的位置就行),所以路由器是有開銷的。
H1 和 H6 通信完畢后,要斷開連接,銷毀路徑,這個時候路由器也會把之前存儲的路徑信息擦除。
在很多網絡通信教程中,這條預先建立好的路徑被稱為“虛電路”,就是一條虛擬的通信電路。
為了保證數據包准確、順序地到達,發送端在發送數據包以后,必須得到接收端的確認才發送下一個數據包;如果數據包發出去了,一段時間以后仍然沒有得到接收端的回應,那么發送端會重新再發送一次,直到得到接收端的回應。這樣一來,發送端發送的所有數據包都能到達接收端,並且是按照順序到達的。
發送端發送一個數據包,如何得到接收端的確認呢?很簡單,為每一個數據包分配一個 ID,接收端接收到數據包以后,再給發送端返回一個數據包,告訴發送端我接收到了 ID 為 xxx 的數據包。
面向連接的套接字會比無連接的套接字多出很多數據包,因為發送端每發送一個數據包,接收端就會返回一個數據包。此外,建立連接和斷開連接的過程也會傳遞很多數據包。
不但是數量多了,每個數據包也變大了:除了源端口和目的端口,面向連接的套接字還包括序號、確認信號、數據偏移、控制標志(通常說的 URG、ACK、PSH、RST、SYN、FIN)、窗口、校驗和、緊急指針、選項等信息;而無連接的套接字則只包含長度和校驗和信息。
有連接的數據包比無連接大很多,這意味着更大的負載和更大的帶寬。許多即時聊天軟件采用 UDP 協議(無連接套接字),與此有莫大的關系。
(3)、總結
兩種套接字各有優缺點:
無連接套接字傳輸效率高,但是不可靠,有丟失數據包、搗亂數據的風險;
有連接套接字非常可靠,萬無一失,但是傳輸效率低,耗費資源多。
兩種套接字的特點決定了它們的應用場景,有些服務對可靠性要求比較高,必須數據包能夠完整無誤地送達,那就得選擇有連接的套接字(TCP 服務),比如 HTTP、FTP 等;而另一些服務,並不需要那么高的可靠性,效率和實時才是它們所關心的,那就可以選擇無連接的套接字(UDP 服務),比如 DNS、即時聊天工具等。
3、分配給套接字的IP地址、MAC和端口號
IP是internet protocol(網絡協議)的簡寫,是為收發網絡數據而分配給計算機的值。端口並非賦予計算機的值,而是為區分程序中創建的套接字而分配給套接字的序號。
3.1、IP地址
在茫茫的互聯網海洋中,要找到一台計算機非常不容易,有三個要素必須具備,它們分別是 IP 地址、MAC 地址和端口號。
IP地址是 Internet Protocol Address 的縮寫,譯為“網際協議地址”。
為使計算機連接到網絡並收發數據,必須向其分配IP地址。IP地址分為兩類:
- IPv4(Internet Protocol version 4):4字節地址族
- IPv6(Internet Protocol version 6):16字節地址族
目前大部分軟件使用 IPv4 地址,但 IPv6 也正在被人們接受,尤其是在教育網中,已經大量使用。
一台計算機可以擁有一個獨立的 IP 地址,一個局域網也可以擁有一個獨立的 IP 地址(對外就好像只有一台計算機)。對於目前廣泛使用 IPv4 地址,它的資源是非常有限的,一台計算機一個 IP 地址是不現實的,往往是一個局域網才擁有一個 IP 地址。
在因特網上進行通信時,必須要知道對方的 IP 地址。實際上數據包中已經附帶了 IP 地址,把數據包發送給路由器以后,路由器會根據 IP 地址找到對方的地里位置,完成一次數據的傳遞。路由器有非常高效和智能的算法,很快就會找到目標計算機。
3.1.1、IP地址的作用
IP地址用來標識網絡中的一台主機。根據不同的協議版本,分為IPv4(32位)和IPv6(128位)。
IPV4和IPV6的差別主要是表示IP地址所用的字節數,目前通用的地址族為IPV4。IPV6是為了應對2010年前后IP地址耗盡的問題而提出的標准,即便如此,現在還主要使用IPV4,IPV6的普及將需要更長時間。
一個IP地址主要包含兩部分:網絡號和主機號。網絡號表示其屬於互聯網的哪一個局域網絡,主機號表示其屬於該局域網絡中的哪一台主機。二者是主從關系。其中,網絡號和主機號根據子網掩碼來區分。簡單的說,有了源IP和目標IP,數據包就能在不同的主機之間傳輸。
網絡地址(網絡ID)是為區分網絡而設置的一部分IP地址。假設向WWW.SEMI.COM公司傳輸數據,該公司內部構建了局域網,把所有計算機連起來。因此,首先應向SEMI.COM網絡傳輸數據,也就是說,並非一開始就瀏覽所有4字節IP地址,進而找到目標主機;而是僅瀏覽4字節IP地址的網絡地址,先把數據送到SEMI.COM網絡,SEMI.COM網絡(構成網絡的路由器)接收到數據后,瀏覽傳輸數據的主機地址(主機ID)並將數據傳給目標主機。下圖展示了數據傳輸過程:
某主機向203.211.172.103和203.211.217.202傳輸數據,其中203.211.172和203.211.217為該網絡的網絡地址。所以,“向相應網絡傳輸數據”實際上是向構成網絡的路由器(Route)或交換機(Switch)傳遞數據,由接收數據的路由器根據數據中的主機地址向目標主機傳遞數據
若想構建網絡,需要一種物理設備完成外網與本網主機之間的數據交換,這種設備便是路由器或交換機。它們實際上也是一種計算機,只不過是為特殊目的而設計運行的,因此有了別名。所以,如果在我們使用的計算機上安裝適當的軟件,也可以將其作為交換機。另外,交換機比路由器功能要簡單一些,但實際用途差別不大
3.1.2、IPv4地址的分類
IP地址根據網絡號和主機號來分,分為A、B、C三類及特殊地址D、E。
A類:(1.0.0.0-126.0.0.0)(默認子網掩碼:255.0.0.0或 0xFF000000)第一個字節為網絡號,后三個字節為主機號。該類IP地址的最前面為“0”,所以地址的網絡號取值於1~126之間。一般用於大型網絡。地址范圍1.0.0.1~126.255.255.254。
B類:(128.0.0.0-191.255.0.0)(默認子網掩碼:255.255.0.0或0xFFFF0000)前兩個字節為網絡號,后兩個字節為主機號。該類IP地址的最前面為“10”,所以地址的網絡號取值於128~191之間。一般用於中等規模網絡。地址范圍128.0.0.1~191.254.255.254。
C類:(192.0.0.0-223.255.255.0)(子網掩碼:255.255.255.0或 0xFFFFFF00)前三個字節為網絡號,最后一個字節為主機號。該類IP地址的最前面為“110”,所以地址的網絡號取值於192~223之間。一般用於小型網絡。地址范圍192.0.1.1~223.255.254.254。
D類:組播地址。該類IP地址的最前面為“1110”,所以地址的網絡號取值於224~239之間。一般用於多路廣播用戶,每一個組播地址代表一個多播組 。地址范圍224.0.0.1~239.255.255.254。
E類:保留地址。該類IP地址的最前面為“1111”,所以地址的網絡號取值於240~255之間。
在IP地址3種主要類型里,各保留了3個區域作為私有地址,其地址范圍如下:
A類地址:10.0.0.0~10.255.255.255
B類地址:172.16.0.0~172.31.255.255
C類地址:192.168.0.0~192.168.255.255
回送地址:127.0.0.1。 也是本機地址,等效於localhost或本機IP。一般用於測試使用。例如:ping 127.0.0.1來測試本機TCP/IP是否正常。
3.1.3、地址數據結構體
1 struct sockaddr 2 { 3 unsigned short sa_family; /*地址族*/ 4 char sa_data[14]; /*IP地址和端口號*/ 5 } 6 7 struct sockaddr_in 8 { 9 short int sin_family; /*地址族*/ 10 struct in_addr sin_addr; /*IP地址*/ 11 unsigned short int sin_port; /*端口號*/ 12 unsigned char sin_zero[8]; /*不使用,一般用0填充,以保持與struct sockaddr同樣大小*/ 13 } 14 15 struct in_addr 16 { 17 in_addr_t s_addr; /*IP地址*/ 18 } 19 20 21 struct sockaddr_in6 22 { 23 sa_family_t sin6_family; /*地址類型,取值為AF_INET6*/ 24 in_port_t sin6_port; /*16位端口號*/ 25 uint32_t sin6_flowinfo; /*IPv6流信息*/ 26 struct in6_addr sin6_addr; /*具體的IPv6地址*/ 27 uint32_t sin6_scope_id; /*接口范圍ID*/ 28 };
uint16_t、in_addr_t等類型可以參考POSIX(可移植操作系統接口),POSIX是為UNIX系列操作系統設立的標准,它定義了一些其它的數據類型,如下表所示:
//POSIX中定義的數據類型
int8_t signed 8-bit int 帶符號8位整數 <sys/types.h> uint8_t unsigned 8-bit int(unsigned char) 無符號8位整數 <sys/types.h> int16_t signed 16-bit int帶符號16位整數 <sys/types.h> uint16_t unsigned 16-bit int(unsigned short) 無符號16位整數 <sys/types.h> int32_t signed 32-bit 帶符號32未整數 <sys/types.h> uint32_t unsigned 32-bit int(unsigned int) 無符號32位整數 <sys/types.h>
sa_family_t 地址族(address family) 套接字地址結構的協議簇 <sys/socket.h> socklen_t 長度(length of struct) 套接字地址結構的長度 <sys/socket.h>
in_addr_t IP地址,聲明為uint32_t IPV4地址 <netinet/in.h> in_port_t 端口號,聲明為uint16_t TCP/UDP端口 <netinet/in.h>
這兩個數據類型大小相同,通常用sockaddr_in來保存某個網絡地址,在使用時強轉成sockaddr類型的指針。
結構定義頭文件 |
#include <netinet/in.h> |
sin_family
|
AF_INET:IPv4協議 |
AF_INET6:IPv6協議 |
|
AF_LOCAL:UNIX域協議 |
|
AF_LINK:鏈路地址協議 |
|
AF_KEY:密鑰套接字 |
|
AF_INET:IPv4協議 |
3.1.4、IP地址格式轉換
IP地址有兩種不同的格式:十進制點分形式和32位二進制形式。前者是用戶所熟悉的形式,而后者則是網絡傳輸中IP地址的存儲方式。
IPv4地址轉換函數有inet_addr()、inet_aton()、inet_ntoa(),而IPv4和IPv6兼容的函數有inet_pton()和inet_ntop()。由於IPv6是下一代互聯網的標准協議,因此這里講解主要以IPv4為主。
inet_addr()和inet_pton()函數是將十進制點分形式轉換成二進制形式,而inet_ntop()是將二進制地址形式轉換為十進制點分形式。
頭文件 |
#include <arpa/inet.h> |
|
函數原型 |
int inet_addr(const char *strptr); |
|
作用 |
將十進制IP地址轉換成二進制,適用於IPv4,該函數在轉換類型的同時進行網絡字節序的轉換 |
|
參數 |
Strptr |
要轉換的IP地址字符串 |
返回值 |
成功 |
32位二進制IP地址(網絡字節序) |
失敗 |
-1 |
頭文件 |
#include <arpa/inet.h> |
|
函數原型 |
int inet_aton(const char* strptr,struct in_addr* addrptr); |
|
作用 |
將十進IP地址制轉換成二進制IP地址,該函數在轉換類型的同時進行網絡字節序的轉換 |
|
參數 |
strptr |
要轉換的十進制IP地址 |
dst |
存放轉換后的二進制IP地址 |
|
返回值 |
成功 |
非0 |
失敗 |
0 |
頭文件 |
#include <arpa/inet.h> |
|
函數原型 |
char * inet_ntoa(struct in_addr addrptr); |
|
作用 |
將二進IP地址制轉換成十進制IP地址 |
|
參數 |
strptr |
要轉換的十進制IP地址 |
返回值 |
成功 |
返回十進制的IP地址 |
失敗 |
0 |
頭文件 |
#include <arpa/inet.h> |
|
函數原型 |
int inet_pton(int family,const char *src,void *dst); |
|
作用 |
將十進制IP地址轉換成二進制,IPv4和IPv6兼容 |
|
參數 |
family |
AF_INET:IPv4 |
AF_INET6:IPv6 |
||
src |
要轉換的IP地址字符串 |
|
dst |
存放轉換后的地址的緩沖區 |
|
返回值 |
成功 |
0 |
失敗 |
-1 |
頭文件 |
#include <arpa/inet.h> |
|
函數原型 |
int inet_ntop(int family,void *src,char *dst,size_t len); |
|
作用 |
將二進制IP地址轉換成十進制,IPv4和IPv6兼容 |
|
參數 |
family |
AF_INET:IPv4 |
AF_INET6:IPv6 |
||
src |
要轉換的二進制IP地址 |
|
dst |
存放十進制地址字符串的緩沖區 |
|
len |
緩沖區的長度 |
|
返回值 |
成功 |
返回dst |
失敗 |
NULL |
在VS2013以后的版本中,增加了inet_pton()、inet_ntop()之類的新函數,用於IP地址在“點分十進制”和“二進制整數”之間轉換,並且能夠處理ipv4和ipv6。而inet_addr是老函數,高版本VS在編譯時默認使用了新函數,所以會報該錯誤。
問題的解決:
(1)、用inet_pton新函數代替inet_addr函數。
(2)、修改VS配置,告訴它我就要舊函數,修改方法:項目->屬性->C/C++->常規->SDL檢查,將“是”改為“否”,即可。
3.2、MAC地址
現實的情況是,一個局域網往往才能擁有一個獨立的 IP;換句話說,IP 地址只能定位到一個局域網,無法定位到具體的一台計算機。這可怎么辦呀?這樣也沒法通信啊。
其實,真正能唯一標識一台計算機的是 MAC 地址,每個網卡的 MAC 地址在全世界都是獨一無二的。計算機出廠時,MAC 地址已經被寫死到網卡里面了(當然通過某些“奇巧淫技”也是可以修改的)。局域網中的路由器/交換機會記錄每台計算機的 MAC 地址。
MAC 地址是 Media Access Control Address 的縮寫,直譯為“媒體訪問控制地址”,也稱為局域網地址(LAN Address),以太網地址(Ethernet Address)或物理地址(Physical Address)。
數據包中除了會附帶對方的 IP 地址,還會附帶對方的 MAC 地址,當數據包達到局域網以后,路由器/交換機會根據數據包中的 MAC 地址找到對應的計算機,然后把數據包轉交給它,這樣就完成了數據的傳遞。
3.3、端口號
有了 IP 地址和 MAC 地址,雖然可以找到目標計算機,但仍然不能進行通信。一台計算機可以同時提供多種網絡服務,例如 Web 服務(網站)、FTP 服務(文件傳輸服務)、SMTP 服務(郵箱服務)等,僅有 IP 地址和 MAC 地址,計算機雖然可以正確接收到數據包,但是卻不知道要將數據包交給哪個網絡程序(即哪個進程)來處理,所以通信失敗。
為了區分不同的網絡程序,計算機會為每個網絡程序分配一個獨一無二的端口號(Port Number),例如,Web 服務的端口號是 80,FTP 服務的端口號是 21,SMTP 服務的端口號是 25。
端口(Port)是一個虛擬的、邏輯上的概念。可以將端口理解為一道門,數據通過這道門流入流出,每道門有不同的編號,就是端口號。
端口號是一個無符號短整形(unsigned short int),取值范圍0到65535。端口號是系統的一種資源,0~1023一般被系統程序所使用,1~255之間為眾所周知的端口,256~1023端口通常由UNIX系統占用,1024~49151位已登記端口,49152~65535為動態或私有端口。TCP端口號和UDP端口號獨立,互不影響。建議使用>=50000的端口。
如果說IP地址可以用來表示網絡中的一台主機,那么端口號可以用來表示主機內部的某個套接字。換句話說,當一個套接字創建好后,需要把它和某個IP地址及端口號綁定,這樣雙方才能實現端到端通信。
IP用於區分計算機,只要有IP地址就能找到目標主機,但僅憑這些無法傳輸給目標主機中的應用程序,畢竟處理數據靠的還是目標主機中的程序。假設用戶在上網的同時,一邊欣賞視頻,一邊瀏覽網頁,這里至少需要兩個套接字,一個接收視頻數據,一個接收網頁數據,那么問題來了,怎么區分這兩個套接字呢?或者說,怎么區分到達的數據是正在觀看的視頻,還是正在瀏覽的網頁呢?這里就需要用到端口號了
計算機中一般配有NIC(Network Interface Card,網絡接口卡)數據傳輸設備。通過NIC向計算機內部傳輸數據時會用到IP,操作系統負責把傳遞到內部的數據適配給套接字,這時就要利用端口號了。也就是說,通過NIC接收的數據內有端口號,操作系統正是參考此端口號把數據傳輸給相應端口的套接字,如圖1-3所示
圖1-3 數據分配過程
端口號就是同一操作系統內區分不同套接字而設置的,因此無法將一個端口號分配給不同套接字。另外,端口號由16位構成,可分配的端口號范圍是0~65535。但0~1023是知名端口(Well-known PORT),一般分配給特定應用程序,所以應當分配此范圍之外的端口。另外,雖然端口號不能重復,但TCP套接字和UDP套接字不會共用端口號,所以允許重復。例如:如果某TCP套接字使用8500號端口,則其他TCP套接字就無法使用該端口號,但UDP套接字可以使用
總之,數據傳輸目標地址同時包含IP地址和端口號,只有這樣,數據才會被傳輸到最終的目的應用程序
4、字節序
字節序又稱為主機字節序,是指計算機中多字節整形數據的存儲方式。字節序有兩種:大端(低地址存高位字節,高地址存低位字節)和小端(低字節存低位字節,高地址存高位字節),在網絡通信中,發送方和接收方有可能使用不同的字節序,為了保證數據接收后能正確的解析處理,統一規定:數據以大端字節序在網絡上傳輸。因此數據在發送前和接收后都需要在主機字節序和網絡字節序之間轉換。
4.1、函數說明
字節序轉換涉及4個函數:htons()、ntohs()、htonl()和ntohl()。這里的h代表host,n代表network,s代表short,l代表long,l代表long。通常16bit的IP端口號用前兩個函數處理,而IP地址用后兩個函數來轉換。調用這些函數只是使其得到相應的字節序,用戶不需要知道該系統的主機字節序和網絡字節序是否真正相等。如果相同不需要轉換的話,該系統的這些函數會定義成空宏。
4.2、函數格式
頭文件 |
#include <netinet/in.h> |
|
函數原型 |
unisgned short htons(unisgned short hostshort); //將16位主機字節順序轉變成網絡字節順序 unisgned short ntohs(unisgned short netshort); //將16位網絡字節順序轉變成主機字節順序 unisgned long htonl(unisgned long hostlong); //將32位主機字節順序轉變成網絡字節順序 unisgned long ntohl(unisgned long netlong); //將32位網絡字節順序轉變成主機字節順序 (h(host)代表主機字節序,n(network)代表網絡字節序) |
|
參數 |
hostshort |
16bit數據的主機字節序 |
netshort |
16bit數據的網絡字節序 |
|
hostlong |
32bit數據的主機字節序 |
|
netlong |
32bit數據的網絡字節序 |
|
返回值 |
成功 |
返回轉換字節序后的數值 |
失敗 |
-1 |