一、部分基礎(chǔ)知識(shí)
簡(jiǎn)單講一下基礎(chǔ)知識(shí),便于后面代碼的理解,建議大概瀏覽一下這一小節(jié)內(nèi)容。這里講的只是冰山一角,建議大家學(xué)習(xí)計(jì)算機(jī)網(wǎng)絡(luò)相關(guān)知識(shí),推薦幾本書:
- 《計(jì)算機(jī)網(wǎng)絡(luò)》(謝希仁)
- 《計(jì)算機(jī)網(wǎng)絡(luò) 自頂向下方法》
- 《計(jì)算機(jī)網(wǎng)絡(luò)技術(shù)》
- 《計(jì)算機(jī)網(wǎng)絡(luò)基礎(chǔ)及應(yīng)用》
- 《Linux C從入門到精通》
1.1 計(jì)算機(jī)網(wǎng)絡(luò)的體系結(jié)構(gòu)
1.11 互聯(lián)網(wǎng)簡(jiǎn)介
互聯(lián)網(wǎng)是一個(gè)巨大的網(wǎng)絡(luò)系統(tǒng),從工作方式上看,它由邊緣部分和核心部分組成。
邊緣部分是指連接到互聯(lián)網(wǎng)的終端設(shè)備,如手機(jī)、電腦、路由器等;而核心部分則是指連接這些設(shè)備的網(wǎng)絡(luò)基礎(chǔ)設(shè)施。
-
邊緣部分
- 邊緣部分指代連接到互聯(lián)網(wǎng)的終端用戶設(shè)備,如臺(tái)式電腦、筆記本電腦、智能手機(jī)、平板電腦等。這些設(shè)備都有一個(gè)共同的特點(diǎn),就是它們都使用TCP/IP協(xié)議(準(zhǔn)確說(shuō)是基于TCP/IP協(xié)議簇的各種協(xié)議)來(lái)進(jìn)行網(wǎng)絡(luò)通信。這個(gè)協(xié)議有助于確保數(shù)據(jù)包從發(fā)送端到接收端的可靠性,并盡可能快地傳輸數(shù)據(jù)。
- 用戶設(shè)備通過各種途徑連接到互聯(lián)網(wǎng),例如通過寬帶或移動(dòng)網(wǎng)絡(luò)。這些設(shè)備通過接入網(wǎng)絡(luò)來(lái)訪問各種網(wǎng)絡(luò)應(yīng)用程序,如電子郵件、社交媒體和在線購(gòu)物等。邊緣部分是互聯(lián)網(wǎng)的主要組成部分,它為用戶提供訪問互聯(lián)網(wǎng)的途徑和服務(wù)。
- 邊緣部分指代連接到互聯(lián)網(wǎng)的終端用戶設(shè)備,如臺(tái)式電腦、筆記本電腦、智能手機(jī)、平板電腦等。這些設(shè)備都有一個(gè)共同的特點(diǎn),就是它們都使用TCP/IP協(xié)議(準(zhǔn)確說(shuō)是基于TCP/IP協(xié)議簇的各種協(xié)議)來(lái)進(jìn)行網(wǎng)絡(luò)通信。這個(gè)協(xié)議有助于確保數(shù)據(jù)包從發(fā)送端到接收端的可靠性,并盡可能快地傳輸數(shù)據(jù)。
-
核心部分
- 核心部分是指連接邊緣部分的基礎(chǔ)網(wǎng)絡(luò)設(shè)備,
如路由器、交換機(jī)、網(wǎng)關(guān)和服務(wù)器等
。這些設(shè)備通過全球互聯(lián)網(wǎng)絡(luò)將數(shù)據(jù)從一個(gè)地方傳輸?shù)搅硪粋€(gè)地方。核心部分是互聯(lián)網(wǎng)的支持結(jié)構(gòu),它確保在用戶設(shè)備之間進(jìn)行有效地網(wǎng)絡(luò)交換。 - 核心部分采用了復(fù)雜的技術(shù),如大規(guī)模的路由協(xié)議、數(shù)據(jù)流量控制和安全性等。這些技術(shù)確保所有數(shù)據(jù)在網(wǎng)絡(luò)中的傳輸是快速、安全和可靠的。
- 核心部分是指連接邊緣部分的基礎(chǔ)網(wǎng)絡(luò)設(shè)備,
從邏輯功能上可以劃分為:資源子網(wǎng)和通信子網(wǎng)。
- 資源子網(wǎng)
- 資源子網(wǎng)由主機(jī)、終端、終端控制器、聯(lián)網(wǎng)外設(shè)、各種軟件資源與信息資源組成。資源子網(wǎng)負(fù)責(zé)全網(wǎng)的數(shù)據(jù)處理業(yè)務(wù),并向網(wǎng)絡(luò)用戶提供各種網(wǎng)絡(luò)資源與網(wǎng)絡(luò)服務(wù)。連接到網(wǎng)絡(luò)中的計(jì)算機(jī)、文件服務(wù)器以及軟件構(gòu)成了網(wǎng)絡(luò)的資源子網(wǎng)。
- 通信子網(wǎng)
- 通信子網(wǎng)由網(wǎng)絡(luò)通信控制處理機(jī)、通信線路與其他通信設(shè)備組成,完成全網(wǎng)數(shù)據(jù)傳輸、轉(zhuǎn)發(fā)等通信處理工作。
1.12 計(jì)算機(jī)網(wǎng)絡(luò)的分類
計(jì)算機(jī)網(wǎng)絡(luò)有很多分類方式,至少需要了解一些名詞得含義(都是耳熟能詳?shù)模热缇钟蚓W(wǎng)、專用網(wǎng)、無(wú)線網(wǎng)等等。
(1)按照網(wǎng)絡(luò)的覆蓋范圍分類
-
廣域網(wǎng)
WAN
(Wide Area Network):廣域網(wǎng)的作用范圍通常為幾十到幾千公里,因而有時(shí)也稱為遠(yuǎn)程網(wǎng)(long haul network)。廣域網(wǎng)是互聯(lián)網(wǎng)的核心部分,其任務(wù)是通過長(zhǎng)距離(例如,跨越不同的國(guó)家)運(yùn)送主機(jī)所發(fā)送的數(shù)據(jù)。連接廣域網(wǎng)各結(jié)點(diǎn)交換機(jī)的鏈路一般都是高速鏈路,具有較大的通信容量。 -
城域網(wǎng)
MAN
(Metropolitan Area Network):城域網(wǎng)的作用范圍一般是一個(gè)城市,可跨越幾個(gè)街區(qū)甚至整個(gè)城市,其作用距離約為5~50km。城域網(wǎng)可以為一個(gè)或幾個(gè)單位所擁有,但也可以是一種公用設(shè)施,用來(lái)將多個(gè)局域網(wǎng)進(jìn)行互連。目前很多城域網(wǎng)采用的是以太網(wǎng)技術(shù),因此有時(shí)也常并入局域網(wǎng)的范圍進(jìn)行討論。 -
局域網(wǎng)
LAN
(Local Area Network):局域網(wǎng)一般用微型計(jì)算機(jī)或工作站通過高速通信線路相連(速率通常在10Mbit/s以上),但地理上則局限在較小的范圍(如1km左右)。在局域網(wǎng)發(fā)展的初期,一個(gè)學(xué)校或工廠往往只擁有一個(gè)局域網(wǎng),但現(xiàn)在局域網(wǎng)已非常廣泛地使用,學(xué)校或企業(yè)大都擁有許多個(gè)互連的局域網(wǎng)(這樣的網(wǎng)絡(luò)常稱為校園網(wǎng)或企業(yè)網(wǎng))。我們將在第3章3.3至3.5節(jié)詳細(xì)討論局域網(wǎng)。 -
個(gè)人區(qū)域網(wǎng)PAN(Personal Area Network)個(gè)人區(qū)域網(wǎng)就是在個(gè)人工作的地方把屬于個(gè)人使用的電子設(shè)備(如便攜式電腦等)用無(wú)線技術(shù)連接起來(lái)的網(wǎng)絡(luò),因此也常稱為無(wú)線個(gè)人區(qū)域網(wǎng)WPAN(Wireless PAN),其范圍很小,大約在10m左右。
若中央處理機(jī)之間的距離非常近(如僅1米的數(shù)量級(jí)或甚至更小些),則一般就稱之為多處理機(jī)系統(tǒng)而不稱它為計(jì)算機(jī)網(wǎng)絡(luò)。
(2)按照使用者劃分
- 公用網(wǎng):公用網(wǎng)由電信部門組建,一般由政府電信部門管理和控制,網(wǎng)絡(luò)內(nèi)的傳輸和交換裝置可提供(如租用)給任何部門和單位使用。公用網(wǎng)分為公共電話交換網(wǎng)(PSTN)、數(shù)字?jǐn)?shù)據(jù)(DDN)、綜合業(yè)務(wù)數(shù)字網(wǎng)(ISDN)等。
- 專用網(wǎng):專用網(wǎng)是由某個(gè)單位或部門組建的,不允許其他部門或單位使用,例如金融、鐵路等行業(yè)都有自己的專用網(wǎng)。專用網(wǎng)可以是租用電信部門的傳輸線路,也可以是自己鋪設(shè)的線路,但后者的成本非常高。
(3)按照傳輸介質(zhì)分
- 有線網(wǎng):有線網(wǎng)是指采用雙絞線、同軸電纜以及光纖作為傳輸介質(zhì)的計(jì)算機(jī)網(wǎng)絡(luò)。
- 無(wú)線網(wǎng):無(wú)線網(wǎng)是指使用空間電磁波作為傳輸介質(zhì)的計(jì)算機(jī)網(wǎng)絡(luò),它可以傳送無(wú)線電波和衛(wèi)星信號(hào)。無(wú)線網(wǎng)包括無(wú)線電話網(wǎng)、語(yǔ)音廣播網(wǎng)、無(wú)線電視網(wǎng)、微波通信網(wǎng)、衛(wèi)星通信網(wǎng)。
(4)按照交換技術(shù)分
- 電路交換網(wǎng)絡(luò):電路交換網(wǎng)絡(luò)指在進(jìn)行數(shù)據(jù)傳輸期間,發(fā)送點(diǎn)(源)與接收點(diǎn)(目的)之間構(gòu)成一條實(shí)際連接的專用物理線路,最典型的電路交換網(wǎng)絡(luò)就是公用電話交換網(wǎng)。
- 報(bào)文交換網(wǎng)絡(luò):報(bào)文交換又稱為存儲(chǔ)-轉(zhuǎn)發(fā)技術(shù),該方式不需要建立一條專用的物理線路,信息先被分解成報(bào)文,然后一站一站地從源頭送達(dá)目的地,這有點(diǎn)類似通常的郵政寄信方式。
- 分組交換網(wǎng)絡(luò):分組交換網(wǎng)絡(luò)的基本原理與報(bào)文交換相同,它也不需要建立專用的物理線路,但信息傳送的單位不是報(bào)文而是分組,分組的最大長(zhǎng)度比報(bào)文短得多。
1.13 協(xié)議與網(wǎng)絡(luò)的分層體系結(jié)構(gòu)
? 協(xié)議
共享計(jì)算機(jī)網(wǎng)絡(luò)的資源,以及在網(wǎng)中交換信息,就需要實(shí)現(xiàn)不同系統(tǒng)中的實(shí)體的通信。實(shí)體包括用戶應(yīng)用程序、文件傳輸信息包、數(shù)據(jù)庫(kù)管理系統(tǒng)、電子郵件設(shè)備以及終端等。兩個(gè)實(shí)體要想成功地通信,它們必須具有同樣的語(yǔ)言。交流什么,怎樣交流以及何時(shí)交流,都必須遵從有關(guān)實(shí)體間某種相互都能接受的一些規(guī)則,這些規(guī)則的集合稱為協(xié)議。協(xié)議的關(guān)鍵成分如下。
- 語(yǔ)法(Syntax)
語(yǔ)法確定協(xié)議元素的格式,即規(guī)定了數(shù)據(jù)與控制信息的結(jié)構(gòu)和格式。 - 語(yǔ)義(Semantics)
語(yǔ)義確定協(xié)議元素的類型,即規(guī)定了通信雙方要發(fā)出何種控制信息、完成何種動(dòng)作以及做出何種應(yīng)答。 - 定時(shí)(Timing)
定時(shí)可以確定通信速度的匹配和排序,即有關(guān)事件實(shí)現(xiàn)順序的詳細(xì)說(shuō)明。
? 網(wǎng)絡(luò)的分層體系結(jié)構(gòu)
體系結(jié)構(gòu)是研究系統(tǒng)各部分組成及相互關(guān)系的技術(shù)科學(xué)。
計(jì)算機(jī)網(wǎng)絡(luò)體系結(jié)構(gòu)采用分層配對(duì)結(jié)構(gòu),用于定義和描述一組用于計(jì)算機(jī)及其通信設(shè)施之間互聯(lián)的標(biāo)準(zhǔn)和規(guī)范的集合。遵循這組規(guī)范可以很方便地實(shí)現(xiàn)計(jì)算機(jī)設(shè)備之間的通信。也就是說(shuō),為了完成計(jì)算機(jī)之間的通信合作,把每臺(tái)計(jì)算機(jī)互聯(lián)的功能劃分成有明確定義的層次,并規(guī)定了同層次進(jìn)程通信的協(xié)議及相鄰層之間的接口及服務(wù),這些同層進(jìn)程通信的協(xié)議以及相鄰層的接口統(tǒng)稱為網(wǎng)絡(luò)的體系結(jié)構(gòu)。
為了減小計(jì)算機(jī)網(wǎng)絡(luò)的復(fù)雜程度,按照結(jié)構(gòu)化設(shè)計(jì)方法,計(jì)算機(jī)網(wǎng)絡(luò)將其功能劃分成若干個(gè)層次(Lyer),較高層次建立在較低層次的基礎(chǔ)上,并為更高層次提供必要的服務(wù)功能。
(1)基本概念
- 實(shí)體:實(shí)體是通信時(shí)能發(fā)送和接收信息的任何軟硬件設(shè)施。在網(wǎng)絡(luò)分層體系結(jié)構(gòu)中,每一層都由一些實(shí)體組成。
- 接口:分層結(jié)構(gòu)中各相鄰層之間要有一個(gè)接口,它定義了低層向其相鄰的高層提供的原始操作和服務(wù)。相鄰層通過它們之間的接口交換信息,高層并不需要知道低層是如何實(shí)現(xiàn)的,僅需要知道該層通過層間的接口所提供的服務(wù),這樣使得兩層之間保持了功能的獨(dú)立性。
(2)層次結(jié)構(gòu)的特點(diǎn)
- 按照結(jié)構(gòu)化設(shè)計(jì)方法,計(jì)算機(jī)網(wǎng)絡(luò)將其功能劃分為若干個(gè)層次,較高層次建立在較低層次的基礎(chǔ)上,并為其更高層次提供必要的服務(wù)功能。
- 網(wǎng)絡(luò)中的每一層都起到隔離作用,使得低層功能的具體實(shí)現(xiàn)方法的變更不會(huì)影響到高層所執(zhí)行的功能。即低層對(duì)于高層而言是透明的。
(3)層次結(jié)構(gòu)的優(yōu)點(diǎn)
- 層之間相互獨(dú)立。高層并不需要知道低層是如何實(shí)現(xiàn)的,而僅需要知道該層通過層間的接口所提供的服務(wù)。各層都可以采用最合適的技術(shù)來(lái)實(shí)現(xiàn),各層實(shí)現(xiàn)技術(shù)的改變不影響其他層。
- 靈活性好。任何一層發(fā)生變化時(shí),只要接口保持不變,則該層及其以下各層均不受影響。若某層提供的服務(wù)不再需要時(shí),甚至可將這層取消。
- 易于實(shí)現(xiàn)和維護(hù)。整個(gè)系統(tǒng)已被分解為若干個(gè)易于處理的部分,這種結(jié)構(gòu)使得一個(gè)龐大而又復(fù)雜的系統(tǒng)的實(shí)現(xiàn)和維護(hù)變得容易控制。
- 有利于網(wǎng)絡(luò)標(biāo)準(zhǔn)化。因?yàn)槊恳粚拥墓δ芎退峁┑姆?wù)都已有了精確的說(shuō)明,所以標(biāo)準(zhǔn)化變得較為容易。
1.14 OSI 七層模型(重要)
20 世紀(jì) 80 年代末和 90年代初,網(wǎng)絡(luò)的規(guī)模和數(shù)量得到了迅猛的擴(kuò)大和增長(zhǎng)。但是許多網(wǎng)絡(luò)都是基于不同的硬件和軟件而實(shí)現(xiàn)的,這使得它們之間互不兼容。顯然,在使用不同標(biāo)準(zhǔn)的網(wǎng)絡(luò)之間是很難實(shí)現(xiàn)其通信的。為解決這個(gè)問題,國(guó)際標(biāo)準(zhǔn)化組織 ISO 研究了許多網(wǎng)絡(luò)方案,認(rèn)識(shí)到需要建立一種有助于網(wǎng)絡(luò)的建設(shè)者們實(shí)現(xiàn)網(wǎng)絡(luò)、并用于通信和協(xié)同工作的網(wǎng)絡(luò)模型,因此在 1984年公布了開放式系統(tǒng)互連參考模型,稱為 OSI/RM 參考模型(
Open System Interconnect Reference Model/Reference Model
),簡(jiǎn)稱為OSI
參考模型。
? OSI 模型的結(jié)構(gòu)
上圖:
相關(guān)概念:
(1) 層:開放系統(tǒng)的邏輯劃分,代表功能上相對(duì)獨(dú)立的一個(gè)子系統(tǒng)。
(2) 對(duì)等層:指不同開放系統(tǒng)的相同層次。
(3) 層功能:本層具有的通信能力,它由標(biāo)準(zhǔn)來(lái)指定。
(4) 層服務(wù):本層向相鄰高層提供的通信能力。根據(jù) OSI 增值服務(wù)的原則,本層服務(wù)應(yīng)是其所有下層服務(wù)與本層功能之和。
? OSI 模型各層的功能
上圖:
不展開講了,本文是介紹socket的??
1.15 TCP/IP 的體系結(jié)構(gòu)(重要)
OSI 參考模型具有定義過于繁雜、實(shí)現(xiàn)困難等缺陷。與此同時(shí),TCP/IP 協(xié)議的出現(xiàn)和廣泛使用,特別是因特網(wǎng)用戶爆炸式的增長(zhǎng),使TCP/IP 網(wǎng)絡(luò)的體系結(jié)構(gòu)日益顯示出其重要性。
TCP/IP 是指?jìng)鬏斂刂茀f(xié)議/網(wǎng)際協(xié)議,它是由多個(gè)獨(dú)立定義的協(xié)議組合在一起的協(xié)議集合。TCP/IP 協(xié)議是目前最流行的商業(yè)化網(wǎng)絡(luò)協(xié)議,盡管它不是某一標(biāo)準(zhǔn)化組織提出的正式標(biāo)準(zhǔn),但它已經(jīng)被公認(rèn)為目前的工業(yè)標(biāo)準(zhǔn)或“事實(shí)標(biāo)準(zhǔn)”。因特網(wǎng)之所以能迅速發(fā)展,就是因?yàn)門CP/IP 協(xié)議能夠適應(yīng)和滿足世界范圍內(nèi)數(shù)據(jù)通信的需要。
TCP/IP協(xié)議的特點(diǎn)
(1) 開放的協(xié)議標(biāo)準(zhǔn),可以免費(fèi)使用,并且獨(dú)立于特定的計(jì)算機(jī)硬件與操作系統(tǒng)。
(2) 獨(dú)立于特定的網(wǎng)絡(luò)硬件,可以運(yùn)行在局域網(wǎng)、廣域網(wǎng)以及因特網(wǎng)中。
(3) 統(tǒng)一的網(wǎng)絡(luò)地址分配方案,使得整個(gè) TCP/IP 設(shè)備在網(wǎng)絡(luò)中都具有唯一的地址。
(4) 標(biāo)準(zhǔn)化的高層協(xié)議,可以提供多種可靠的用戶服務(wù)。
TCP/IP 體系結(jié)構(gòu)將網(wǎng)絡(luò)劃分為 4 層,它們分別是應(yīng)用層(Application layer)、傳輸層(Transport layer)、網(wǎng)際層(Internet layer)和網(wǎng)絡(luò)接口層(主機(jī)-網(wǎng)絡(luò)層)(Network interface layer),與OSI模型的對(duì)應(yīng)關(guān)系如下:
? TCP/IP 體系結(jié)構(gòu)各層的功能
-
網(wǎng)絡(luò)接口層
在 TCP/IP 分層體系結(jié)構(gòu)中,網(wǎng)絡(luò)接口層又稱主機(jī)-網(wǎng)絡(luò)層,它是最低層,負(fù)責(zé)接收網(wǎng)際層的 IP 數(shù)據(jù)報(bào)以形成幀發(fā)送到傳輸介質(zhì)上;或者從網(wǎng)絡(luò)上接收幀,抽取數(shù)據(jù)報(bào)交給互連層。它包括了能使用 TCP/IP 與物理網(wǎng)絡(luò)進(jìn)行通信的所有協(xié)議。TCP/IP 體系結(jié)構(gòu)并未定義具體的網(wǎng)絡(luò)接口層協(xié)議,旨在提高靈活性,以適應(yīng)各種網(wǎng)絡(luò)類型,如 LAN、WAN。它允許主機(jī)連入網(wǎng)絡(luò)時(shí)使用多種現(xiàn)成的和流行的協(xié)議,例如局域網(wǎng)協(xié)
議或其他一些協(xié)議。 -
網(wǎng)際層
網(wǎng)際層又稱互連層,是 TCP/IP 體系結(jié)構(gòu)的第二層,它實(shí)現(xiàn)的功能相當(dāng)于 OSI 參考模型中網(wǎng)絡(luò)層的功能。網(wǎng)際層的主要功能如下。- 處理來(lái)自傳輸層的分組發(fā)送請(qǐng)求。在收到分組發(fā)送請(qǐng)求之后,將分組裝入 IP 數(shù)據(jù)報(bào),填充報(bào)頭,選擇發(fā)送路徑,然后將數(shù)據(jù)報(bào)發(fā)送到相應(yīng)的網(wǎng)絡(luò)接口。
- 處理接收的數(shù)據(jù)報(bào)。檢查收到的數(shù)據(jù)報(bào)的合法性,進(jìn)行路由。在接收到其他主機(jī)發(fā)送的數(shù)據(jù)報(bào)之后,檢查目的地址,如需要轉(zhuǎn)發(fā),則選擇發(fā)送路徑,轉(zhuǎn)發(fā)出去;如目的地址為本節(jié)點(diǎn) IP 地址,則除去報(bào)頭,將分組送交傳輸層處理。
- 處理 ICMP 報(bào)文、路由、流控與擁塞問題。
-
傳輸層
傳輸層位于網(wǎng)際層之上,它的主要功能是負(fù)責(zé)應(yīng)用進(jìn)程之間的端到端通信。在 TCP/IP體系結(jié)構(gòu)中,設(shè)計(jì)傳輸層的主要目的是在互連層中的源主機(jī)與目的主機(jī)的對(duì)等實(shí)體之間建立用于會(huì)話的端到端連接。因此,它與 OSI 參考模型的傳輸層相似。 -
應(yīng)用層
應(yīng)用層是最高層。它與 OSI 模型中的高 3 層的任務(wù)相同,都是用于提供網(wǎng)絡(luò)服務(wù),比如文件傳輸、遠(yuǎn)程登錄、域名服務(wù)和簡(jiǎn)單網(wǎng)絡(luò)管理等。
? TCP/IP協(xié)議簇
TCP/IP中各層的協(xié)議如圖 :
我們每天都在使用近乎所有的協(xié)議。
這些協(xié)議都很有用,不展開了,下面介紹一下TCP和UDP協(xié)議。
1.2 本文使用的主要協(xié)議 (必備)
前面的知識(shí)點(diǎn)或許你瀏覽一下就可以了,在下面這些知識(shí)點(diǎn)是socket編程中必備知識(shí)點(diǎn)。
1.21 Mac地址、IP地址與端口號(hào)
網(wǎng)絡(luò)地址就是網(wǎng)絡(luò)中唯一標(biāo)識(shí)網(wǎng)絡(luò)中每臺(tái)網(wǎng)絡(luò)設(shè)備的一個(gè)數(shù)字,若沒有這種唯一的地址,網(wǎng)絡(luò)中的計(jì)算機(jī)之間就不可能進(jìn)行可靠的通信。實(shí)際上網(wǎng)絡(luò)中每個(gè)節(jié)點(diǎn)都有兩類地址標(biāo)識(shí):數(shù)據(jù)鏈路層地址和網(wǎng)絡(luò)層地址。
? Mac地址
網(wǎng)絡(luò)上的每一個(gè)設(shè)備有一個(gè)唯一的物理地址(Physical Address),有時(shí)被稱為硬件地址或數(shù)據(jù)鏈路地址。數(shù)據(jù)鏈路層地址是與網(wǎng)絡(luò)硬件相關(guān)聯(lián)的固定序列號(hào),通常在出廠前即被確定(也可以通過一些方式手動(dòng)修改,某些情況下還會(huì)出現(xiàn)mac地址沖突的情況,不過很少)。
這些地址通過位于數(shù)據(jù)鏈路層中的介質(zhì)訪問控制(MAC,Media Access Control
)子層后被稱為 MAC 地址。它是在媒體接入層上使用的地址,由網(wǎng)絡(luò)設(shè)備制造商生產(chǎn)時(shí)寫在硬件內(nèi)部。MAC 地址與網(wǎng)絡(luò)無(wú)關(guān),無(wú)論將帶有這個(gè)地址的硬件(如網(wǎng)卡、路由器等)接入到網(wǎng)絡(luò)的何處,該硬件都有相同的 MAC 地址。(可以把他比喻為你的身份證號(hào),一出生就確定,無(wú)論你走到哪里,它都不變)
對(duì)于網(wǎng)絡(luò)硬件而言,地址通常被編碼到網(wǎng)絡(luò)的接口卡中。常見的情況是,用戶根本不能改變這些地址,因?yàn)檫@個(gè)唯一的編號(hào)已經(jīng)編到可編程只讀存儲(chǔ)器(PROM)中。
例如以太網(wǎng)卡的 MAC 地址由廠商寫在網(wǎng)卡的 BIOS 里,為 6 字節(jié) 48 比特的 MAC 地址。這個(gè) 48比特都有其規(guī)定的意義,前 24 位是由 IEEE(電氣與電子工程師協(xié)會(huì))分配,稱為機(jī)構(gòu)唯一標(biāo)識(shí)符(OUI,OrganizationllyUnity Idientifier);后 24 位由廠商自行分配,這樣的分配使得世界上任意一個(gè)擁有 48 位 MAC地址的網(wǎng)卡都有唯一的標(biāo)識(shí)。
以太網(wǎng)卡的MAC地址通常表示為12個(gè)十六進(jìn)制數(shù),每?jī)蓚€(gè)十六進(jìn)制數(shù)之間用冒號(hào)隔開,如 08:00:20:0A:8C:6D
就是一個(gè) MAC 地址,其中前 6 位十六進(jìn)制數(shù) 08:00:20 代表網(wǎng)絡(luò)硬件制造商的編號(hào),它由 IEEE 分配,而后 6 位十六進(jìn)制數(shù) 0A:8C:6D 代表該制造商所制造的某個(gè)網(wǎng)絡(luò)產(chǎn)品(如網(wǎng)卡)的系列號(hào)。每個(gè)網(wǎng)絡(luò)制造商必須確保它所制造的每個(gè)網(wǎng)絡(luò)設(shè)備都具有相同的前 3 字節(jié)以及不同的后 3 個(gè)字節(jié)。這樣就可保證世界上每個(gè)設(shè)備都具有唯一的 MAC 地址。
通信過程中需要有兩個(gè)地址:一個(gè)地址標(biāo)識(shí)發(fā)送設(shè)備(源);一個(gè)用于接收設(shè)備(目的)。數(shù)據(jù)鏈路層的 PDU 包含了目的 MAC 地址和源 MAC 地址,它是確認(rèn)通信雙方身份的唯一標(biāo)識(shí)。通過 MAC 地址的識(shí)別,才能準(zhǔn)確、可靠地找到對(duì)方,也才能夠?qū)崿F(xiàn)通信。MAC 地址用于標(biāo)識(shí)本地網(wǎng)絡(luò)上的系統(tǒng)。大多數(shù)數(shù)據(jù)鏈路層協(xié)議,包括以太網(wǎng)和令牌環(huán)網(wǎng)協(xié)議,都使用制造商通過硬編碼寫入網(wǎng)卡的地址。
我們可以使用查看電腦的網(wǎng)卡的mac地址,比如Linux使用:ifconfig -a
ether 52:57:00:4b:ac:85 txqueuelen 1000 (Ethernet)
? IP地址
網(wǎng)絡(luò)地址是邏輯地址,該地址可以通過操作系統(tǒng)進(jìn)行定義和更改。網(wǎng)絡(luò)地址采用一種分層編址方案,如同個(gè)人通信地址包括國(guó)家、省、市、街道、住宅號(hào)及個(gè)人姓名一樣,網(wǎng)絡(luò)分類邏輯化,越容易管理和使用,因而更加有用。(你可能在一個(gè)地方住一段時(shí)間,這段時(shí)間內(nèi),你的通信地址不變;當(dāng)你換個(gè)地方住,出差、旅游的時(shí)候,你的地址又會(huì)發(fā)生改變)
在 TCP/IP 環(huán)境中,每個(gè)節(jié)點(diǎn)都具有唯一的 IP 地址。每個(gè)網(wǎng)絡(luò)被看作一個(gè)單獨(dú)的、唯一的地址。在訪問到這個(gè)網(wǎng)絡(luò)內(nèi)的主機(jī)之前,必須首先訪問到這個(gè)網(wǎng)絡(luò)。
? IP地址的表示方法
TCP/IP 協(xié)議棧中的 IP(IPv4)地址是網(wǎng)絡(luò)地址,為標(biāo)識(shí)主機(jī)而采用 32 位無(wú)符號(hào)二進(jìn)制數(shù)表示。
為了方便用戶的理解和記憶,它采用了點(diǎn)分十進(jìn)制標(biāo)記法,即將 4 字節(jié)的二進(jìn)制數(shù)值轉(zhuǎn)換成 4 個(gè)十進(jìn)制數(shù)值,每個(gè)數(shù)值小于等于 255,數(shù)值中間用“.”隔開,表示成為 w.x.y.z 的形式,因此,最小的 IPv4 地址值為 0.0.0.0,最大的地址值為 255.255.255.255,
舉例:
同樣你可以使用ipconfig
或ifconfig
查看電腦ip地址。
? IP地址的分類
IP地址的編址方式經(jīng)歷了3個(gè)階段:
- 分類的IP地址
- 劃分子網(wǎng)
- CIDR
(1)2級(jí) IP地址
過去,將一個(gè)ip地址可以分為兩部分,網(wǎng)絡(luò)號(hào)和主機(jī)號(hào),即IP 地址由網(wǎng)絡(luò)號(hào)(Net id)和主機(jī)號(hào)(Host id)兩個(gè)層次組成。
網(wǎng)絡(luò)號(hào)用來(lái)標(biāo)識(shí)互聯(lián)網(wǎng)中的一個(gè)特定網(wǎng)絡(luò),而主機(jī)號(hào)則用來(lái)表示該網(wǎng)絡(luò)中主機(jī)的一個(gè)特定連接。因此,IP 地址的編址方式明顯攜帶了位置信息。這給 IP 互聯(lián)網(wǎng)的路由選擇帶來(lái)了很大好處。
TCP/IP 規(guī)定,只有同一網(wǎng)絡(luò)(網(wǎng)絡(luò)號(hào)相同)內(nèi)的主機(jī)才能夠直接通信,不同網(wǎng)絡(luò)內(nèi)的主機(jī),只有通過其他網(wǎng)絡(luò)設(shè)備(如路由器),才能夠進(jìn)行通信。
在長(zhǎng)度為 32 位的 IP 地址中,哪些位代表網(wǎng)絡(luò)號(hào),哪些代表主機(jī)號(hào)呢?
這個(gè)問題看似簡(jiǎn)單,意義卻非常重大,只有明確其網(wǎng)絡(luò)號(hào)和主機(jī)號(hào),才能確定其通信地址;同時(shí)當(dāng)?shù)刂烽L(zhǎng)度確定后,網(wǎng)絡(luò)號(hào)長(zhǎng)度又將決定整個(gè)互聯(lián)網(wǎng)中可以包含多少個(gè)網(wǎng)絡(luò),主機(jī)號(hào)長(zhǎng)度則決定每個(gè)網(wǎng)絡(luò)能容納多少臺(tái)主機(jī)。
為了適應(yīng)各種網(wǎng)絡(luò)規(guī)模的不同,IP 協(xié)議將 IP 地址劃分為 5 類網(wǎng)絡(luò)(A、B、C、D 和 E),它們分別使用 IP 地址的前幾位(地址類別)加以區(qū)分,常用的為 A、B 和 C 三類。
A、B、C類IP地址可以容納的網(wǎng)絡(luò)數(shù)和主機(jī)數(shù):
(2)劃分子網(wǎng)和子網(wǎng)掩碼
隨著網(wǎng)絡(luò)設(shè)備的爆發(fā)增長(zhǎng),前面的ip地址劃分方式表現(xiàn)出很大的缺陷:
- 地址空間利用率低;
- 不靈活;
- 路由表會(huì)很大
從 1985 年起,IP 地址中增加了一個(gè)“子網(wǎng)號(hào)字段”,使兩級(jí)的 IP 地址變成三級(jí)的 IP 地址,也就是 IP 地址由網(wǎng)絡(luò)號(hào)、子網(wǎng)號(hào)和主機(jī)號(hào)3 部分組成。
劃分子網(wǎng)只是從 IP 地址的主機(jī)號(hào)中拿出幾位來(lái)作為子網(wǎng)號(hào),而不改變 IP 地址的網(wǎng)絡(luò)號(hào)字段,即:
子網(wǎng)劃分規(guī)則:
(1) 子網(wǎng)化的規(guī)則不允許使用全 0 或者全 1 的子網(wǎng)地址,這些地址是保留的。因此只有 1 位數(shù)時(shí),不能得到可用的子網(wǎng)地址。
(2)在利用主機(jī)號(hào)劃分子網(wǎng)后,剩余的主機(jī)號(hào)部分,全部為“0”的表示該子網(wǎng)的網(wǎng)絡(luò)號(hào),全部為“1”的則表示該子網(wǎng)的廣播地址,剩余的就可以作為主機(jī)號(hào)分配給子網(wǎng)中的主機(jī)。也就是說(shuō),剩余的主機(jī)號(hào)部分的二進(jìn)制全“0”或全“1”的子網(wǎng)號(hào)不能分配給實(shí)際的子網(wǎng)。
機(jī)器是如何知道 IP 地址中哪些位數(shù)用來(lái)表示網(wǎng)絡(luò)、子網(wǎng)和主機(jī)部分呢?
為了解決這個(gè)問題,子網(wǎng)編址使用了子網(wǎng)掩碼(或稱為子網(wǎng)屏蔽碼)。子網(wǎng)掩碼也采用了 32 位二進(jìn)制數(shù)值,分別與 IP 地址的 32 位二進(jìn)制數(shù)相對(duì)應(yīng)。
IP 協(xié)議規(guī)定,在子網(wǎng)掩碼中,與 IP 地址的網(wǎng)絡(luò)號(hào)和子網(wǎng)號(hào)部分相對(duì)應(yīng)的位使用“1”來(lái)表示,而與 IP 地址的主機(jī)號(hào)部分相對(duì)應(yīng)的位則用“0”表示。將一臺(tái)主機(jī)的 IP 地址和它的子網(wǎng)掩碼按位進(jìn)行“與”運(yùn)算,就可以判斷出 IP 地址中哪些位用來(lái)表示網(wǎng)絡(luò)和子網(wǎng),哪些位用來(lái)表示主機(jī)號(hào)。
圖示:
(3)無(wú)分類編址(CIDR)
劃分子網(wǎng)在一定程度上緩解了互聯(lián)網(wǎng)在發(fā)展中遇到的困難。然而在1992年互聯(lián)網(wǎng)仍然面臨三個(gè)必須盡早解決的問題,這就是:
- B類地址在1992年已分配了近一半,眼看很快就將全部分配完畢!
- 互聯(lián)網(wǎng)主干網(wǎng)上的路由表中的項(xiàng)目數(shù)急劇增長(zhǎng)(從幾千個(gè)增長(zhǎng)到幾萬(wàn)個(gè))。
- 整個(gè)Pv4的地址空間最終將全部耗盡。在2011年2月3日,IANA宣布Pv4地址已經(jīng)耗盡了。
早在1987年,RFC1009就指明了在一個(gè)劃分子網(wǎng)的網(wǎng)絡(luò)中可同時(shí)使用幾個(gè)不同的子網(wǎng)掩碼。使用變長(zhǎng)子網(wǎng)掩碼VLSM(Variable Length Subnet Mask)可進(jìn)一步提高IP地址資源的利用率。在VLSM的基礎(chǔ)上又進(jìn)一步研究出無(wú)分類編址方法,它的正式名字是無(wú)分類域間路由選擇CIDR
(Classless Inter–Domain Routing)。
CIDR 是傳統(tǒng)地址分配策略的重大突破,它完全拋棄了有分類地址,前面介紹的有類地址用 8 位表示一個(gè) A 類網(wǎng)絡(luò)號(hào),16 位表示一個(gè) B 類網(wǎng)絡(luò)號(hào),24 位表示一個(gè) C 類網(wǎng)絡(luò)號(hào)。CIDR用網(wǎng)絡(luò)前綴代替了這些類,前綴可以任意長(zhǎng)度,而不僅僅是 8 位,16 位或 24 位。允許 CIDR可以根據(jù)網(wǎng)絡(luò)大小來(lái)分配網(wǎng)絡(luò)地址空間,而不是在預(yù)定義的網(wǎng)絡(luò)地址空間中作裁剪。每一個(gè)CIDR 網(wǎng)絡(luò)地址和一個(gè)相關(guān)位的掩碼一起廣播,這個(gè)掩碼識(shí)別了網(wǎng)絡(luò)前綴的長(zhǎng)度。也就是說(shuō),一個(gè)網(wǎng)絡(luò)地址中主機(jī)部分與網(wǎng)絡(luò)部分的劃分完全是由子網(wǎng)掩碼確定的。
例如,使用192.125.61.8/20
標(biāo)識(shí)一個(gè) CIDR 地址,此地址有 20 位網(wǎng)絡(luò)地址
? 特殊IP地址
(1) 網(wǎng)絡(luò)地址
在互聯(lián)網(wǎng)中,經(jīng)常需要使用網(wǎng)絡(luò)地址,那么,怎么來(lái)表示一個(gè)網(wǎng)絡(luò)地址呢?IP 地址方案規(guī)定,一個(gè)網(wǎng)絡(luò)地址包含了一個(gè)有效的網(wǎng)絡(luò)號(hào)和一個(gè)全“0”的主機(jī)號(hào)。
例如,地址 113.0.0.0 就表示該網(wǎng)絡(luò)是一個(gè) A 類網(wǎng)絡(luò)的網(wǎng)絡(luò)地址。而一個(gè) IP 地址為202.100.100.2的主機(jī)所處的網(wǎng)絡(luò)地址為 202.100.100.0,它是一個(gè) C 類網(wǎng)絡(luò),其主機(jī)號(hào)為 2。
(2) 廣播地址
當(dāng)一個(gè)設(shè)備向網(wǎng)絡(luò)上所有的設(shè)備發(fā)送數(shù)據(jù)時(shí),就產(chǎn)生了廣播。為了使網(wǎng)絡(luò)上所有設(shè)備能夠注意到這樣一個(gè)廣播,必須使用一個(gè)可識(shí)別和偵聽的 IP 地址。通常,一個(gè)廣播的標(biāo)志是,其目的 IP 地址的主機(jī)號(hào)是全“1”。IP 廣播有兩種形式,一種叫直接廣播,另一種叫有限廣播。
- ① 直接廣播
如果廣播地址包含一個(gè)有效的網(wǎng)絡(luò)號(hào)和一個(gè)全“1”的主機(jī)號(hào),則稱之為直接廣播(Directed Broadcasting)地址。在 IP 互聯(lián)網(wǎng)中,任意一臺(tái)主機(jī)均可向其他網(wǎng)絡(luò)進(jìn)行直接廣播。
例如 C 類地址 202.100.100.255 就是一個(gè)直接廣播地址?;ヂ?lián)網(wǎng)上的一臺(tái)主機(jī)如果使用該 IP 地址作為數(shù)據(jù)報(bào)的目的 IP地址,那么這個(gè)數(shù)據(jù)報(bào)將同時(shí)發(fā)送到 202.100.100.0 網(wǎng)絡(luò)上的所有主機(jī)。
顯然,直接廣播的一個(gè)主要問題是在發(fā)送前必須知道目的網(wǎng)絡(luò)的網(wǎng)絡(luò)號(hào)。
- ② 有限廣播
32 位數(shù)全為“1”的 IP 地址(255.255.255.255)用于本網(wǎng)廣播,該地址稱為有限廣播(Limited Broadcasting)地址。實(shí)際上,有限廣播將廣播限制在最小的范圍內(nèi)。如果采用標(biāo)準(zhǔn)的 IP 編址,那么有限廣播將被限制在本網(wǎng)絡(luò)之中;如果采用子網(wǎng)編址,那么有限廣播將被限
制在本子網(wǎng)之中。有限廣播不需要知道網(wǎng)絡(luò)號(hào)。因此,在主機(jī)不知道本機(jī)所處的網(wǎng)絡(luò)時(shí)(如主機(jī)的啟動(dòng)過程中),只能采用有限廣播方式。
(3) 回送地址(環(huán)回地址)
A 類網(wǎng)絡(luò)地址 127.0.0.0 是一個(gè)保留地址,用于網(wǎng)絡(luò)軟件測(cè)試以及本地機(jī)器進(jìn)程間通信,這個(gè) IP 地址叫做回送地址(Loop back address)。無(wú)論什么程序,一旦使用回送地址發(fā)送數(shù)據(jù),協(xié)議軟件不進(jìn)行任何網(wǎng)絡(luò)傳輸,立即將之返回。因此,含有目的網(wǎng)絡(luò)號(hào) 127 的數(shù)據(jù)報(bào)不可能出現(xiàn)在任何網(wǎng)絡(luò)上。(我們常用的是127.0.0.1,稱之為localhost,當(dāng)然,你使用127.0.0.2等等也是可以的)
? 端口號(hào)
我們的電腦、移動(dòng)設(shè)備一般只有1個(gè)ipv4地址,但通常由許多軟件,只通過一個(gè)IP地址,怎么區(qū)分接收到的數(shù)據(jù)是誰(shuí)的,發(fā)送的數(shù)據(jù)是誰(shuí)發(fā)的呢?
——使用端口號(hào)。
端口號(hào)是用于在計(jì)算機(jī)網(wǎng)絡(luò)中標(biāo)識(shí)特定應(yīng)用程序或服務(wù)的數(shù)字。它可以看作是一種與IP地址相結(jié)合的地址擴(kuò)展,用于將網(wǎng)絡(luò)流量正確地發(fā)送到目標(biāo)應(yīng)用程序。
在計(jì)算機(jī)網(wǎng)絡(luò)通信中,每個(gè)網(wǎng)絡(luò)連接都使用一個(gè)唯一的端口號(hào)來(lái)區(qū)分不同的應(yīng)用程序或服務(wù)。端口號(hào)范圍從0到65535
(為什么呢?因?yàn)镮P、TCP等協(xié)議使用16位表示端口號(hào)),其中0到1023是稱為"知名端口"的預(yù)留端口,用于一些常見的服務(wù)如HTTP(端口80)、FTP(端口21)、SSH(端口22)等。
通過將數(shù)據(jù)包的目的端口號(hào)和源端口號(hào)與IP地址結(jié)合使用,網(wǎng)絡(luò)中的設(shè)備可以將數(shù)據(jù)正確地路由到目標(biāo)應(yīng)用程序或服務(wù)。端口號(hào)是網(wǎng)絡(luò)通信中重要的組成部分,允許多個(gè)應(yīng)用程序同時(shí)在同一設(shè)備上進(jìn)行通信,每個(gè)應(yīng)用程序都有唯一的標(biāo)識(shí)符。
端口號(hào)主要用于標(biāo)識(shí)網(wǎng)絡(luò)通信中的應(yīng)用程序或服務(wù),而不是直接用于區(qū)分協(xié)議。然而,某些端口號(hào)通常與特定的協(xié)議相關(guān)聯(lián),因?yàn)樘囟ǖ膮f(xié)議通常在預(yù)定的端口上進(jìn)行通信。
例如,HTTP(超文本傳輸協(xié)議)通常使用端口號(hào)80,HTTPS(安全的超文本傳輸協(xié)議)通常使用端口號(hào)443,F(xiàn)TP(文件傳輸協(xié)議)通常使用端口號(hào)21,SSH(安全外殼協(xié)議)通常使用端口號(hào)22等。
因此,端口號(hào)經(jīng)常與特定的協(xié)議相關(guān)聯(lián),以便網(wǎng)絡(luò)設(shè)備和應(yīng)用程序可以識(shí)別并將數(shù)據(jù)正確地傳送到相應(yīng)的服務(wù)或應(yīng)用程序。然而,并非所有的端口號(hào)都與特定協(xié)議相關(guān),因?yàn)樵谝恍┣闆r下,用戶可以自定義端口號(hào)來(lái)與其特定應(yīng)用程序關(guān)聯(lián)。
? ipv6
ipv4地址早就分配完了。作為新一代的 Internet 的地址協(xié)議標(biāo)準(zhǔn),它克服了 IPv4 的一些問題,但由于 IPv6 和 IPv4 協(xié)議不兼容,而現(xiàn)在 Internet 上的設(shè)備大多只支持 IPv4 協(xié)議,考慮到代價(jià),不可能立即用 IPv6 代替 IPv4,所以目前一些網(wǎng)絡(luò)設(shè)備都支持這 2 種協(xié)議,由用戶來(lái)決定用什么協(xié)議。長(zhǎng)遠(yuǎn)規(guī)劃,IPv6 會(huì)代替 IPv4,因?yàn)?IPv6 有下列 IPv4 不具有的優(yōu)勢(shì):龐大的地址空間、簡(jiǎn)化的報(bào)頭定長(zhǎng)結(jié)構(gòu)、更合理的分段方法、完善的服務(wù)種類。
ipv6有很多特性,不講了,比如不用DHCP分配,我的服務(wù)器就有3個(gè)ipv6地址。
現(xiàn)在很多網(wǎng)站、設(shè)別、軟件、協(xié)議都開始很好地支持ipv6協(xié)議了。
? 域名
ip地址很有用,但我記不住啊。
雖然 IP 地址是 TCP/IP 的基礎(chǔ),但每個(gè)用過互聯(lián)網(wǎng)的人都知道用戶并不必記住或輸入IP 地址。類似地,計(jì)算機(jī)也被賦予符號(hào)名字,當(dāng)需要指定一臺(tái)計(jì)算機(jī)時(shí),應(yīng)用軟件允許用戶輸入這個(gè)符號(hào)名字。例如,在說(shuō)明一個(gè)電子郵件的目的地時(shí),用戶輸入一個(gè)字符串來(lái)標(biāo)識(shí)接收者以及接收者的計(jì)算機(jī)。類似地,用戶在輸入字符串指定 WWW 上的站點(diǎn)時(shí),計(jì)算機(jī)名字是嵌入在該字符串中的。
由于二進(jìn)制形式的 IP地址比符號(hào)名字更為緊湊,在操作時(shí)需要的計(jì)算量更少。而且地址比名字占用更少的內(nèi)存,在網(wǎng)絡(luò)上傳輸需要的時(shí)間也更少。于是,盡管應(yīng)用軟件允許用戶輸入符號(hào)名字,基本網(wǎng)絡(luò)協(xié)議仍要求使用地址——應(yīng)用在使用每個(gè)名字進(jìn)行通信前必須將它翻譯成對(duì)等的 IP 地址。在大多數(shù)情況下,翻譯是自動(dòng)進(jìn)行的,翻譯結(jié)果對(duì)用戶隱蔽——IP 地址保存在內(nèi)存中,僅在收發(fā)數(shù)據(jù)報(bào)的時(shí)候使用。
把域名翻譯成 IP 地址的軟件稱為域名系統(tǒng)(Domain Name System,DNS
)。例如,電子工業(yè)出版社的域名是:www.phei.com.cn??梢钥闯鲇蛎怯袑哟蔚模蛎凶钪匾牟糠治挥谟疫?。域可以繼續(xù)劃分為子域,如二級(jí)域、三級(jí)域等。域名的結(jié)構(gòu)是由若干分量組成的,各分量之間用點(diǎn)隔開:….三級(jí)域名.二級(jí)域名.頂級(jí)域名。
每一個(gè)域名服務(wù)器(name server
)不但能夠進(jìn)行一些域名到 IP 地址的轉(zhuǎn)換(這種轉(zhuǎn)換常被稱為地址解析),而且還必須具有連向其他域名服務(wù)器的信息,當(dāng)自己不能進(jìn)行域名到 IP 地址的轉(zhuǎn)換時(shí),就應(yīng)該知道到什么地方去找別的域名服務(wù)器?;ヂ?lián)網(wǎng)上的域名服務(wù)器系統(tǒng)也是按照域名的層次來(lái)安排的。每一個(gè)域名服務(wù)器都只對(duì)域名體系中的一部分進(jìn)行管轄。
點(diǎn)擊一個(gè) URL 后,涉及的全過程可以大致描述如下:
解析 URL:瀏覽器首先會(huì)解析 URL(統(tǒng)一資源定位符),包括協(xié)議類型(如HTTP、HTTPS)、主機(jī)名(域名或IP地址)、端口號(hào)(可選)、路徑等。
DNS 解析:如果主機(jī)名在本地 DNS 緩存中找不到,瀏覽器會(huì)向 DNS(域名系統(tǒng))服務(wù)器發(fā)送請(qǐng)求,以獲取與主機(jī)名對(duì)應(yīng)的 IP 地址。
建立 TCP 連接:使用解析得到的 IP 地址和端口號(hào),瀏覽器會(huì)嘗試與目標(biāo)服務(wù)器建立 TCP(傳輸控制協(xié)議)連接。這是一個(gè)三次握手的過程,用于建立可靠的數(shù)據(jù)傳輸通道。
發(fā)起 HTTP 請(qǐng)求:一旦建立了 TCP 連接,瀏覽器會(huì)向服務(wù)器發(fā)送 HTTP(超文本傳輸協(xié)議)請(qǐng)求,包括請(qǐng)求方法(如GET、POST)、請(qǐng)求頭、請(qǐng)求體等。請(qǐng)求的目標(biāo)是服務(wù)器上的特定資源(如網(wǎng)頁(yè)、圖像、API
等)。服務(wù)器處理請(qǐng)求:服務(wù)器接收到請(qǐng)求后,會(huì)根據(jù)請(qǐng)求的內(nèi)容和服務(wù)器端的配置來(lái)處理請(qǐng)求。這可能涉及動(dòng)態(tài)生成內(nèi)容、從數(shù)據(jù)庫(kù)檢索數(shù)據(jù)、執(zhí)行業(yè)務(wù)邏輯等操作。
返回 HTTP 響應(yīng):服務(wù)器處理完請(qǐng)求后,會(huì)生成一個(gè) HTTP 響應(yīng),包括狀態(tài)碼、響應(yīng)頭、響應(yīng)體等。狀態(tài)碼表示請(qǐng)求的結(jié)果,如200表示成功、404表示資源未找到等。
接收響應(yīng):瀏覽器接收到服務(wù)器返回的響應(yīng)后,會(huì)開始解析響應(yīng)內(nèi)容。
渲染頁(yè)面:如果響應(yīng)的內(nèi)容是一個(gè) HTML 頁(yè)面,瀏覽器會(huì)解析 HTML、加載和解析 CSS 和 JavaScript 文件,并根據(jù)標(biāo)記、樣式和腳本來(lái)構(gòu)建頁(yè)面的渲染樹。最終,將渲染樹轉(zhuǎn)換為屏幕上的可視化布局和呈現(xiàn)。
完成請(qǐng)求:瀏覽器執(zhí)行完頁(yè)面的渲染后,觸發(fā)相應(yīng)的事件,可能會(huì)執(zhí)行后續(xù)的 JavaScript 代碼或處理其他交互。
又說(shuō)多了,這篇文章寫不完了。??
1.22 TCP/UDP 協(xié)議
? Intro
TCP/IP 體系結(jié)構(gòu)的傳輸層定義了傳輸控制協(xié)議(TCP
,Transport Control Protocol)和用戶數(shù)據(jù)報(bào)協(xié)議(UDP
,User Datagram Protocol)兩種協(xié)議。它們利用 IP 層提供的服務(wù),分別提供端到端可靠的和不可靠的服務(wù)。應(yīng)用層協(xié)議通常都是基于他們的。
TCP
:
- TCP 是一種面向連接的協(xié)議,提供可靠的、有序的、基于字節(jié)流的數(shù)據(jù)傳輸。
- 在使用 TCP 時(shí),通信雙方必須先建立連接,然后才能進(jìn)行數(shù)據(jù)的傳輸。
- TCP 使用三次握手來(lái)建立連接,并使用四次揮手來(lái)關(guān)閉連接。
- TCP 提供可靠性,通過使用序列號(hào)、確認(rèn)應(yīng)答、超時(shí)重傳、擁塞控制等機(jī)制來(lái)確保數(shù)據(jù)的可靠傳輸。
- TCP 支持點(diǎn)對(duì)點(diǎn)的通信方式,適用于需要可靠傳輸、有序交付和流控制的應(yīng)用,如網(wǎng)頁(yè)瀏覽、文件傳輸、電子郵件等。
UDP
:
- UDP 是一種無(wú)連接的協(xié)議,提供不可靠的、無(wú)序的、盡力而為的數(shù)據(jù)傳輸。
- 在使用 UDP 時(shí),通信雙方之間沒有建立連接的過程,可以直接發(fā)送數(shù)據(jù)包。
- UDP 不提供可靠性保證,數(shù)據(jù)包可能會(huì)丟失、重復(fù)或者無(wú)序到達(dá)。
- UDP 以數(shù)據(jù)報(bào)(Datagram)的形式發(fā)送和接收數(shù)據(jù),每個(gè)數(shù)據(jù)報(bào)都是獨(dú)立的、完整的數(shù)據(jù)單元。
- UDP 沒有擁塞控制和流量控制機(jī)制,可以實(shí)現(xiàn)更低的延遲和更高的傳輸速度。
- UDP 適用于對(duì)實(shí)時(shí)性要求較高的應(yīng)用,如實(shí)時(shí)音視頻傳輸、在線游戲等,也常用于 DNS 解析和簡(jiǎn)單的請(qǐng)求-響應(yīng)通信。
關(guān)于他們的報(bào)文幀格式、連接建立過程、流量控制等,此處先不介紹。
哎,不行,報(bào)文格式還是必須說(shuō)一下的。
? 報(bào)文首部格式、長(zhǎng)度
應(yīng)用層的進(jìn)程發(fā)送數(shù)據(jù),是將數(shù)據(jù)依次向下傳遞給運(yùn)輸層、網(wǎng)絡(luò)層等等,最后通過物理傳輸?shù)竭_(dá)目的地,沒向下傳輸一層,都要在數(shù)據(jù)前面添加相應(yīng)層的首部,首都通常用來(lái)指明數(shù)據(jù)部分的長(zhǎng)度、使用的協(xié)議版本、校驗(yàn)和等等信息,使得數(shù)據(jù)可以在各層進(jìn)行正確的交付。
此外,每一層的報(bào)文長(zhǎng)度都會(huì)受到一些限制,有協(xié)議本身的、也有設(shè)備限制,因此,是將數(shù)據(jù)全部塞進(jìn)一個(gè)報(bào)文傳輸,還是每次只傳輸特定長(zhǎng)度的數(shù)據(jù)呢?每次傳多長(zhǎng)好呢?
? UDP報(bào)文首部格式、長(zhǎng)度
報(bào)文=首部+數(shù)據(jù)
數(shù)據(jù)就不用說(shuō)了奧,來(lái)看看首部。
這是UDP的首部格式(不看位首部,那個(gè)是計(jì)算校驗(yàn)和用的):
UDP報(bào)文的首部很短,只有8字節(jié)。注意長(zhǎng)度這個(gè)字段,表示整個(gè)UDP數(shù)據(jù)報(bào)的長(zhǎng)度(單位:字節(jié)),占2個(gè)字節(jié)理論來(lái)說(shuō)最大長(zhǎng)度可以是: 2 16 2^{16} 216 字節(jié),但數(shù)據(jù)部分往往遠(yuǎn)遠(yuǎn)達(dá)不到 2 16 ? 8 2^{16}-8 216?8個(gè)字節(jié)。
它還受:
- 以太網(wǎng)(Ethernet)數(shù)據(jù)幀的長(zhǎng)度,數(shù)據(jù)鏈路層的MTU(最大傳輸單元)。
- socket的UDP發(fā)送緩存區(qū)大小。
- 設(shè)備限制。
- 等等。
的限制,通常,數(shù)據(jù)部分應(yīng)該小于:548字節(jié)。
具體的計(jì)算可以參考這篇文章:UDP傳輸報(bào)文大小詳解
? TCP報(bào)文首部格式、長(zhǎng)度
、
TCP傳輸不像UPD那樣,它提供可靠的、面向連接的字節(jié)流傳輸,因此TCP報(bào)文首部比較復(fù)雜:
按照上圖,1個(gè)TCP報(bào)文段的最大長(zhǎng)度為65495字節(jié),TCP封裝在IP內(nèi),IP數(shù)據(jù)報(bào)最大長(zhǎng)度65535 ,頭部最小20字節(jié);TCP頭部長(zhǎng)度最小20字節(jié),所以最大封裝數(shù)據(jù)長(zhǎng)度為65535-20-20=65495字節(jié),實(shí)際情況下,還受很多因素影響,會(huì)短很多。
TCP 首部字段釋義:
- 端口號(hào):用來(lái)標(biāo)識(shí)一臺(tái)主機(jī)的不同進(jìn)程
- 1) 源端端口號(hào):源端口和IP層解析出來(lái)的IP地址標(biāo)識(shí)報(bào)文的發(fā)送地,同時(shí)也確定了報(bào)文的返回地址;
- 2)目的端口號(hào):表明了該數(shù)據(jù)報(bào)是發(fā)送給接收方計(jì)算機(jī)的具體的一個(gè)應(yīng)用程序 。
- 序號(hào)和確定號(hào):TCP可靠傳輸?shù)谋U?
- 1) 序號(hào):文段發(fā)送的數(shù)據(jù)組的第一個(gè)字節(jié)的序號(hào)。在TCP傳送的流中,每一個(gè)字節(jié)一個(gè)序號(hào)。例如:一個(gè)報(bào)文段的序號(hào)為300,此報(bào)文段數(shù)據(jù)部分共有100字節(jié),則下一個(gè)報(bào)文段的序號(hào)為400。所以序號(hào)確保了TCP傳輸?shù)挠行蛐?/li>
- 2)確認(rèn)號(hào):即
ACK
,指明下一個(gè)期待收到的字節(jié)序號(hào),表明該序號(hào)之前的所有數(shù)據(jù)已經(jīng)正確無(wú)誤的收到。確認(rèn)號(hào)只有當(dāng)ACK標(biāo)志為1時(shí)才有效。比如收到一個(gè)報(bào)文段的序號(hào)為300,報(bào)文段數(shù)據(jù)部分共有100字節(jié),回復(fù)的ACK的確認(rèn)序號(hào)就是400- 數(shù)據(jù)偏移:也叫做首部長(zhǎng)度,以32位比特位為長(zhǎng)度單位
- 使用4個(gè)比特位,因?yàn)閳?bào)頭數(shù)據(jù)中含有可選項(xiàng)字段,所以TCP報(bào)頭的長(zhǎng)度是不確定的,除去可選項(xiàng)字段TCP的長(zhǎng)度為20字節(jié),4bit最大表示的數(shù)據(jù)為15,15* (32 / 8)= 60 ,故。TCP報(bào)頭最大長(zhǎng)度為60字節(jié)
- 保留:為將來(lái)定義新的用途保留,現(xiàn)在一般置0
- 標(biāo)志位:UGR ACK PSH RST SYN FIN ,六個(gè)標(biāo)志位代表六個(gè)不同的功能
- 1) UGR: 緊急指針標(biāo)志,為 1 時(shí)表示緊急指針有效,為 0 則忽略緊急指針
- 2) ACK: 確認(rèn)序號(hào)標(biāo)志,為 1 時(shí)表示確認(rèn)序號(hào)有效,為 0 則表示報(bào)文中不含有確認(rèn)信息,忽略確認(rèn)字段號(hào)
- 3) PSH:push標(biāo)志,為 1 表示是帶有push標(biāo)志的數(shù)據(jù),指示接收方在接收到數(shù)據(jù)以后,應(yīng)盡快的將這個(gè)報(bào)文交付到應(yīng)用程序,而不是在緩沖區(qū)緩沖
- 4)RST: 重置連接標(biāo)志,用于由主機(jī)崩潰或者其他原因而出現(xiàn)錯(cuò)誤的連接,或者用于拒絕非法的報(bào)文段和拒絕連接請(qǐng)求
- 5)
SYN
: 同步序號(hào),用于連接建立過程,在請(qǐng)求連接的時(shí)候,SYN=1和ACK=0表示該數(shù)據(jù)段沒有捎帶確認(rèn)域,而連接應(yīng)答捎帶一個(gè)確認(rèn),表示為SYN=1和ACK=1- 6)
FIN
: 斷開連接標(biāo)志,用于釋放連接,為 1 表示發(fā)送方已經(jīng)沒有數(shù)據(jù)發(fā)送,即關(guān)閉數(shù)據(jù)流- 窗口:滑動(dòng)窗口大小,用來(lái)告知發(fā)送端接收端的緩存大小,以此來(lái)控制發(fā)送端的發(fā)送速率,從而達(dá)到流量控制。窗口大小是一個(gè)16比特位的字段,因而窗口大小最大為65535字節(jié)
- 校驗(yàn)和: 奇偶校驗(yàn),此校驗(yàn)和是對(duì)整個(gè)TCP報(bào)文段,包括TCP頭部和TCP數(shù)據(jù),以16位字進(jìn)行計(jì)算所得,由發(fā)送端計(jì)算和存儲(chǔ),并由接收端進(jìn)行驗(yàn)證
- 緊急指針: 只有當(dāng)URG標(biāo)志置為1的時(shí)候,緊急指針才有效,緊急指針是一個(gè)正的偏移量,和順序號(hào)字段中的值相加表示緊急數(shù)據(jù)最后一個(gè)字節(jié)的序號(hào)。TCP的緊急方式是發(fā)送端向另一端發(fā)送緊急數(shù)據(jù)的一種方式
- 選項(xiàng)和填充: 最常見的可選字段是最長(zhǎng)報(bào)文的大小,又稱為MSS,每個(gè)連接方通常在通信的第一個(gè)報(bào)文段(也就是第一次握手的SYN報(bào)文的時(shí)候)中指明這個(gè)選項(xiàng),表示本端所能接受的最大報(bào)文段的長(zhǎng)度。提示:選項(xiàng)長(zhǎng)度不一定是32位的整倍數(shù),所以要有填充位,即在這個(gè)字段中加入額外的零,以保證TCP頭是32的整倍數(shù)
- 數(shù)據(jù)部分:TCP報(bào)文段中的數(shù)據(jù)部分是可選的,在一個(gè)建立連接和斷開連接的時(shí)候,雙方交換的報(bào)文段只有TCP的首部。如果一方?jīng)]有數(shù)據(jù)要發(fā)送,也滅幼使用任何數(shù)據(jù)的首部倆確認(rèn)收到的數(shù)據(jù),在處理超時(shí)的許多情況中,也會(huì)發(fā)送不帶你任何數(shù)據(jù)的報(bào)文段。
1.3套接字編程
1.31 套接字名稱分類
套接字用于在2個(gè)進(jìn)程之間建立連接,就像一個(gè)套接管一樣。
套接管(靈魂畫手??):
雖然說(shuō)這名字挺有有道理,但真不好理解,很多人連套接管都不知道。我更喜歡叫他原名socket
(它的翻譯時(shí)插座、插口)。
socket用來(lái)唯一標(biāo)識(shí)網(wǎng)絡(luò)中的一個(gè)通信連接,套接字=ip地址:端口號(hào),如10.0.0.1:443
但是下面這些,也可以稱為socket:
- 允許應(yīng)用程序訪問連網(wǎng)協(xié)議的應(yīng)用編程接口 API (Application ProgrammingInterface),即運(yùn)輸層和應(yīng)用層之間的一種接口,稱為
socket API
,并簡(jiǎn)稱為socket。 - 在socket API中使用的一個(gè)函數(shù)名也叫做socket.
- 調(diào)用socket函數(shù)的端點(diǎn)稱為socket,.如“創(chuàng)建一個(gè)數(shù)據(jù)報(bào)socket”。
- 調(diào)用socket函數(shù)時(shí),其返回值稱為socket描述符,可簡(jiǎn)稱為socket.
- 在操作系統(tǒng)內(nèi)核中連網(wǎng)協(xié)議的Berkeley實(shí)現(xiàn),稱為socket實(shí)現(xiàn)。
3和4,也可以稱為:句柄。
句柄:(比如文件句柄、窗口句柄、內(nèi)存句柄、對(duì)象句柄等等)也叫做描述符,它通常是一個(gè)指針,指向一個(gè)描述某個(gè)對(duì)象的數(shù)據(jù)結(jié)構(gòu),這個(gè)數(shù)據(jù)結(jié)構(gòu)可以使對(duì)象的屬性、方法、狀態(tài)或者數(shù)據(jù)等等。句柄是一種抽象和封裝,隱藏了許多底層的細(xì)節(jié)。(通常Windows下叫句柄,Linux下叫描述符)
套接字分為:原始套接字、流式套接字、數(shù)據(jù)報(bào)套接字:
- 原始套接字使開發(fā)人員能對(duì)底層的數(shù)據(jù)傳輸進(jìn)行控制,原始套接字下接收的數(shù)據(jù)含有IP首部;
- 流式套接字(stream socket)提供雙向、有序、可靠的數(shù)據(jù)服務(wù),通信前需要先建立連接,TCP采用的就是流式套接字,因此,我們通常稱為TCP套接字。
- 數(shù)據(jù)報(bào)套接字:不能保證可靠、有序和不重復(fù),也稱為UDP套接字。
下面先從簡(jiǎn)單的UDP套接字開始介紹。
1.32 socket編程使用的函數(shù)、常量、結(jié)構(gòu)體
各種編程語(yǔ)言、操作系統(tǒng),都有相應(yīng)的socket編程API,他們的函數(shù)、數(shù)據(jù)結(jié)構(gòu)可能會(huì)有一些差異,但功能都是類似的。
下面是socket編程中常用的內(nèi)容:
函數(shù):
-
socket()
: 創(chuàng)建一個(gè)套接字 -
bind()
: 將套接字與特定的地址和端口綁定 -
listen()
: 監(jiān)聽傳入的連接請(qǐng)求 -
accept()
: 接受連接請(qǐng)求,創(chuàng)建一個(gè)新的套接字用于通信 -
connect()
: 建立與遠(yuǎn)程套接字的連接 -
send()/sendto():
發(fā)送數(shù)據(jù) -
recv()/recvfrom()
: 接收數(shù)據(jù) -
close()
: 關(guān)閉套接字
常量:
-
AF_INET
: IPv4地址族 -
AF_INET6:
IPv6地址族 -
SOCK_STREAM
: 流式套接字,通常用于TCP -
SOCK_DGRAM
: 數(shù)據(jù)報(bào)套接字,通常用于UDP -
IPPROTO_TCP
: TCP協(xié)議 -
IPPROTO_UDP
: UDP協(xié)議
結(jié)構(gòu)體:
- sockaddr_in: IPv4地址結(jié)構(gòu)
- sockaddr_in6: IPv6地址結(jié)構(gòu)
- sockaddr: 通用地址結(jié)構(gòu),用于在函數(shù)中表示地址
這只是一些常見的函數(shù)、常量和結(jié)構(gòu)體示例,實(shí)際上可能還有其他函數(shù)和相關(guān)的數(shù)據(jù)結(jié)構(gòu)和常量,具體取決于編程語(yǔ)言和操作系統(tǒng)的支持。
后面還會(huì)具體介紹。
1.33 UDP套接字
? UDP套接字簡(jiǎn)介
-
UDP套接字概述
UDP是一種面向數(shù)據(jù)報(bào)的傳輸協(xié)議,而UDP套接字則提供了對(duì)UDP協(xié)議的抽象接口。UDP套接字通過數(shù)據(jù)報(bào)進(jìn)行通信,每個(gè)數(shù)據(jù)報(bào)是一個(gè)獨(dú)立的、不可拆分的消息單元。UDP套接字以無(wú)連接的方式進(jìn)行通信,不需要在發(fā)送和接收數(shù)據(jù)之前建立連接。 -
特點(diǎn)與優(yōu)勢(shì)
- 無(wú)連接性:UDP套接字不需要在通信之前建立連接,通信雙方可以直接發(fā)送和接收數(shù)據(jù)報(bào)。這使得UDP套接字的建立和斷開的開銷較低,適用于一對(duì)多的廣播或多播通信。
- 不可靠性:UDP協(xié)議本身不提供數(shù)據(jù)的可靠傳輸機(jī)制,數(shù)據(jù)報(bào)可能會(huì)丟失、重復(fù)、亂序或損壞。這使得UDP套接字適用于那些對(duì)數(shù)據(jù)傳輸?shù)膶?shí)時(shí)性要求較高,但可靠性要求較低的應(yīng)用,如實(shí)時(shí)音視頻傳輸。
- 低延遲:由于無(wú)連接的特性,UDP套接字具有較低的傳輸延遲。它避免了TCP的連接建立和斷開過程,適合于需要快速傳輸數(shù)據(jù)、對(duì)準(zhǔn)確性要求不嚴(yán)格的場(chǎng)景。
-
應(yīng)用場(chǎng)景
UDP套接字在以下場(chǎng)景中得到廣泛應(yīng)用:- 實(shí)時(shí)音視頻傳輸:UDP套接字適用于實(shí)時(shí)音視頻傳輸,如視頻會(huì)議、實(shí)時(shí)直播等。雖然數(shù)據(jù)報(bào)可能會(huì)丟失,但可以通過其他機(jī)制進(jìn)行丟失恢復(fù)或補(bǔ)償。
- 游戲應(yīng)用:多人在線游戲通常需要快速傳輸玩家的位置和動(dòng)作信息,UDP套接字提供了低延遲和即時(shí)性的數(shù)據(jù)傳輸,使得游戲體驗(yàn)更加流暢。
- 廣播和多播:UDP套接字可以進(jìn)行一對(duì)多的廣播和多播通信。它可以將數(shù)據(jù)報(bào)發(fā)送到多個(gè)目標(biāo)地址,適用于需要同時(shí)向多個(gè)客戶端發(fā)送相同數(shù)據(jù)的場(chǎng)景,如廣播消息、設(shè)備發(fā)現(xiàn)等。
-
與TCP套接字的區(qū)別
UDP套接字與TCP套接字在特性上存在明顯的區(qū)別:- 連接性:TCP套接字是面向連接的,需要在通信之前建立連接。而UDP套接字是無(wú)連接的,通信雙方可以直接發(fā)送和接收數(shù)據(jù)報(bào)。
- 可靠性:TCP套接字提供可靠的數(shù)據(jù)傳輸,通過確認(rèn)和重傳機(jī)制來(lái)確保數(shù)據(jù)的完整性和順序性。UDP套接字則不提供數(shù)據(jù)的可靠傳輸,數(shù)據(jù)報(bào)可能會(huì)丟失、重復(fù)、亂序或損壞。
- 有序性:TCP套接字保證數(shù)據(jù)的有序傳輸,而UDP套接字不保證數(shù)據(jù)報(bào)的順序,不同的數(shù)據(jù)報(bào)可能以不同的順序到達(dá)目標(biāo)。
TCP、UDP可以在很多模式下工作(對(duì)等、多播、廣播),本文主要介紹經(jīng)典的服務(wù)器-客戶端模式(C/S),這也是實(shí)際應(yīng)用中最常見的工作模式,下面的通信過程、代碼,默認(rèn)試試C/S模式。
? UDP套接字通信過程
當(dāng)使用UDP套接字進(jìn)行通信時(shí),過程比較簡(jiǎn)單,以下是UDP套接字的通信過程的詳細(xì)說(shuō)明:
創(chuàng)建套接字:使用
socket()
函數(shù)創(chuàng)建一個(gè)UDP套接字。指定協(xié)議簇(如AF_INET)和套接字類型(如SOCK_DGRAM)。綁定套接字:使用
bind()
函數(shù)將套接字綁定到本地地址和端口。綁定套接字可以讓操作系統(tǒng)知道該套接字要使用的本地地址和端口號(hào)。接收數(shù)據(jù):調(diào)用
recvfrom()
函數(shù)等待接收來(lái)自網(wǎng)絡(luò)中其他主機(jī)的UDP數(shù)據(jù)報(bào)。該函數(shù)會(huì)阻塞當(dāng)前進(jìn)程,直到有數(shù)據(jù)到達(dá)套接字。當(dāng)數(shù)據(jù)到達(dá)時(shí),操作系統(tǒng)將數(shù)據(jù)復(fù)制到應(yīng)用程序指定的接收緩沖區(qū),并返回?cái)?shù)據(jù)的發(fā)送者的地址和端口。處理數(shù)據(jù):應(yīng)用程序可以對(duì)接收到的UDP數(shù)據(jù)報(bào)進(jìn)行處理。這可能包括解析數(shù)據(jù)報(bào)的內(nèi)容、驗(yàn)證數(shù)據(jù)的完整性、進(jìn)行數(shù)據(jù)處理等操作。
準(zhǔn)備發(fā)送數(shù)據(jù):準(zhǔn)備要發(fā)送的UDP數(shù)據(jù)報(bào),包括目標(biāo)地址和端口號(hào)以及要發(fā)送的數(shù)據(jù)內(nèi)容。
發(fā)送數(shù)據(jù):使用
sendto()
函數(shù)將UDP數(shù)據(jù)報(bào)發(fā)送給特定的目標(biāo)地址和端口??梢灾付繕?biāo)地址為其他主機(jī)的IP地址和端口號(hào)。操作系統(tǒng)將數(shù)據(jù)報(bào)發(fā)送到網(wǎng)絡(luò)中,并不關(guān)心是否成功到達(dá)目標(biāo)主機(jī)。等待響應(yīng)(可選):應(yīng)用程序可以選擇等待接收來(lái)自目標(biāo)主機(jī)的響應(yīng)數(shù)據(jù)。這需要調(diào)用recvfrom()函數(shù)等待接收響應(yīng)數(shù)據(jù)報(bào)。
關(guān)閉套接字:使用
close()
函數(shù)關(guān)閉UDP套接字,釋放相關(guān)的資源。
1.34 TCP套接字
就不繼續(xù)啰嗦介紹特點(diǎn)什么的了,前面有對(duì)比。
? TCP套接字通信過程
TCP 需要先建立連接,才傳輸數(shù)據(jù)。
創(chuàng)建套接字:使用
socket()
函數(shù)創(chuàng)建一個(gè)TCP套接字。指定協(xié)議簇(如AF_INET)和套接字類型(如SOCK_STREAM)。綁定套接字(可選):如果是服務(wù)器端,可以使用
bind()
函數(shù)將套接字綁定到指定的本地地址和端口。這樣客戶端就可以連接到該地址和端口進(jìn)行通信。如果是客戶端,可以省略此步驟。監(jiān)聽連接(僅服務(wù)器端):如果是服務(wù)器端,使用
listen()
函數(shù)開始監(jiān)聽來(lái)自客戶端的連接請(qǐng)求。指定同時(shí)允許多少個(gè)連接請(qǐng)求進(jìn)入待處理隊(duì)列。建立連接(僅客戶端):如果是客戶端,使用
connect()
函數(shù)連接到服務(wù)器端的指定地址和端口。該函數(shù)會(huì)阻塞當(dāng)前進(jìn)程,直到連接成功建立或發(fā)生錯(cuò)誤。接受連接請(qǐng)求(僅服務(wù)器端):使用
accept()
函數(shù)接受客戶端的連接請(qǐng)求。該函數(shù)會(huì)阻塞當(dāng)前進(jìn)程,直到有客戶端連接進(jìn)入。一旦連接被接受,將創(chuàng)建一個(gè)新的套接字來(lái)處理與該客戶端的通信。發(fā)送數(shù)據(jù):使用
send()
函數(shù)發(fā)送數(shù)據(jù)給連接的對(duì)方??梢詫⒁l(fā)送的數(shù)據(jù)放入發(fā)送緩沖區(qū)。接收數(shù)據(jù):使用
recv()
函數(shù)等待接收來(lái)自對(duì)方的數(shù)據(jù)。該函數(shù)會(huì)阻塞當(dāng)前進(jìn)程,直到有數(shù)據(jù)到達(dá)。一旦有數(shù)據(jù)到達(dá),操作系統(tǒng)將數(shù)據(jù)復(fù)制到應(yīng)用程序指定的接收緩沖區(qū)。處理數(shù)據(jù):應(yīng)用程序可以對(duì)接收到的數(shù)據(jù)進(jìn)行處理,如解析數(shù)據(jù)內(nèi)容、進(jìn)行數(shù)據(jù)處理等。
發(fā)送響應(yīng)(可選):根據(jù)業(yè)務(wù)邏輯,應(yīng)用程序可以選擇發(fā)送響應(yīng)數(shù)據(jù)給對(duì)方。
關(guān)閉連接:使用
close()
函數(shù)關(guān)閉TCP連接??梢赃x擇在雙方都完成數(shù)據(jù)傳輸后關(guān)閉連接,或根據(jù)應(yīng)用程序的需求決定何時(shí)關(guān)閉連接。
ok,準(zhǔn)備工作差不多了,來(lái)快樂地寫代碼吧????????
二、Windows下C語(yǔ)言 socket編程
2.1 <Winsock2.h>詳解
2.11 庫(kù)的引入和初始化
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
第二行的靜態(tài)庫(kù)加載是必要的,因?yàn)檫@不是C語(yǔ)言的標(biāo)準(zhǔn)庫(kù),編譯的時(shí)候編譯器不會(huì)主動(dòng)幫你連接到庫(kù)。
WSADATA
是 Windows Sockets Data 結(jié)構(gòu)體的類型定義,它用于在使用 Winsock 庫(kù)進(jìn)行網(wǎng)絡(luò)編程時(shí)存儲(chǔ)相關(guān)的初始化和版本信息。
WSADATA
結(jié)構(gòu)體包含了用于存儲(chǔ) Winsock 庫(kù)初始化后的信息和狀態(tài)的字段。在使用 Winsock 庫(kù)之前,我們需要在應(yīng)用程序中聲明一個(gè) WSADATA
類型的變量,并在初始化 Winsock 庫(kù)時(shí)將其傳遞給相應(yīng)的函數(shù),以便獲取初始化的狀態(tài)和版本信息。
以下是 WSADATA
結(jié)構(gòu)體的定義:
typedef struct _WSADATA {
WORD wVersion; // 請(qǐng)求的 Winsock 版本
WORD wHighVersion; // 支持的最高 Winsock 版本
char szDescription[WSADESCRIPTION_LEN+1]; // 描述信息
char szSystemStatus[WSASYS_STATUS_LEN+1]; // 系統(tǒng)狀態(tài)
unsigned short iMaxSockets; // 支持的最大套接字?jǐn)?shù)
unsigned short iMaxUdpDg; // 支持的最大 UDP 數(shù)據(jù)報(bào)大小
char* lpVendorInfo; // 供應(yīng)商信息
} WSADATA;
當(dāng)調(diào)用 WSAStartup
函數(shù)初始化 Winsock 庫(kù)時(shí),將會(huì)填充 WSADATA
結(jié)構(gòu)體的相應(yīng)字段,提供關(guān)于 Winsock 庫(kù)的詳細(xì)信息。我們可以通過檢查 wVersion
字段來(lái)確保請(qǐng)求的 Winsock 版本被成功初始化,并且根據(jù)需要使用其他字段中的信息。
WSAStartup
函數(shù)是 Windows Sockets 啟動(dòng)函數(shù),用于初始化 Winsock 庫(kù)的使用。
函數(shù)原型如下:
int WSAStartup(
WORD wVersionRequested,
LPWSADATA lpWSAData
);
參數(shù)說(shuō)明:
-
wVersionRequested
:請(qǐng)求的 Winsock 版本,以 WORD 類型表示。通常使用宏MAKEWORD
創(chuàng)建所需的版本號(hào),例如MAKEWORD(2, 2)
表示請(qǐng)求使用版本 2.2。 -
lpWSAData
:指向WSADATA
結(jié)構(gòu)體的指針,用于接收初始化后的 Winsock 信息和狀態(tài)。
函數(shù)返回值:
- 如果調(diào)用成功,返回值為零(
0
)。 - 如果調(diào)用失敗,返回值為非零,可以通過調(diào)用
WSAGetLastError
獲取錯(cuò)誤代碼。
在使用 Winsock 相關(guān)函數(shù)之前,需要先調(diào)用 WSAStartup
函數(shù)初始化庫(kù),以確保庫(kù)的正確運(yùn)行和版本匹配。
在成功調(diào)用 WSAStartup
后,會(huì)填充 lpWSAData
參數(shù)指向的 WSADATA
結(jié)構(gòu)體,其中包含了有關(guān) Winsock 庫(kù)的詳細(xì)信息和狀態(tài)。我們可以檢查 WSADATA
結(jié)構(gòu)體中的字段,如 wVersion
,以確保請(qǐng)求的 Winsock 版本已成功初始化。
在使用完 Winsock 庫(kù)后,應(yīng)調(diào)用 WSACleanup
函數(shù)來(lái)釋放 Winsock 資源。 且分別在使用 Winsock 函數(shù)之前和之后分別調(diào)用。
示例:
#include <stdio.h>
#include <winsock2.h>
int main() {
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
// 初始化 Winsock 庫(kù)
int result = WSAStartup(wVersionRequested, &wsaData);
if (result != 0) {
printf("WSAStartup failed: %d\n", result);
return 1;
}
// 使用 Winsock 庫(kù)進(jìn)行網(wǎng)絡(luò)編程
// ...
// 釋放 Winsock 資源
WSACleanup();
return 0;
}
2.12 常量和結(jié)構(gòu)體
這些常量和結(jié)構(gòu)體提供了表示網(wǎng)絡(luò)地址、協(xié)議族和套接字類型的方式,并在網(wǎng)絡(luò)編程中被廣泛使用。通過使用這些常量和結(jié)構(gòu)體,開發(fā)者可以方便地指定地址、端口和協(xié)議,并在套接字編程中進(jìn)行地址轉(zhuǎn)換、綁定、連接等操作。
常量:
-
AF_INET
:表示 IPv4 地址族。 - AF_INET6:表示 IPv6 地址族。
-
SOCK_STREAM
:表示流式套接字,用于 TCP 協(xié)議。 -
SOCK_DGRAM
:表示數(shù)據(jù)報(bào)套接字,用于 UDP 協(xié)議。 -
IPPROTO_TCP
:表示 TCP 協(xié)議。 -
IPPROTO_UDP
:表示 UDP 協(xié)議。 - INADDR_ANY:表示通配地址,用于綁定套接字時(shí)指定任意可用的本地地址。
- INADDR_LOOPBACK:表示回環(huán)地址,用于本地測(cè)試和通信。
結(jié)構(gòu)體:
-
sockaddr
:- 描述:用于表示套接字的地址信息。
- 成員:
- sa_family:地址族,通常為 AF_INET 或 AF_INET6。
- sa_data:存儲(chǔ)地址信息的字節(jié)流。
struct sockaddr{
sa_family_t sin_family; //地址族(Address Family),也就是地址類型
char sa_data[14]; //IP地址和端口號(hào)
};
-
sockaddr_in
:- 描述:用于表示 IPv4 套接字的地址信息。
- 成員:
- sin_family:地址族,固定為 AF_INET。
- sin_port:16 位端口號(hào)。
- sin_addr:32 位 IP 地址。(是個(gè)結(jié)構(gòu)體)
- sin_zero:用于填充字節(jié),使結(jié)構(gòu)體大小與 sockaddr 保持一致。
struct sockaddr_in{
sa_family_t sin_family; //地址族(Address Family),也就是地址類型
uint16_t sin_port; //16位的端口號(hào)
struct in_addr sin_addr; //32位IP地址
char sin_zero[8]; //不使用,一般用0填充
};
其中,sin_addr為:
struct in_addr{
in_addr_t s_addr; //32位的IP地址(4字節(jié)的整數(shù))
};
- sockaddr_in6:
- 描述:用于表示 IPv6 套接字的地址信息。
- 成員:
- sin6_family:地址族,固定為 AF_INET6。
- sin6_port:16 位端口號(hào)。
- sin6_flowinfo:流信息,用于區(qū)分?jǐn)?shù)據(jù)流。
- sin6_addr:128 位 IPv6 地址。
- sin6_scope_id:用于區(qū)分接口的范圍標(biāo)識(shí)符。
重要說(shuō)明:
套接字的地址信息是很重要的內(nèi)容。
socket函數(shù)中的參數(shù)列表中,都是:sockaddr*
類型的參數(shù),即指向sockaddr類型的指針,也就是上面的第一個(gè)結(jié)構(gòu)體。
但是:sockaddr結(jié)構(gòu)體中,將ip、地址和端口號(hào)合起來(lái)了,不方便我們操作;而第二個(gè)結(jié)構(gòu)體sockaddr_in
,做的很好,很清晰。
很難給 sockaddr 類型的變量賦值,所以使用 sockaddr_in
來(lái)代替。這兩個(gè)結(jié)構(gòu)體的長(zhǎng)度相同,強(qiáng)制轉(zhuǎn)換類型時(shí)不會(huì)丟失字節(jié),也沒有多余的字節(jié)。
所以常見的操作是,使用sockaddr_in設(shè)置參數(shù),使用 ,再?gòu)?qiáng)制類型轉(zhuǎn)換為:sockaddr 類型。
如:
//創(chuàng)建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//創(chuàng)建sockaddr_in結(jié)構(gòu)體變量
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr)); //每個(gè)字節(jié)都用0填充
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具體的IP地址,使用這個(gè)函數(shù)將字符串轉(zhuǎn)為對(duì)應(yīng)的類型
serv_addr.sin_port = htons(1234); //端口
//將套接字和IP、端口綁定
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
最后注意,結(jié)構(gòu)體名稱不是結(jié)構(gòu)體地址。
2.13 函數(shù)原型
注意:UDP和TCP的接收、發(fā)送函數(shù)是不同的,因?yàn)樗麄円粋€(gè)面向連接、一個(gè)無(wú)連接嘛。
注意參數(shù)和返回值。
-
socket():
- 函數(shù)原型:
SOCKET socket(int af, int type, int protocol)
; - 描述:創(chuàng)建一個(gè)套接字。
- 參數(shù):
- af:指定地址族,如 AF_INET(IPv4)或 AF_INET6(IPv6)。
- type:指定套接字類型,如 SOCK_STREAM(TCP)或 SOCK_DGRAM(UDP)。
- protocol:指定協(xié)議,通常為 0,根據(jù)套接字類型和地址族自動(dòng)選擇合適的協(xié)議。
- 返回值:成功:返回一個(gè)新創(chuàng)建的套接字描述符;失敗:
INVALID_SOCKET
。
- 函數(shù)原型:
-
bind():
- 函數(shù)原型:
int bind(SOCKET s, const sockaddr* name, int namelen);
- 描述:將套接字綁定到指定的本地地址和端口。
- 參數(shù):
- s:套接字描述符。
- name:指向 sockaddr 結(jié)構(gòu)的指針,表示要綁定的地址(通常我們使用sockaddr_in結(jié)構(gòu)體,然后類型轉(zhuǎn)換為sockaddr )。
- namelen:sockaddr 結(jié)構(gòu)的長(zhǎng)度。
- 返回值:成功返回 0,失敗返回
SOCKET_ERROR
。
- 函數(shù)原型:
-
listen():
- 函數(shù)原型:
int listen(SOCKET s, int backlog);
- 描述:開始監(jiān)聽套接字上的連接請(qǐng)求。
- 參數(shù):
- s:服務(wù)端的套接字描述符。
- backlog:等待處理的連接請(qǐng)求的最大數(shù)量。
- 返回值:成功返回 0,失敗返回 SOCKET_ERROR。
- 函數(shù)原型:
-
accept():
- 函數(shù)原型:
SOCKET accept(SOCKET s, sockaddr* addr, int* addrlen);
- 描述:接受客戶端的連接請(qǐng)求,并創(chuàng)建一個(gè)新的套接字用于與客戶端通信。
- 參數(shù):
- s:服務(wù)端套接字描述符。
- addr:指向 sockaddr 結(jié)構(gòu)的指針,用于存儲(chǔ)客戶端的地址信息。
- addrlen:addr 緩沖區(qū)的長(zhǎng)度。
- 返回值:返回新創(chuàng)建的套接字描述符(后面通信用這個(gè),而不是原來(lái)的客戶端套接字),失敗返回 INVALID_SOCKET。
- 函數(shù)原型:
-
connect():
- 函數(shù)原型:
int connect(SOCKET s, const sockaddr* name, int namelen);
- 描述:與指定的目標(biāo)套接字建立連接。
- 參數(shù):
- s:客戶端套接字描述符。
- name:指向 sockaddr 結(jié)構(gòu)的指針,表示目標(biāo)套接字的地址。
- namelen:sockaddr 結(jié)構(gòu)的長(zhǎng)度。
- 返回值:成功返回 0,失敗返回 SOCKET_ERROR。
- 函數(shù)原型:
-
send()
:用在TCP中- 函數(shù)原型:
int send(SOCKET s, const char* buf, int len, int flags);
- 描述:發(fā)送數(shù)據(jù)給連接的對(duì)方,用在TCP中。
- 參數(shù):
- s:要發(fā)送數(shù)據(jù)的套接字描述符(connect函數(shù)返回的那個(gè))。
- buf:指向要發(fā)送數(shù)據(jù)的緩沖區(qū)。
- len:要發(fā)送的數(shù)據(jù)長(zhǎng)度。
- flags:可選的標(biāo)志,如 MSG_DONTROUTE、MSG_OOB 等。
- 返回值:成功返回發(fā)送的字節(jié)數(shù),失敗返回 SOCKET_ERROR。
- 函數(shù)原型:
-
sendto()
: 用在UDP中- 函數(shù)原型:
int sendto(int socket, const void *buffer, int length, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
- 描述:發(fā)送數(shù)據(jù)給對(duì)方,用在UDP中。
- 參數(shù):
- socket: 套接字描述符
- buffer:待發(fā)送數(shù)據(jù)的緩沖區(qū)指針
- ength:待發(fā)送數(shù)據(jù)的長(zhǎng)度
- flags :可選的標(biāo)志參數(shù)
- dest_addr:目標(biāo)地址的結(jié)構(gòu)體指針
- addrlen:目標(biāo)地址結(jié)構(gòu)體的長(zhǎng)度
- 返回值:返回值表示成功發(fā)送的字節(jié)數(shù),返回值為 -1 表示發(fā)送失敗。
- 函數(shù)原型:
-
recv()
:用在TCP中- 函數(shù)原型:
int recv(SOCKET s, char* buf, int len, int flags);
- 描述:接收來(lái)自連接對(duì)方的數(shù)據(jù)。
- 參數(shù):
- s:要接受數(shù)據(jù)的套接字描述符。
- buf:接收數(shù)據(jù)的緩沖區(qū)。
- len:buf 緩沖區(qū)的長(zhǎng)度。
- flags:可選的標(biāo)志,如 MSG_PEEK、MSG_WAITALL 等(設(shè)為0或NULL)。
- 返回值:成功返回接收到的字節(jié)數(shù),失敗返回 SOCKET_ERROR。
- 函數(shù)原型:
-
recvfrom()
:用在UDP中- 函數(shù)原型:
int recvfrom(int socket, void *buffer, int length, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
- 描述:用于從一個(gè)無(wú)連接套接字(如 UDP 套接字)接收數(shù)據(jù)
- 參數(shù):
- socket:套接字描述符。
- buffer:接收數(shù)據(jù)的緩沖區(qū)指針。
- ength:緩沖區(qū)的長(zhǎng)度,即可接收的最大數(shù)據(jù)量。
- flags:可選的標(biāo)志參數(shù),用于控制接收操作的行為。
- src_add:用于存儲(chǔ)發(fā)送方地址信息的結(jié)構(gòu)體指針。在調(diào)用
recvfrom
函數(shù)之后,該結(jié)構(gòu)體將被填充為發(fā)送方的地址信息。 - addrlen:
src_addr
結(jié)構(gòu)體的長(zhǎng)度,需要傳遞一個(gè)指向socklen_t
類型的指針。
- 返回值:返回值表示實(shí)際接收到的數(shù)據(jù)字節(jié)數(shù)。如果返回值為 -1,則表示接收出錯(cuò)
- 函數(shù)原型:
-
closesocket():
- 函數(shù)原型:
int closesocket(SOCKET s);
- 描述:關(guān)閉套接字。
- 參數(shù):s:要關(guān)閉連接的套接字描述符。
- 返回值:成功返回 0,失敗返回 SOCKET_ERROR。
- 函數(shù)原型:
這些函數(shù)只是 winsock2.h 頭文件中的一部分,用于創(chuàng)建、綁定、監(jiān)聽、接受、連接、發(fā)送和接收數(shù)據(jù)等常見的網(wǎng)絡(luò)編程操作。通過這些函數(shù),開發(fā)者可以構(gòu)建各種網(wǎng)絡(luò)應(yīng)用程序,實(shí)現(xiàn)可靠的數(shù)據(jù)傳輸和網(wǎng)絡(luò)通信。
補(bǔ)充1:
如果服務(wù)器和客戶端的字節(jié)模式(一個(gè)大端模式、一個(gè)小端)不一樣會(huì)怎么樣,收到的數(shù)據(jù)順序和發(fā)送的一致嗎?
—— 一致。對(duì)于這種問題,數(shù)據(jù)在發(fā)送時(shí)統(tǒng)一采用網(wǎng)絡(luò)字節(jié)序(即大端)。socket的發(fā)送、接收函數(shù)會(huì)自動(dòng)完成發(fā)送和接收時(shí)的轉(zhuǎn)換工作。
補(bǔ)充2:
有一些函數(shù)返回值或者參數(shù)類型是size_t
或者int
,或者ssize_t
。這里要稍微注意以下,雖然絕大數(shù)情況下不會(huì)出錯(cuò),但可能有潛在風(fēng)險(xiǎn)。
-
int
:C語(yǔ)言的基本整數(shù)類型,為有符號(hào)整數(shù),通常是32位; -
size_t
:無(wú)符號(hào)整數(shù)類型,表示對(duì)象大小、數(shù)組長(zhǎng)度或內(nèi)存塊的字節(jié)數(shù)(比如sizeof返回值),它的值是非負(fù)的。很多編譯器和平臺(tái)上,他的定義可能是unsigned int
,但C語(yǔ)言標(biāo)準(zhǔn)并沒有這樣規(guī)定,有的平臺(tái)也有可能被定義為unsigned long
。 -
ssize_t
:有符號(hào)整數(shù)類型,用來(lái)表示字節(jié)數(shù)或數(shù)據(jù)大小,通常用來(lái)表示讀取和寫入的結(jié)果。 - 但函數(shù)的參數(shù)類型和你傳入的數(shù)據(jù)類型不完全相同時(shí),應(yīng)該注意類型轉(zhuǎn)換。
2.2 UDP 套接字編程
函數(shù),流程,前面都講完了。這里直接上代碼。
要說(shuō)明的是:
- 同一電腦上運(yùn)行他們,可以通信;
- 同一局域網(wǎng)運(yùn)行,也可以連接(連接同一wifi時(shí),你可以用手機(jī)軟件做客戶端或者服務(wù)端來(lái)測(cè)試);
- 服務(wù)端使用公網(wǎng)ip,也可以連接;
- 跨網(wǎng)絡(luò)時(shí),使用局域網(wǎng)地址是不能通信的。
2.21 服務(wù)端
sever的代碼里面我寫了詳細(xì)注釋,client就不寫了哦。
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib") //加載 socket靜態(tài)庫(kù)
#define BUF_SIZE 1024 // 緩沖區(qū)大小
#define PORT 8888 // 端口號(hào)
int main() {
// 初始化 Winsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { //請(qǐng)求的winsock版本是winsock2
printf("WSAStartup failed.\n");
return 1;
}
SOCKET serverSocket; // 服務(wù)端、客戶端套接字句柄
struct sockaddr_in serverAddr, clientAddr; // ipv4地址結(jié)構(gòu)體
int clientAddrLen = sizeof(clientAddr); // 客戶端地址結(jié)構(gòu)體長(zhǎng)度
char buffer[BUF_SIZE]; // 緩沖區(qū)
// 創(chuàng)建套接字
serverSocket = socket(AF_INET, SOCK_DGRAM, 0); // ipv4地址,數(shù)據(jù)報(bào)套接字,根據(jù)套接字自動(dòng)選擇協(xié)議類型(即UDP)
if (serverSocket == INVALID_SOCKET) {
printf("Failed to create socket.\n");
WSACleanup();
return 1;
}
// 設(shè)置服務(wù)器地址和端口
memset(&serverAddr, 0, sizeof(serverAddr)); //每個(gè)字節(jié)都用0填充
serverAddr.sin_family = AF_INET; // 協(xié)議
// serverAddr.sin_addr.s_addr = INADDR_ANY; // 地址(即0.0.0.0,監(jiān)聽本機(jī)所有網(wǎng)卡),寫成:“127.0.0.1”也可以
serverAddr.sin_addr.s_addr = inet_addr("192.168.88.89"); //你自己改成你的哦
serverAddr.sin_port = htons(PORT); // 端口
// 綁定套接字
if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
printf("Failed to bind socket.\n");
closesocket(serverSocket);
WSACleanup();
return 1;
}
printf("UDP server started. Waiting for data...\n");
// 接收和發(fā)送數(shù)據(jù)
while (1) {
int recvLen = recvfrom(serverSocket, buffer, BUF_SIZE, 0, (struct sockaddr*)&clientAddr, &clientAddrLen);
if (recvLen == SOCKET_ERROR) {
printf("Failed to receive data.\n");
break;
}
// 處理接收到的數(shù)據(jù)
buffer[recvLen] = '\0';
printf("Received data from client: %s\n", buffer);
if (strcmp(buffer, "exit") == 0) break; // 收到exit時(shí)退出
// 發(fā)送回應(yīng)數(shù)據(jù)給客戶端
const char* response = "I've got it.";
if (sendto(serverSocket, response, (int)strlen(response), 0, (struct sockaddr*)&clientAddr, clientAddrLen) == SOCKET_ERROR) {
printf("Failed to send response.\n");
break;
}
}
// 關(guān)閉套接字和清理 Winsock
closesocket(serverSocket);
WSACleanup();
return 0;
}
2.22 客戶端
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib") //加載 socket靜態(tài)庫(kù)
#define BUF_SIZE 1024
#define SERVER_IP "192.168.88.89" // 這里是他要連接的服務(wù)端的ip地址和端口號(hào)
#define PORT 8888
int main() {
WSADATA wsaData;
// 初始化 Winsock
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
printf("WSAStartup failed.\n");
return 1;
}
SOCKET clientSocket;
struct sockaddr_in serverAddr; // 它連接的服務(wù)端的ip地址結(jié)構(gòu)體
char buffer[BUF_SIZE];
// 創(chuàng)建套接字
clientSocket = socket(AF_INET, SOCK_DGRAM, 0);
if (clientSocket == INVALID_SOCKET) {
printf("Failed to create socket.\n");
WSACleanup();
return 1;
}
// 設(shè)置服務(wù)器地址和端口
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
serverAddr.sin_port = htons(PORT);
while (1) {
printf("\nEnter message to send (max %d characters, exit to close):", BUF_SIZE);
fgets(buffer, BUF_SIZE, stdin);
buffer[strlen(buffer) - 1] = '\0';
// 發(fā)送數(shù)據(jù)到服務(wù)器
if (sendto(clientSocket, buffer, (int)strlen(buffer), 0, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
printf("Failed to send data.\n");
closesocket(clientSocket);
WSACleanup();
return 1;
}
if (strcmp(buffer, "exit") == 0)break;
// 接收服務(wù)器的回應(yīng)數(shù)據(jù)
int serverAddrLen = sizeof(serverAddr);
int recvLen = recvfrom(clientSocket, buffer, BUF_SIZE, 0, (struct sockaddr*)&serverAddr, &serverAddrLen);
if (recvLen == SOCKET_ERROR) {
printf("Failed to receive response.\n");
closesocket(clientSocket);
WSACleanup();
return 1;
}
// 處理接收到的數(shù)據(jù)
buffer[recvLen] = '\0';
printf("Received response from server: %s\n", buffer);
}
// 關(guān)閉套接字和清理 Winsock
closesocket(clientSocket);
WSACleanup();
return 0;
}
功能測(cè)試:
2.3 TCP 套接字編程
2.31 服務(wù)端
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#define ip "192.168.81.89"
#define PORT 8080
#define BUF_SIZE 1024
int main() {
WSADATA wsaData;
// 初始化Winsock
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
printf("無(wú)法初始化Winsock\n");
return 1;
}
SOCKET serverSocket, clientSocket;
struct sockaddr_in serverAddr, clientAddress;
char buffer[BUF_SIZE];
// 創(chuàng)建服務(wù)器套接字
serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket == INVALID_SOCKET) {
printf("無(wú)法創(chuàng)建套接字\n");
return 1;
}
// 設(shè)置服務(wù)器地址和端口
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
// serverAddress.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_addr.s_addr = inet_addr(ip);
serverAddr.sin_port = htons(PORT);
// 綁定套接字到指定的地址和端口
if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
printf("綁定失敗\n");
return 1;
}
// 監(jiān)聽傳入的連接
if (listen(serverSocket, 1) == SOCKET_ERROR) {
printf("監(jiān)聽失敗\n");
return 1;
}
printf("服務(wù)器正在監(jiān)聽端口 %d\n", PORT);
// 接受傳入的連接
int clientAddressSize = sizeof(clientAddress);
clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &clientAddressSize);
if (clientSocket == INVALID_SOCKET) {
printf("接受連接失敗\n");
return 1;
}
printf("已經(jīng)與客戶端建立連接\n");
while (1) {
// 接收來(lái)自客戶端的數(shù)據(jù)
memset(buffer, 0, sizeof(buffer));
int recvLen = recv(clientSocket, buffer, BUF_SIZE, 0);
if (recvLen == SOCKET_ERROR) {
printf("接收數(shù)據(jù)失敗\n");
return 1;
}
buffer[recvLen] = '\0';
printf("從客戶端接收到的數(shù)據(jù):%s\n", buffer);
if (strcmp(buffer, "exit") == 0) break;
if (send(clientSocket, buffer, (int)strlen(buffer), 0) == SOCKET_ERROR) {
printf("發(fā)送響應(yīng)失敗\n");
return 1;
}
}
// 關(guān)閉連接
closesocket(clientSocket);
closesocket(serverSocket);
WSACleanup();
return 0;
}
2.32 客戶端
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#define server_ip "192.168.81.89"
#define PORT 8080
#define BUF_SIZE 1024
int main() {
// 初始化Winsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
printf("無(wú)法初始化Winsock\n");
return 1;
}
SOCKET clientSocket;
struct sockaddr_in serverAddr;
char buffer[BUF_SIZE];
// 創(chuàng)建客戶端套接字
clientSocket = socket(AF_INET, SOCK_STREAM, 0);
if (clientSocket == INVALID_SOCKET) {
printf("無(wú)法創(chuàng)建套接字\n");
return 1;
}
// 設(shè)置服務(wù)器地址和端口
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);
serverAddr.sin_addr.s_addr = inet_addr(server_ip);
// 連接服務(wù)器
if (connect(clientSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
printf("連接服務(wù)器失敗\n");
return 1;
}
printf("與服務(wù)器建立連接成功\n");
while (1) {
// 發(fā)送數(shù)據(jù)給服務(wù)器
memset(buffer, 0, sizeof(buffer));
printf("\n輸入要發(fā)送的數(shù)據(jù),exit退出:");
fgets(buffer,BUF_SIZE,stdin);
buffer[strlen(buffer) - 1] = '\0';
if (send(clientSocket, buffer, (int)strlen(buffer), 0) == SOCKET_ERROR) {
printf("發(fā)送數(shù)據(jù)失敗\n");
return 1;
}
if (strcmp(buffer, "exit") == 0)break;
// 接收服務(wù)器的響應(yīng)
memset(buffer, 0, BUF_SIZE);
if (recv(clientSocket, buffer, BUF_SIZE, 0) == SOCKET_ERROR) {
printf("接收響應(yīng)失敗\n");
return 1;
}
printf("從服務(wù)器接收到的響應(yīng):%s\n", buffer);
}
// 關(guān)閉連接
closesocket(clientSocket);
WSACleanup();
return 0;
}
功能測(cè)試:
三、Linux下C語(yǔ)言 socket編程(云服務(wù)器)
3.1 <sys/socket.h>詳解
簡(jiǎn)單寫個(gè)tcp的吧,到這里,大家應(yīng)該都會(huì)了。
常量、結(jié)構(gòu)、函數(shù)名和windows下幾乎一樣的,
它與window的Winsock2.h不同的地方有:
- 函數(shù)命名空間,Linux下是POSIX的命名空間,windows下是windows api的命名空間;
- 參數(shù)類型和返回類型;
- 頭文件名稱不同,windows上除了頭文件還需要鏈接靜態(tài)庫(kù);而Linux下不用,它是類unix系統(tǒng)的標(biāo)準(zhǔn)頭文件;
- windows下需要使用WSAStartup函數(shù)來(lái)初始化,以及清理;Linux下不需要;
- 某些數(shù)據(jù)類型可能不同,有的是unsigned int,有的是int;
- 錯(cuò)誤處理不同,sys/socket.h使用的是errno全局變量來(lái)表示錯(cuò)誤代碼;Winsock2.h使用WSAGetLastError函數(shù)來(lái)獲取最后發(fā)生的錯(cuò)誤代碼。
- Linux下套接字也是“文件”。
3.11 常量和結(jié)構(gòu)體
sys/socket.h
定義了許多常量和數(shù)據(jù)結(jié)構(gòu),用于在套接字編程中表示和處理網(wǎng)絡(luò)地址、套接字選項(xiàng)和協(xié)議等。
絕大多部分和Winsock2.h
里面的差不多。
以下是其中一些常見的常量和數(shù)據(jù)結(jié)構(gòu)的詳細(xì)介紹:
1. 常量:
-
套接字域(domain)常量:
-
AF_UNIX
:本地域套接字(Unix域套接字)。 -
AF_INET
:IPv4套接字。 -
AF_INET6
:IPv6套接字。 -
AF_NETLINK
:Linux內(nèi)核通信套接字。
-
-
套接字類型(type)常量:
-
SOCK_STREAM
:面向連接的流套接字,提供可靠的、基于字節(jié)流的通信(如TCP)。 -
SOCK_DGRAM
:無(wú)連接的數(shù)據(jù)報(bào)套接字,提供不可靠的、基于數(shù)據(jù)報(bào)的通信(如UDP)。 -
SOCK_RAW
:原始套接字,允許直接訪問底層網(wǎng)絡(luò)協(xié)議。
-
-
套接字選項(xiàng)常量:
-
SO_REUSEADDR
:允許地址重用,可以在套接字關(guān)閉后立即重用相同的本地地址。 -
SO_BROADCAST
:允許發(fā)送廣播消息。 -
SO_KEEPALIVE
:?jiǎn)⒂锰捉幼值谋3只顒?dòng)功能,以檢測(cè)連接是否斷開。 -
SO_RCVBUF
:接收緩沖區(qū)大小的選項(xiàng)。 -
SO_SNDBUF
:發(fā)送緩沖區(qū)大小的選項(xiàng)。
-
-
協(xié)議常量:
-
IPPROTO_TCP
:TCP傳輸協(xié)議。 -
IPPROTO_UDP
:UDP傳輸協(xié)議。 -
IPPROTO_ICMP
:ICMP協(xié)議。
-
2. 數(shù)據(jù)結(jié)構(gòu):
-
struct sockaddr
:
通用的套接字地址結(jié)構(gòu)體,用于表示各種套接字域的地址信息。它的成員包括:-
sa_family
:地址族,表示套接字的域。 -
sa_data
:地址數(shù)據(jù)。
-
-
struct sockaddr_in
:
IPv4的套接字地址結(jié)構(gòu)體,用于表示IPv4地址信息。它的成員包括:-
sin_family
:地址族,通常為AF_INET
。 -
sin_port
:16位的端口號(hào)。 -
sin_addr
:IPv4地址。
-
-
struct sockaddr_in6
:
IPv6的套接字地址結(jié)構(gòu)體,用于表示IPv6地址信息。它的成員包括:-
sin6_family
:地址族,通常為AF_INET6
。 -
sin6_port
:16位的端口號(hào)。 -
sin6_addr
:IPv6地址。 -
sin6_flowinfo
:流標(biāo)識(shí)符。 -
sin6_scope_id
:范圍ID。
-
-
struct sockaddr_storage
:
通用的套接字地址存儲(chǔ)結(jié)構(gòu)體,用于存儲(chǔ)任意套接字地址信息。它的大小足夠容納任何可能的套接字地址結(jié)構(gòu)體。
常用的常量、結(jié)構(gòu)和windows下是一樣的,注意的點(diǎn)也是一樣的,這里就不重復(fù)了。
3.12 函數(shù)原型
下面是sys/socket.h
中一些常用函數(shù)的詳細(xì)介紹:
-
int socket(int domain, int type, int protocol)
:- 用途:創(chuàng)建一個(gè)套接字。
- 參數(shù):
-
domain
:套接字的域,如AF_INET
(IPv4)或AF_INET6
(IPv6)。 -
type
:套接字的類型,如SOCK_STREAM
(面向連接的流套接字)或SOCK_DGRAM
(無(wú)連接的數(shù)據(jù)報(bào)套接字)。 -
protocol
:套接字使用的協(xié)議,通常為0,表示根據(jù)域和類型自動(dòng)選擇默認(rèn)協(xié)議。
-
- 返回值:成功時(shí)返回套接字的文件描述符,失敗時(shí)返回-1。
-
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
:- 用途:將一個(gè)套接字綁定到一個(gè)地址。
- 參數(shù):
-
sockfd
:要綁定的套接字的文件描述符。 -
addr
:指向要綁定的地址結(jié)構(gòu)體的指針,可以是struct sockaddr
、struct sockaddr_in
或struct sockaddr_in6
等。 -
addrlen
:地址結(jié)構(gòu)體的長(zhǎng)度。
-
- 返回值:成功時(shí)返回0,失敗時(shí)返回-1。
-
int listen(int sockfd, int backlog)
:- 用途:開始監(jiān)聽指定套接字上的連接請(qǐng)求。
- 參數(shù):
-
sockfd
:要監(jiān)聽的套接字的文件描述符。 -
backlog
:等待連接隊(duì)列的最大長(zhǎng)度。
-
- 返回值:成功時(shí)返回0,失敗時(shí)返回-1。
-
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
:- 用途:接受一個(gè)連接請(qǐng)求,返回新的套接字文件描述符。
- 參數(shù):
-
sockfd
:監(jiān)聽套接字的文件描述符。 -
addr
:(可選)指向用于存儲(chǔ)客戶端地址信息的結(jié)構(gòu)體指針。 -
addrlen
:(可選)指向addr
結(jié)構(gòu)體長(zhǎng)度的指針。
-
- 返回值:成功時(shí)返回新的套接字文件描述符,失敗時(shí)返回-1。
-
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
:- 用途:與遠(yuǎn)程套接字建立連接。
- 參數(shù):
-
sockfd
:要連接的套接字的文件描述符。 -
addr
:指向遠(yuǎn)程地址結(jié)構(gòu)體的指針,可以是struct sockaddr
、struct sockaddr_in
或struct sockaddr_in6
等。 -
addrlen
:地址結(jié)構(gòu)體的長(zhǎng)度。
-
- 返回值:成功時(shí)返回0,失敗時(shí)返回-1。
-
ssize_t send(int sockfd, const void *buf, size_t len, int flags)
:- 用途:發(fā)送數(shù)據(jù)到套接字,TCP。
- 參數(shù):
-
sockfd
:要發(fā)送數(shù)據(jù)的套接字的文件描述符。 -
buf
:指向要發(fā)送數(shù)據(jù)的緩沖區(qū)。 -
len
:要發(fā)送的數(shù)據(jù)長(zhǎng)度。 -
flags
:附加標(biāo)志,可以是0或包含MSG_DONTWAIT
等選項(xiàng)的標(biāo)志。
-
- 返回值:成功時(shí)返回發(fā)送的字節(jié)數(shù),失敗時(shí)返回-1。
-
ssize_t recv(int sockfd, void *buf, size_t len, int flags)
:- 用途:從套接字接收數(shù)據(jù),TCP。
- 參數(shù):
-
sockfd
:要接收數(shù)據(jù)的套接字的文件描述符。 -
buf
:用于接收數(shù)據(jù)的緩沖區(qū)。 -
len
:接收數(shù)據(jù)的緩沖區(qū)長(zhǎng)度。 -
flags
:附加標(biāo)志,可以是0或包含MSG_DONTWAIT
等選項(xiàng)的標(biāo)志。
-
- 返回值:成功時(shí)返回接收的字節(jié)數(shù),失敗時(shí)返回-1。
-
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen)
:- 用途:向指定的目標(biāo)地址發(fā)送數(shù)據(jù)報(bào),UDP。
- 參數(shù):
-
sockfd
:要發(fā)送數(shù)據(jù)的套接字的文件描述符。 -
buf
:指向要發(fā)送的數(shù)據(jù)的緩沖區(qū)。 -
len
:要發(fā)送的數(shù)據(jù)的長(zhǎng)度。 -
flags
:附加標(biāo)志,可以是0或包含MSG_DONTWAIT
等選項(xiàng)的標(biāo)志。 -
dest_addr
:指向目標(biāo)地址的結(jié)構(gòu)體指針,可以是struct sockaddr
、struct sockaddr_in
或struct sockaddr_in6
等。 -
addrlen
:目標(biāo)地址結(jié)構(gòu)體的長(zhǎng)度。
-
- 返回值:成功時(shí)返回發(fā)送的字節(jié)數(shù),失敗時(shí)返回-1。
-
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen)
:- 用途:從套接字接收數(shù)據(jù)報(bào),并獲取發(fā)送方的地址,UDP。
- 參數(shù):
-
sockfd
:要接收數(shù)據(jù)的套接字的文件描述符。 -
buf
:用于接收數(shù)據(jù)的緩沖區(qū)。 -
len
:接收數(shù)據(jù)的緩沖區(qū)長(zhǎng)度。 -
flags
:附加標(biāo)志,可以是0或包含MSG_DONTWAIT
等選項(xiàng)的標(biāo)志。 -
src_addr
:(可選)指向用于存儲(chǔ)發(fā)送方地址信息的結(jié)構(gòu)體指針。 -
addrlen
:(可選)指向src_addr
結(jié)構(gòu)體長(zhǎng)度的指針。
-
- 返回值:成功時(shí)返回接收的字節(jié)數(shù),失敗時(shí)返回-1。
-
int close(int sockfd)
:- 用途:關(guān)閉套接字。
- 參數(shù):要關(guān)閉的套接字的文件描述符。
- 返回值:成功時(shí)返回0,失敗時(shí)返回-1。
3.2 <arpa/inet.h> 介紹
前面的結(jié)構(gòu)體中,ip地址、端口號(hào)這些通常都是整數(shù)類型,但我們輸入的ip地址一般是點(diǎn)分十進(jìn)制的字符串,需要進(jìn)行轉(zhuǎn)換。這就用到了<arpa/inet.h> 中的一些函數(shù);前面windows的忘了講了,都差不多,它定義在Winsock.h中的。
arpa:最早的分組交換網(wǎng)絡(luò),arpa也是一個(gè)mac地址和ip地址轉(zhuǎn)換的協(xié)議;
inet:Internet。
下面是arpa/inet.h
頭文件中提供的函數(shù)的函數(shù)原型、參數(shù)和返回值的詳細(xì)介紹:
-
inet_addr
:將點(diǎn)分十進(jìn)制ip地址轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序的32位整數(shù)類型(in_addr_t)- 函數(shù)原型:
in_addr_t inet_addr(const char *cp);
- 參數(shù):
cp
是一個(gè)指向以空字符結(jié)尾的字符串,表示點(diǎn)分十進(jìn)制的IP地址。 - 返回值:如果轉(zhuǎn)換成功,返回網(wǎng)絡(luò)字節(jié)序的32位整數(shù)形式的IP地址;如果轉(zhuǎn)換失敗,返回
INADDR_NONE
(通常是 -1)表示錯(cuò)誤。
- 函數(shù)原型:
-
inet_ntoa
:功能和前面的那個(gè)相反- 函數(shù)原型:
char *inet_ntoa(struct in_addr in);
- 參數(shù):
in
是一個(gè)struct in_addr
結(jié)構(gòu),表示網(wǎng)絡(luò)字節(jié)序的32位整數(shù)形式的IP地址。 - 返回值:返回一個(gè)指向靜態(tài)緩沖區(qū)的指針,其中包含轉(zhuǎn)換后的點(diǎn)分十進(jìn)制形式的IP地址。需要注意的是,后續(xù)的調(diào)用會(huì)覆蓋該緩沖區(qū),因此應(yīng)盡快使用轉(zhuǎn)換后的字符串。
- 函數(shù)原型:
-
inet_ntop
:將網(wǎng)絡(luò)字節(jié)序的ipv4、6地址(整數(shù))轉(zhuǎn)換為點(diǎn)分十(十六)進(jìn)制的字符串- 函數(shù)原型:
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
- 參數(shù):
af
是地址族,可以是AF_INET
(IPv4)或AF_INET6
(IPv6);src
是一個(gè)指向源地址的指針,可以是struct in_addr
或struct in6_addr
;dst
是一個(gè)指向目標(biāo)字符串緩沖區(qū)的指針;size
是目標(biāo)緩沖區(qū)的大小。 - 返回值:如果轉(zhuǎn)換成功,返回指向目標(biāo)字符串的指針;如果發(fā)生錯(cuò)誤,返回
NULL
,并設(shè)置errno
表示錯(cuò)誤原因。
- 函數(shù)原型:
-
inet_pton
:轉(zhuǎn)換成2進(jìn)制- 函數(shù)原型:
int inet_pton(int af, const char *src, void *dst);
- 參數(shù):
af
是地址族,可以是AF_INET
(IPv4)或AF_INET6
(IPv6);src
是一個(gè)指向表示IP地址的字符串的指針;dst
是一個(gè)指向目標(biāo)地址的指針,可以是struct in_addr
或struct in6_addr
。 - 返回值:如果轉(zhuǎn)換成功,返回 1;如果轉(zhuǎn)換失敗,返回 0,并且
errno
表示錯(cuò)誤原因。
- 函數(shù)原型:
-
htonl
:主機(jī)字節(jié)序和網(wǎng)絡(luò)字節(jié)序的轉(zhuǎn)換- 函數(shù)原型:
uint32_t htonl(uint32_t hostlong);
- 參數(shù):
hostlong
是主機(jī)字節(jié)序的32位整數(shù)值。 - 返回值:返回網(wǎng)絡(luò)字節(jié)序的32位整數(shù)值。
- 函數(shù)原型:
-
htons
:主機(jī)字節(jié)序和網(wǎng)絡(luò)字節(jié)序的轉(zhuǎn)換- 函數(shù)原型:
uint16_t htons(uint16_t hostshort);
- 參數(shù):
hostshort
是主機(jī)字節(jié)序的16位整數(shù)值。 - 返回值:返回網(wǎng)絡(luò)字節(jié)序的16位整數(shù)值。
- 函數(shù)原型:
-
ntohl
:主機(jī)字節(jié)序和網(wǎng)絡(luò)字節(jié)序的轉(zhuǎn)換- 函數(shù)原型:
uint32_t ntohl (uint32_t netlong);
- 參數(shù):
netlong
是網(wǎng)絡(luò)字節(jié)序的32位整數(shù)值。 - 返回值:返回主機(jī)字節(jié)序的32位整數(shù)值。
- 函數(shù)原型:
-
ntohs
:主機(jī)字節(jié)序和網(wǎng)絡(luò)字節(jié)序的轉(zhuǎn)換- 函數(shù)原型:
uint16_t ntohs(uint16_t netshort);
- 參數(shù):
netshort
是網(wǎng)絡(luò)字節(jié)序的16位整數(shù)值。 - 返回值:返回主機(jī)字節(jié)序的16位整數(shù)值。
- 函數(shù)原型:
3.3 Linux TCP 套接字編程
3.31 IP地址問題
在windows那一節(jié)我已經(jīng)說(shuō)了,只有在局域網(wǎng)內(nèi)能相互通信,或者擁有公網(wǎng)ip。
對(duì)于云服務(wù)器,服務(wù)器廠商會(huì)給你一個(gè)ip地址,你可以用它來(lái)ssh登錄之類的。但是要注意,它給你的地址可能不是你私有的,而是通過內(nèi)網(wǎng)ip地址映射到公網(wǎng)ip的。
你用ifconfig -a看一下ether0網(wǎng)卡的ip地址和你登錄的ip地址是否一致就可以判斷了。
我兩個(gè)服務(wù)器,一個(gè)是地址映射的(騰訊云),另一個(gè)是我獨(dú)占的公網(wǎng)ip。
第二種情況,你可以直接綁定ip地址給套接字;第一種情況就不能直接用那個(gè)公網(wǎng)ip了.
你可以使用 0.0.0.0
或者 INADDR_ANY
。
另外,記得把相應(yīng)的端口放行。
3.32 服務(wù)端代碼和測(cè)試
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h> // linux下socket頭文件
#include <arpa/inet.h> // ip地址轉(zhuǎn)換、字節(jié)序轉(zhuǎn)換
#define ip INADDR_ANY // 主機(jī)ip地址,表示監(jiān)聽主機(jī)所有網(wǎng)卡
//#define ip "0.0.0.0"
#define port 8087 // 端口號(hào)
#define BUF_SIZE 1024 //緩沖區(qū)大小
int main() {
int server_socket, client_socket;
struct sockaddr_in server_address, client_address;
char buffer[BUF_SIZE]; //緩沖區(qū)
// 創(chuàng)建套接字
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
printf("無(wú)法創(chuàng)建套接字\n");
return -1;
}
// 設(shè)置服務(wù)器地址和端口
memset(&server_address,0,sizeof(server_address));
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = ip;
//server_address.sin_addr.s_addr=inet_addr(ip);
server_address.sin_port = htons(port);
// 綁定套接字到指定的地址和端口
if (bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address)) < 0) {
printf("綁定失敗\n");
return -1;
}
// 監(jiān)聽傳入的連接
if (listen(server_socket, 1) < 0) {
printf("監(jiān)聽失敗\n");
return -1;
}
printf("服務(wù)器正在監(jiān)聽端口 %d\n", port);
// 接受傳入的連接
socklen_t client_address_length = sizeof(client_address); //注意這里的長(zhǎng)度的類型是:socklen_t
client_socket = accept(server_socket, (struct sockaddr *)&client_address, &client_address_length);
if (client_socket < 0) {
printf("接受連接失敗\n");
return -1;
}
printf("與客戶端建立連接成功\n");
// 循環(huán)接受客戶端請(qǐng)求,收到exit時(shí)關(guān)閉套接字
while(1){
memset(buffer, 0, sizeof(buffer));
ssize_t recvLen=recv(client_socket,buffer ,sizeof(buffer), 0);
if(recvLen<0){
puts("接收數(shù)據(jù)失敗");
return -1;
}
buffer[recvLen]='\0';
printf("從客戶端接收到的數(shù)據(jù):%s\n",buffer);
if(strcmp(buffer,"exit")==0) break;
// 將收到的數(shù)據(jù)回送給客戶端
if(send(client_socket,buffer,sizeof(buffer),0)<0){
puts("發(fā)送響應(yīng)失敗");
return -1;
}
}
// 關(guān)閉連接
close(client_socket);
close(server_socket);
return 0;
}
直接用的手機(jī)app來(lái)連接測(cè)試吧,都一樣的。
服務(wù)器:
手機(jī) app(socketdebugtools):
3.33 客戶端代碼和測(cè)試
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define ip "198.52.xx.xxx"
#define port 8087
#define BUF_SIZE 1024
int main() {
int client_socket;
struct sockaddr_in server_address;
char buffer[BUF_SIZE];
// 創(chuàng)建套接字
client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket == -1) {
printf("無(wú)法創(chuàng)建套接字\n");
return -1;
}
// 設(shè)置服務(wù)器地址和端口
memset(&server_address,0,sizeof(server_address));
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr=inet_addr(ip);
server_address.sin_port = htons(port);
// 連接服務(wù)器
if (connect(client_socket, (struct sockaddr *)&server_address, sizeof(server_address))<0){
puts("與服務(wù)器建立連接失敗");
return -1;
}
puts("與服務(wù)器建立連接成功");
while(1){
memset(buffer, 0, BUF_SIZE);
printf("輸入要發(fā)送的數(shù)據(jù)(exit關(guān)閉兩側(cè)鏈接):");
fgets(buffer,BUF_SIZE,stdin);
buffer[strlen(buffer)-1]='\0';
if(send(client_socket,buffer,strlen(buffer),0)<0){
puts("發(fā)生數(shù)據(jù)失敗");
return -1;
}
if(strcmp(buffer, "exit")==0) break;
memset(buffer, 0, BUF_SIZE);
if(recv(client_socket,buffer,sizeof(buffer),0)<0){
puts("從服務(wù)器接收響應(yīng)失敗");
return -1;
}
printf("從服務(wù)器接收響應(yīng)為:%s\n",buffer);
}
// 關(guān)閉連接
close((int)client_socket);
return 0;
}
不在一個(gè)局域網(wǎng),手機(jī)不方便做服務(wù)端了(路由器端口轉(zhuǎn)發(fā)也不行,路由器ip也不是公網(wǎng)的)。
我另一個(gè)服務(wù)器有公網(wǎng)ip,用它做服務(wù)端(當(dāng)然都運(yùn)行在一臺(tái)服務(wù)器上也行的)。
服務(wù)端:
客戶端:
四、Python socket
4.1 socket 庫(kù) 詳解
socket
庫(kù)是Python標(biāo)準(zhǔn)庫(kù)的一部分,它提供了創(chuàng)建、連接和通信套接字的功能,使得開發(fā)網(wǎng)絡(luò)應(yīng)用程序變得簡(jiǎn)單和方便。以下是socket
庫(kù)中一些常用函數(shù)和類的詳細(xì)介紹:
函數(shù):
-
socket.socket(family, type, proto=0)
:創(chuàng)建一個(gè)新的套接字對(duì)象。參數(shù)family
指定地址族(如socket.AF_INET
表示IPv4),type
指定套接字類型(如socket.SOCK_STREAM
表示TCP套接字),proto
指定協(xié)議。返回套接字對(duì)象。 -
socket.gethostname()
:獲取當(dāng)前主機(jī)的主機(jī)名。 -
socket.gethostbyname(hostname)
:根據(jù)主機(jī)名獲取主機(jī)的IP地址。
套接字方法和屬性:
-
socket.bind(address)
:將套接字綁定到指定的地址和端口。參數(shù)address
是一個(gè)元組,包含IP地址和端口號(hào)。 -
socket.listen(backlog)
:開始監(jiān)聽傳入的連接。參數(shù)backlog
指定掛起連接隊(duì)列的最大長(zhǎng)度。 -
socket.accept()
:接受傳入的連接,并返回一個(gè)新的套接字對(duì)象和客戶端地址。 -
socket.connect(address)
:連接到指定的服務(wù)器地址和端口。參數(shù)address
是一個(gè)元組,包含IP地址和端口號(hào)。 -
socket.send(data)
:將數(shù)據(jù)發(fā)送到已連接的套接字。參數(shù)data
是要發(fā)送的字節(jié)流數(shù)據(jù)。 -
socket.recv(bufsize)
:從套接字接收數(shù)據(jù)。參數(shù)bufsize
指定每次最多接收的字節(jié)數(shù)。 -
socket.close()
:關(guān)閉套接字連接。
除了上述方法和屬性,socket
對(duì)象還具有其他一些方法和屬性,用于設(shè)置套接字的選項(xiàng)、獲取有關(guān)套接字的信息等。
這只是socket
庫(kù)的一些基本功能。根據(jù)需要,你還可以使用socket
庫(kù)提供的其他函數(shù)和類來(lái)實(shí)現(xiàn)更復(fù)雜的網(wǎng)絡(luò)應(yīng)用程序,如設(shè)置套接字選項(xiàng)、使用多線程或異步操作處理多個(gè)連接等。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-480727.html
4.2 服務(wù)端
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time : 2023-6-2 上午 1:23
# @Author : 666
# @FileName: server_tcp
# @Software: PyCharm
# @Abstract : tcp服務(wù)端
import socket
# 定義主機(jī)和端口號(hào)
host = '127.0.0.1'
port = 8080
# 創(chuàng)建套接字對(duì)象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 將套接字綁定到指定的主機(jī)和端口
server_socket.bind((host, port))
# 開始監(jiān)聽傳入的連接
server_socket.listen(1)
print("服務(wù)器正在監(jiān)聽端口 {}:{}".format(host, port))
# 接受傳入的連接
client_socket, address = server_socket.accept()
print("與客戶端建立連接:{}".format(address))
# 接收來(lái)自客戶端的數(shù)據(jù)
data = client_socket.recv(1024).decode('utf-8')
print("從客戶端接收到的數(shù)據(jù):", data)
# 發(fā)送響應(yīng)給客戶端
response = "服務(wù)器已接收到數(shù)據(jù):{}".format(data)
client_socket.sendall(response.encode('utf-8'))
# 關(guān)閉連接
client_socket.close()
server_socket.close()
4.3 客戶端
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time : 2023-6-2 上午 1:24
# @Author : 666
# @FileName: client_tcp
# @Software: PyCharm
# @Abstract : tcp客戶端
import socket
# 定義主機(jī)和端口號(hào)
host = '127.0.0.1'
port = 8080
# 創(chuàng)建套接字對(duì)象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 連接服務(wù)器
client_socket.connect((host, port))
print("與服務(wù)器建立連接:{}:{}".format(host, port))
# 發(fā)送數(shù)據(jù)給服務(wù)器
data = "Hello, Server!"
client_socket.sendall(data.encode('utf-8'))
# 接收服務(wù)器的響應(yīng)
response = client_socket.recv(1024).decode('utf-8')
print("從服務(wù)器接收到的響應(yīng):", response)
# 關(guān)閉連接
client_socket.close()
把 永 遠(yuǎn) 愛 你 寫 進(jìn) 詩(shī) 的 結(jié) 尾 ~ 文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-480727.html
到了這里,關(guān)于【socket】從計(jì)算機(jī)網(wǎng)絡(luò)基礎(chǔ)到socket編程——Windows && Linux C語(yǔ)言 + Python實(shí)現(xiàn)(TCP+UDP)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!