對(duì)于網(wǎng)絡(luò)編程,我們也言必稱 TCP/IP ,似乎其它網(wǎng)絡(luò)協(xié)議已經(jīng)不存在了。對(duì)于 TCP/IP ,我們還知道 TCP 和 UDP ,前者可以保證數(shù)據(jù)的正確和可靠性,后者則允許數(shù)據(jù)丟失。最后,我們還知道,在建立連接前,必須知道對(duì)方的 IP 地址和端口號(hào)。除此,普通的程序員就不會(huì)知道太多了,很多時(shí)候這些知識(shí)已經(jīng)夠用了。最多,寫(xiě)服務(wù)程序的時(shí)候,會(huì)使用多線程來(lái)處理并發(fā)訪問(wèn)。
創(chuàng)新互聯(lián)主營(yíng)靖江網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營(yíng)網(wǎng)站建設(shè)方案,成都App制作,靖江h(huán)5微信小程序搭建,靖江網(wǎng)站營(yíng)銷(xiāo)推廣歡迎靖江等地區(qū)企業(yè)咨詢我們還知道如下幾個(gè)事實(shí):
1 。一個(gè)指定的端口號(hào)不能被多個(gè)程序共用。比如,如果 IIS 占用了 80 端口,那么 Apache 就不能也用 80 端口了。
2 。很多防火墻只允許特定目標(biāo)端口的數(shù)據(jù)包通過(guò)。
3 。服務(wù)程序在 listen 某個(gè)端口并 accept 某個(gè)連接請(qǐng)求后,會(huì)生成一個(gè)新的 socket 來(lái)對(duì)該請(qǐng)求進(jìn)行處理。
于是,一個(gè)困惑了我很久的問(wèn)題就產(chǎn)生了。如果一個(gè) socket 創(chuàng)建后并與 80 端口綁定后,是否就意味著該 socket 占用了 80 端口呢?如果是這樣的,那么當(dāng)其 accept 一個(gè)請(qǐng)求后,生成的新的 socket 到底使用的是什么端口呢(我一直以為系統(tǒng)會(huì)默認(rèn)給其分配一個(gè)空閑的端口號(hào))?如果是一個(gè)空閑的端口,那一定不是 80 端口了,于是以后的 TCP 數(shù)據(jù)包的目標(biāo)端口就不是 80 了 -- 防火墻一定會(huì)組織其通過(guò)的!實(shí)際上,我們可以看到,防火墻并沒(méi)有阻止這樣的連接,而且這是最常見(jiàn)的連接請(qǐng)求和處理方式。我的不解就是,為什么防火墻沒(méi)有阻止這樣的連接?它是如何判定那條連接是因?yàn)?connet80 端口而生成的?是不是 TCP 數(shù)據(jù)包里有什么特別的標(biāo)志?或者防火墻記住了什么東西?
后來(lái),我又仔細(xì)研讀了 TCP/IP 的協(xié)議棧的原理,對(duì)很多概念有了更深刻的認(rèn)識(shí)。比如,在 TCP 和 UDP 同屬于傳輸層,共同架設(shè)在 IP 層(網(wǎng)絡(luò)層)之上。而 IP 層主要負(fù)責(zé)的是在節(jié)點(diǎn)之間( End to End )的數(shù)據(jù)包傳送,這里的節(jié)點(diǎn)是一臺(tái)網(wǎng)絡(luò)設(shè)備,比如計(jì)算機(jī)。因?yàn)?IP 層只負(fù)責(zé)把數(shù)據(jù)送到節(jié)點(diǎn),而不能區(qū)分上面的不同應(yīng)用,所以 TCP 和 UDP 協(xié)議在其基礎(chǔ)上加入了端口的信息,端口于是標(biāo)識(shí)的是一個(gè)節(jié)點(diǎn)上的一個(gè)應(yīng)用。除了增加端口信息, UPD 協(xié)議基本就沒(méi)有對(duì) IP 層的數(shù)據(jù)進(jìn)行任何的處理了。而 TCP 協(xié)議還加入了更加復(fù)雜的傳輸控制,比如滑動(dòng)的數(shù)據(jù)發(fā)送窗口( Slice Window ),以及接收確認(rèn)和重發(fā)機(jī)制,以達(dá)到數(shù)據(jù)的可靠傳送。不管應(yīng)用層看到的是怎樣一個(gè)穩(wěn)定的 TCP 數(shù)據(jù)流,下面?zhèn)魉偷亩际且粋€(gè)個(gè)的 IP 數(shù)據(jù)包,需要由 TCP 協(xié)議來(lái)進(jìn)行數(shù)據(jù)重組。
所以,我有理由懷疑,防火墻并沒(méi)有足夠的信息判斷 TCP 數(shù)據(jù)包的更多信息,除了 IP 地址和端口號(hào)。而且,我們也看到,所謂的端口,是為了區(qū)分不同的應(yīng)用的,以在不同的 IP 包來(lái)到的時(shí)候能夠正確轉(zhuǎn)發(fā)。
TCP/IP 只是一個(gè)協(xié)議棧,就像操作系統(tǒng)的運(yùn)行機(jī)制一樣,必須要具體實(shí)現(xiàn),同時(shí)還要提供對(duì)外的操作接口。就像操作系統(tǒng)會(huì)提供標(biāo)準(zhǔn)的編程接口,比如 Win32 編程接口一樣, TCP/IP 也必須對(duì)外提供編程接口,這就是 Socket 編程接口 -- 原來(lái)是這么回事?。?
在 Socket 編程接口里,設(shè)計(jì)者提出了一個(gè)很重要的概念,那就是 socket 。這個(gè) socket 跟文件句柄很相似,實(shí)際上在 BSD 系統(tǒng)里就是跟文件句柄一樣存放在一樣的進(jìn)程句柄表里。這個(gè) socket 其實(shí)是一個(gè)序號(hào),表示其在句柄表中的位置。這一點(diǎn),我們已經(jīng)見(jiàn)過(guò)很多了,比如文件句柄,窗口句柄等等。這些句柄,其實(shí)是代表了系統(tǒng)中的某些特定的對(duì)象,用于在各種函數(shù)中作為參數(shù)傳入,以對(duì)特定的對(duì)象進(jìn)行操作 -- 這其實(shí)是 C 語(yǔ)言的問(wèn)題,在 C++ 語(yǔ)言里,這個(gè)句柄其實(shí)就是 this 指針,實(shí)際就是對(duì)象指針啦。
現(xiàn)在我們知道, socket 跟 TCP/IP 并沒(méi)有必然的聯(lián)系。 Socket 編程接口在設(shè)計(jì)的時(shí)候,就希望也能適應(yīng)其他的網(wǎng)絡(luò)協(xié)議。所以, socket 的出現(xiàn)只是可以更方便的使用 TCP/IP 協(xié)議棧而已,其對(duì) TCP/IP 進(jìn)行了抽象,形成了幾個(gè)最基本的函數(shù)接口。比如 create , listen , accept , connect , read 和 write 等等。
現(xiàn)在我們明白,如果一個(gè)程序創(chuàng)建了一個(gè) socket ,并讓其監(jiān)聽(tīng) 80 端口,其實(shí)是向 TCP/IP 協(xié)議棧聲明了其對(duì) 80 端口的占有。以后,所有目標(biāo)是 80 端口的 TCP 數(shù)據(jù)包都會(huì)轉(zhuǎn)發(fā)給該程序(這里的程序,因?yàn)槭褂玫氖?Socket 編程接口,所以首先由 Socket 層來(lái)處理)。所謂 accept 函數(shù),其實(shí)抽象的是 TCP 的連接建立過(guò)程。 accept 函數(shù)返回的新 socket 其實(shí)指代的是本次創(chuàng)建的連接,而一個(gè)連接是包括兩部分信息的,一個(gè)是源 IP 和源端口,另一個(gè)是宿 IP 和宿端口。所以, accept 可以產(chǎn)生多個(gè)不同的 socket ,而這些 socket 里包含的宿 IP 和宿端口是不變的,變化的只是源 IP 和源端口。這樣的話,這些 socket 宿端口就可以都是 80 ,而 Socket 層還是能根據(jù)源 / 宿對(duì)來(lái)準(zhǔn)確地分辨出 IP 包和 socket 的歸屬關(guān)系,從而完成對(duì) TCP/IP 協(xié)議的操作封裝!而同時(shí),放火墻的對(duì) IP 包的處理規(guī)則也是清晰明了,不存在前面設(shè)想的種種復(fù)雜的情形。
明白 socket 只是對(duì) TCP/IP 協(xié)議棧操作的抽象,而不是簡(jiǎn)單的映射關(guān)系,這很重要!
一個(gè)socket 由四個(gè)標(biāo)識(shí)決定,客戶端ip,客戶端port, 服務(wù)端ip,服務(wù)端port,
在一個(gè)客戶端和服務(wù)端通信過(guò)程中,客戶端ip, 服務(wù)端ip,服務(wù)端port,都是相同的,只有客戶端port不同,但是只要一個(gè)不同,就算是一個(gè)新的socket,
所以accept 返回的是一個(gè)新的socket。