網(wǎng)絡(luò)編程
計算機之間真正進行通信的是網(wǎng)絡(luò)應(yīng)用程序。那么我們發(fā)送給一個IP 地址主機的數(shù)據(jù)應(yīng)該由哪一個程序來接收呢,為了標示在計算機之間進行網(wǎng)絡(luò)通信的程序,給某一個程序分配一個端口號,那么在發(fā)送時候,除了指定IP
計算機之間真正進行通信的是網(wǎng)絡(luò)應(yīng)用程序。那么我們發(fā)送給一個IP 地址主機的數(shù)據(jù)應(yīng)該由哪一個程序來接收呢,為了標示在計算機之間進行網(wǎng)絡(luò)通信的程序,給某一個程序分配一個端口號,那么在發(fā)送時候,除了指定IP 地址外,同時指定發(fā)送到哪一個端口,這樣,在指定IP 地址的計算機上,就會有在這個端口上等待數(shù)據(jù)的網(wǎng)絡(luò)應(yīng)用程序去接收數(shù)據(jù),網(wǎng)絡(luò)通信和打電話是類似的,IP 地址就好像一個公司的總機的電話號碼,端口就好像總機的分機號,我們發(fā)送的數(shù)據(jù)到了總機之后,相應(yīng)的要轉(zhuǎn)到分機上。
關(guān)于IP 地址:一,IP 網(wǎng)絡(luò)中每臺主機都必須有一個惟一的IP 地址;二,IP 地址是一個邏輯地址;三,因特網(wǎng)上的IP 地址具有全球唯一性;四,IP 地址是由32位,4個字節(jié)來表示的,常用點分十進制的格式表示。
協(xié)議
為進行網(wǎng)絡(luò)中的數(shù)據(jù)交換(通信)而建立的規(guī)則、標準或約定。(—=語義 語法 規(guī)則);不同層具有各自不同的協(xié)議。程序員應(yīng)該掌握的網(wǎng)絡(luò)基本知識:
多種通信媒介——有線、無線、、、、、;
不同種類的設(shè)備——通用、專用。。。。
不同的操作系統(tǒng)——Unix,Windows ??
不同的應(yīng)用環(huán)境——固定,移動。。。。。
不同業(yè)務(wù)種類——分時,交互,實時。。。。。
寶貴的投資和積累——有形、無形。。。。
用戶業(yè)務(wù)的延續(xù)性---不允許出現(xiàn)大的跌宕起伏。
它們互相交織,形成了非常復(fù)雜的系統(tǒng)應(yīng)用環(huán)境。
網(wǎng)絡(luò)異質(zhì)性問題的解決
網(wǎng)絡(luò)體系結(jié)構(gòu)就是使這些用不同媒介連接起來的不同設(shè)備和網(wǎng)絡(luò)系統(tǒng)在不同的應(yīng)用環(huán)境下實現(xiàn)互操作性,并滿足各種業(yè)務(wù)需求的一種粘合劑,它營造了一種生存空間——任何廠商的任何產(chǎn)品、以及任何技術(shù)只要遵守這個空間的行為規(guī)則,就能夠在其中生存并發(fā)展。網(wǎng)絡(luò)體系結(jié)構(gòu)解決異質(zhì)性問題采用的是分層方法——把復(fù)雜的網(wǎng)絡(luò)問題劃分為若干個較小、單一的問題,在不同層上予以解決。就像我們在編程時百問題分解為很多小的模塊來解決一樣。 ISO/OSI七層參考模型
ISO 國際標準化組織,提出了OSI(Open System Interconnection)七層參考模型,OSI 將網(wǎng)絡(luò)的不同功能劃分為7層. 從下往上依次是物理層,第一層是提供二進制傳輸,確定如何在通訊信道上傳輸比特流,第二層是數(shù)據(jù)鏈路層,提供介質(zhì)訪問,加強物理層的傳輸功能,建立一條無差錯的傳輸線路,第三層是網(wǎng)絡(luò)層,提供IP 尋址和路由,因為在網(wǎng)絡(luò)上數(shù)據(jù)到達一個目的地可以有多條線路,網(wǎng)絡(luò)層
就負責(zé)找出最佳傳輸線路。第四層是傳輸層,傳輸層為源端主機和到目的端主機提供可靠的數(shù)據(jù)傳輸服務(wù),隔離網(wǎng)絡(luò)的上下層協(xié)議,使得網(wǎng)絡(luò)應(yīng)用與下層協(xié)議無關(guān),第五層是會話層,會話層在兩個相互通信的應(yīng)用進程之間建立組織和協(xié)調(diào)相互之間通信,第六次是表示層,表示層處理被傳輸數(shù)據(jù)的表示問題,即信息的語法和語義,如有必要,使用一種通用的數(shù)據(jù)表示格式,在多種數(shù)據(jù)表示格式之間,進行轉(zhuǎn)換,例如在貨幣,日期,數(shù)值,等本地數(shù)據(jù)表示格式與標準數(shù)據(jù)表示格式之間進行轉(zhuǎn)換,還有數(shù)據(jù)的加解密,壓縮,解壓縮,等。第七層是應(yīng)用層為用戶的應(yīng)用程序提供網(wǎng)絡(luò)通信服務(wù)。要注意的是:OSI 參考模型并不是物理實體上存在這七層,這只是功能上的劃分,是抽象的參考模型,在我們進行一次網(wǎng)絡(luò)通信的時候,每一層負責(zé)為我們這次通信提供本層的功能。通信實體的對等層不允許直接通信。 我們一個通訊實體和另外一個實體進行通信,在他們的對等層之間不允許直接進行通信。各層之間是嚴格單向依賴。上層使用下層提供的服務(wù)—Service user 即服務(wù)的使用者; 下層向上層提供服務(wù)-Service provider. 作為服務(wù)的提供者。對等層實體之間虛擬通信,下層向上層提供
,服務(wù),實際通信在最底層完成。在一個實體和另外一個實體進行通信時候,應(yīng)用層發(fā)送的數(shù)據(jù)經(jīng)過表示層,會話層 ,傳輸層,最后到達物理層,物理層將數(shù)據(jù)傳輸?shù)搅硗庖粋€實體的物理層,然后數(shù)據(jù)以此向上傳遞,最終到達應(yīng)用層。這就是兩個通信實體在通訊時候數(shù)據(jù)進行傳輸?shù)倪^程。對等層實體之間是一個虛擬的通信。下層向上層提供服務(wù),實際通信是在最底層完成的。
OSI各層所使用的協(xié)議
應(yīng)用層:遠程登錄協(xié)議Telnet, 文件傳輸協(xié)議FTP ,超文本傳輸協(xié)議HTTP, 域名服務(wù)DNS, 簡單郵件傳輸協(xié)議SMTP, 郵局協(xié)議POP3等。我們在網(wǎng)上下載一個軟件就使用FTP 協(xié)議,HTTP 是使用的比較多的協(xié)議,我們在上網(wǎng)的時候,通過瀏覽器訪問一個網(wǎng)頁,就會使用超文本傳輸協(xié)議,DNS 也是使用的比較多的協(xié)議,我們通過網(wǎng)絡(luò)訪問一臺主機時候,我們很少直接輸入他的IP 地址,經(jīng)常會使用這臺主機的域名,通過DNS, 就可以將域名解析為他所對應(yīng)的IP 地址,通過IP 就可以訪問到對應(yīng)的主機。我們通過Mail 發(fā)送郵件的時候,就會使用SMTP 協(xié)議,我們利用Mail 從263信箱中收取郵件的時候,就使用POP 協(xié)議。
傳輸層:傳輸控制協(xié)議TCP, 用戶數(shù)據(jù)報協(xié)議UDP.
TCP:面向連接的可靠的傳輸協(xié)議。我們在利用TCP 協(xié)議進行通信的時候,首先經(jīng)過三步握手,建立起通信雙方的連接,一旦連接建立起,就可以進行通信了,TCP 提供了數(shù)據(jù)確認和數(shù)據(jù)重傳的機制,保證了發(fā)送的數(shù)據(jù)一定能夠到達通信的對方。這就像我們使用電話通信一樣,首先撥打電話號碼,建立一個連接,一旦電話撥通,連接建立之后,我們所說的每一句話都能傳輸?shù)脚c之通信的對方。
UDP:是無連接的,不可靠的傳輸協(xié)議。采用該協(xié)議進行通信時候,不需要建立連接,我們可以直接向一個IP 地址發(fā)送數(shù)據(jù),至于這個數(shù)據(jù)對方能否收到,不能保證了,我們知道在網(wǎng)絡(luò)上傳輸?shù)氖请娦盘?,既然是電信號,在傳輸?shù)臅r候就會有衰竭,所以數(shù)據(jù)可能在網(wǎng)絡(luò)上消失了,也有可能制定的IP 地址還沒有分配,或者說具有這個IP 地址的主機還沒有運行,都有可能導(dǎo)致發(fā)送的數(shù)據(jù)接收不到,和遞信一樣,信件有可能在運送途中丟失,也有可能收信的人搬家了,都有導(dǎo)致信件的丟失,我們在遞信的時候不需要和對方認識,也就是不需要建立連接。既然該協(xié)議有這么多缺點,為什么還要用呢,主要就是該協(xié)議不需要建立連接,而且沒有數(shù)據(jù)確認和數(shù)據(jù)重傳的機制,所以實時性較高,在一些實時性要求較高的場合,例如視頻會議,視頻點播,就可以采用該協(xié)議來實現(xiàn),因為對于這些數(shù)據(jù)來說,丟失少量的數(shù)據(jù),捕獲影響我們觀看視頻,而在數(shù)據(jù)完整性要求較高的場合,就可以采用TCP 協(xié)議,例如從網(wǎng)絡(luò)上下載一個安裝程序,如果丟失數(shù)據(jù),安裝程序無法應(yīng)用。
網(wǎng)絡(luò)層:網(wǎng)際協(xié)議IP,Internet 互聯(lián)網(wǎng)控制報文協(xié)議ICMP,Internet 組管理協(xié)議IGMP. 數(shù)據(jù)封裝
一臺計算機要發(fā)送數(shù)據(jù)到另一臺計算機,數(shù)據(jù)首先必須打包,打包的過程成為封裝。封裝就是在數(shù)據(jù)前面加上特定的協(xié)議頭部。比如利用TCP 協(xié)議進行通信,當數(shù)據(jù)到達傳輸層的時候,當數(shù)據(jù)到達傳輸層時候要加上HTTP 頭,當數(shù)據(jù)到達網(wǎng)絡(luò)層時候,在數(shù)據(jù)前面加上IP 頭。 OSI 參考模型中,對等層協(xié)議之間交換的信息單元統(tǒng)稱為協(xié)議數(shù)據(jù)單元(PDU,Protocol Data Unit ). 為了提供服務(wù),下層把上層的PDU 作為本層的數(shù)據(jù)封裝,然后加入本層的頭部(和尾部)。頭部中含有完成數(shù)據(jù)傳輸所需的控制信息。這樣,數(shù)據(jù)自上而下遞交的過程實際上就是不斷封裝的過程。到達目的地后自下而上遞交的過程就是不斷拆封的過程,由此可知,在物理線路上傳輸?shù)臄?shù)據(jù),其外面實際上被包封了多層“信封”。但是,某一層只能識別由對等層封裝的“信封”,而對于被封裝在“信封”內(nèi)部的數(shù)據(jù)僅僅是拆封后將其提交給上層,本層不做任何處理。
TCP/IP模型
TCP/IP起源于美國國防部高級研究規(guī)劃署(DARPA )的一項研究計劃——實現(xiàn)若干臺主機的
,相互通信?,F(xiàn)在TCP/IP已成為Internet 上通信的工業(yè)標準。TCP/IP模型包括4個層次。應(yīng)用層,傳輸層,網(wǎng)絡(luò)層,網(wǎng)絡(luò)接口層。該模型網(wǎng)絡(luò)接口層對應(yīng)了OSI 的數(shù)據(jù)鏈路層和物理層。網(wǎng)絡(luò)層對應(yīng)OSI 網(wǎng)絡(luò)層,傳輸層對應(yīng)傳輸層,應(yīng)用層對應(yīng)OSI 的應(yīng)用層,表示層以及會話層。
在論述用JAVA 語言編寫網(wǎng)絡(luò)應(yīng)用程序之前,先論述端口的概念。
按照OSI 七層模型的描述,傳輸層提供進程(應(yīng)用程序)通信的能力,為了標示通信實體中進行通信的進程(應(yīng)用程序),TCP/IP協(xié)議提出了協(xié)議端口(protocol port)的概念。 端口是一種抽象的軟件結(jié)構(gòu)(包括一些數(shù)據(jù)結(jié)構(gòu)和I/O緩沖區(qū))。應(yīng)用程序通過系統(tǒng)調(diào)用與某端口建立連接(binding ,也就是應(yīng)用程序綁定到個端口)后,傳輸層傳給該端口的數(shù)據(jù)都被相應(yīng)的進程所接收,相應(yīng)進程發(fā)給傳輸層的數(shù)據(jù)都通過該端口輸出。端口用一個整數(shù)型標識符來表示,即端口號。端口號跟協(xié)議相關(guān)。TCP/IP傳輸層的兩個協(xié)議TCP 和UDP 是完全獨立的兩個軟件模塊,因此各自的端口號也相應(yīng)獨立,端口通常稱為協(xié)議端口(protocol port ), 簡稱端口。端口使用一個16位數(shù)字來表示,也就是兩個字節(jié),范圍是0-65535,1024以下的端口號保留給預(yù)定義的服務(wù),因此在網(wǎng)絡(luò)編程時采用1024以上的端口號。例如http 使用80端口。
套接字(Socekt )的引用
為了能夠方便的開發(fā)網(wǎng)絡(luò)應(yīng)用軟件,由美國伯克利大學(xué)在Unix 上推出了一種應(yīng)用程序訪問通信協(xié)議的操作系統(tǒng)調(diào)用socket ,套接字的出現(xiàn),使程序員很方便的訪問TCP/IP,從而開發(fā)各種網(wǎng)絡(luò)應(yīng)用的程序。隨著Unix 的應(yīng)用推廣,套接字在編寫網(wǎng)絡(luò)軟件中得到了極大的普及。后來,套接字又被引進了Windows 等操作系統(tǒng)中,成為開發(fā)網(wǎng)絡(luò)應(yīng)用程序的非常有效快捷的工具。
套接字存在于通信區(qū)域中,通信區(qū)域也叫做地址族,它是一個抽象的概念,主要用于通過套接字通信的進程的共有特性綜合在一起,套接字通常只與同一區(qū)域的套接字交換數(shù)據(jù)(也有可能跨區(qū)域通信,但這只在執(zhí)行了某種轉(zhuǎn)換進程后才能實現(xiàn))。Windows Socket 只支持一個通信區(qū)域:網(wǎng)際域(AF_INET), 這個域被使用網(wǎng)際協(xié)議簇通信的進程使用。
網(wǎng)絡(luò)字節(jié)順序
不同的計算機存放多字節(jié)值的順序不同,有的機器在起始地址存放地位字節(jié)(低位先存),有的機器在起始地址存放高位字節(jié)(高位先存)。基于Intel 的CPU, 即我們常用的pc 機采用的是地位先存。為保證數(shù)據(jù)的正確性,在網(wǎng)絡(luò)協(xié)議中需要指定網(wǎng)絡(luò)字節(jié)順序。TCP/IP協(xié)議使用16位整數(shù)和32位整數(shù)的高位先存格式。
客戶機/服務(wù)器模式
在TCP/IP網(wǎng)絡(luò)應(yīng)用中,通信的兩個進程間相互作用的主要模式是客戶機/服務(wù)器模式,即客戶機向服務(wù)器提出請求,服務(wù)器接收到請求后,提供相應(yīng)的服務(wù)。
客戶機/服務(wù)器模式的建立基于以下兩點:首先,建立網(wǎng)絡(luò)的起因是網(wǎng)絡(luò)中軟硬件資源,運算能力和信息不均等,需要共享,從而造就擁有眾多資源的主機提供服務(wù),資源較少的客戶請求服務(wù)這一非對等作用。其次,網(wǎng)間進程通信完全是異步的,相互通信的進程間既不存在父子關(guān)系,又不共享內(nèi)存緩沖區(qū),因此需要一種機制為希望通信的進程間建立聯(lián)系,為二者的數(shù)據(jù)交換提供同步,這就是基于客戶機/服務(wù)器模式TCP/IP。
客戶機/服務(wù)器模式在操作過程中采取的是主動請求方式。首先服務(wù)器方要先啟動,并根據(jù)請求提供相應(yīng)的服務(wù):
打開一個通信通道并告知本地主機,它愿意在某一地址和端口上接收客戶請求。 等待客戶請求到達該端口。
接收到重復(fù)服務(wù)請求,處理該請求并發(fā)送應(yīng)答信號。接收到并發(fā)服務(wù)請求,要激活一個新的進程(或線程)來處理這個客戶請求。新進程(或線程)處理此客戶請求,并不需要對其他
,請求作出應(yīng)答,服務(wù)完成后,關(guān)閉此新進程與客戶的通信鏈路,并終止。
返回第二步,等待另一客戶的請求。
關(guān)閉服務(wù)器。
客戶方:
打開一個通信通道,并連接到服務(wù)器所在主機的特定端口。
向服務(wù)器服務(wù)請求報文,等待并接收應(yīng)答;繼續(xù)提出請求。
請求結(jié)束后關(guān)閉通信通道并終止。
Windows Sockets的實現(xiàn)
Windows Sockets是Microsoft Windows的網(wǎng)絡(luò)程序設(shè)計接口,它是從Berkeley Sockets擴展而來的,以動態(tài)鏈接庫的形式提供給我們使用。Windows Sockets在繼承了Berkeley Sockets 主要特征的基礎(chǔ)上,又對它進行了重要擴充。這些擴充主要是提供了一些異步函數(shù),并增加了符合Windows 消息驅(qū)動特性的網(wǎng)絡(luò)事件異步選擇機制。
Windows Sockets1.1和Berkeley Sockets都是基于TCP/IP協(xié)議的;Windows Sockets2從Windows Sockets1.1發(fā)展而來,Windows Sockets1.1中的很多函數(shù)和伯克利套接字中的函數(shù)都是一致的,也就是如果采用雙方共有的函數(shù)編寫程序,那么我們的網(wǎng)絡(luò)程序很容易移植到其他的系統(tǒng)平臺下,與協(xié)議無關(guān)并向下兼容,可以使用任何底層傳輸協(xié)議提供的通信能力,來為上層應(yīng)用程序完成網(wǎng)絡(luò)數(shù)據(jù)通訊,而不關(guān)心底層網(wǎng)絡(luò)鏈路的通訊情況,真正實現(xiàn)了底層網(wǎng)絡(luò)通訊對應(yīng)用程序的透明。
套接字的類型
流式套接字(SOCK_STREAM)
提供面向連接、可靠的數(shù)據(jù)傳輸服務(wù),數(shù)據(jù)無差錯、無重復(fù)的發(fā)送,且按發(fā)送順序接收。 數(shù)據(jù)報套接字(SOCK_DGRAM)
提供無連接服務(wù)。數(shù)據(jù)包以獨立形式發(fā)送,不提供無錯保證,數(shù)據(jù)可能丟失或重復(fù),并且接收順序混亂。
原始套接字(SOCK_RAW).
基于TCP 的socket 編程
服務(wù)器端程序:
1、創(chuàng)建套接字(socket )
2、將套接字綁定到一個本地地址和端口上。(bind)
3、將套接字設(shè)為監(jiān)聽模式,準備接收客戶請求。(listen)
4、等待客戶請求到來;當請求到來后,結(jié)束后連接請求,返回一個新的對應(yīng)于此連接的套接字。(accept)
5、用返回的 和客戶端進行通信。(send/recv)
6、返回,等待另一客戶請求。
7、關(guān)閉套接字。
客戶端程序:
1、創(chuàng)建套接字(socket)
2、向服務(wù)器發(fā)出連接請求(connect)
3、和服務(wù)器端進行通信。(send/recv)
4、關(guān)閉套接字。
基于UDP (面向無連接)的socket 編程
服務(wù)器端程序:(接收端)
,創(chuàng)建套接字(socket ).
將套接字綁定到本地地址和端口上(bind )
等待接收數(shù)據(jù)(recvfrom )
關(guān)閉套接字。
客戶端(發(fā)送端)程序:
創(chuàng)建套接字(SOCKET )
向服務(wù)器發(fā)送數(shù)據(jù)(sendto ).
關(guān)閉套接字。
那么基于UDP 的網(wǎng)絡(luò)編程為什么要綁定呢,雖然面向無連接的套接字不需要建立連接,但是要作為我們要完成這次通訊的話,對于我們接收端必須先啟動,來接收客戶端發(fā)送的數(shù)據(jù),所以對于接收端來說,它也要告訴本地主機,它是哪一個地址上和端口上等待數(shù)據(jù)的到來,所以要調(diào)用bind. 注意面向無連接調(diào)用的是sendto,recvfrom, 而面向連接的調(diào)用是send/recv,
在視頻實例中實際編寫代碼中,第一步,要利用套接字需要加載套接字庫,這要調(diào)用函數(shù)WSAStartup. 其聲明如下:
int WSAStartup(__in WORD wVersionRequested , __out LPWSADATA lpWSAData );
功能是加載套接字庫,進行套接字庫的版本協(xié)商。wVersionRequested 參數(shù)用于指定準備加載的Winsock 庫的版本。高位字節(jié)指定所需要的Winsock 庫的副版本。而低位字節(jié)則是主版本,我們通常見到的版本號類似于2.1,2就是主版本號,1就是福版本號,可以用MAKWORD 這個宏來得到一個WORD 值,可用MAKEWORD(x,y)(其中x 是高位字節(jié),y 是低位字節(jié)) 方便地獲得wVersionRequested 的正確值。lpWSAData 參數(shù)是指向WSADATA 結(jié)構(gòu)的指針,該函數(shù)用其加載的庫版本有關(guān)的信息填在這個結(jié)構(gòu)中。
關(guān)于struct WSAData {
WORD wVersion;
WORD wHighVersion;
char szDescription[WSADESCRIPTION_LEN 1];
char szSystemStatus[WSASYSSTATUS_LEN 1];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpVendorInfo;
};
WSAStartup 把第一個字段wVersion 設(shè)為或是打算使用的Winsock 版本,wHighVersion 參數(shù)容納的是現(xiàn)有的Winsock 庫的最高版本,這兩個字段中,高位字節(jié)代表的是Winsock 副版本,而低位字節(jié)代表的則是Winsock 主版本。szDescription 和szSystemStatus 這兩個字段由特定的Winsock 實施方案設(shè)定,事實上沒有用,不要使用下面這兩個字段:iMaxSockets 和iMaxUdpDg ,它們是假定同時最多可打開多少套接字和數(shù)據(jù)報的最大長度。然而要知道數(shù)據(jù)報的最大長度應(yīng)該通過WSAEnumProtocols 來查詢協(xié)議信息。同時最多可打開套接字的數(shù)目不是固定的,很大程度上和可用物理內(nèi)存的多少有關(guān)。最后。lpVendorInfo 字段是為Winsock 實施方案有關(guān)的制定廠商信息預(yù)留的,任何應(yīng)該Win32平臺上都沒有使用這個字段。
如果Winsockdll 或底層網(wǎng)絡(luò)系統(tǒng)沒有被正確初始化或沒有被找到,WSAStartup 將返回WSASYSNOTREADY, 此外這個函數(shù)允許你的應(yīng)用程序協(xié)商使用某種版本的Winsock 規(guī)范,如果我們請求的版本等于或高于DLL 所支持的最低版本,WSAData 的成員wVersion 成員中將包含你的應(yīng)用程序應(yīng)該使用的版本,它是DLL 所支持的最低版本與請求版本中較小的那個。反之,如果請求的版本低于DLL 所支持的最低版本,WSAStartup 將返回WSAVERNOTSUPPORTED.
,關(guān)于WSAStartup 更詳細的信息,查閱相關(guān)MSDN.
對于每一個WSAStartup 的成功調(diào)用(成功加載WinsockDLL 后),在最后都對應(yīng)應(yīng)該WSACLeanUp 調(diào)用,以便釋放為應(yīng)用程序分配的資源,終止對WinsockDLL 的使用。 一個小知識整理一個代碼段中代碼,使其對齊可以使用ALT F8鍵。
關(guān)于SOCKET socket(int af,int type,int protocol)的說明
SOCKET WSAAPI socket( __in int af, __in int type, __in int protocol
);
該函數(shù)接收三個參數(shù),第一個參數(shù)指定地址族,對于TCP/IP協(xié)議的套接字,它只能是AF_INET(也可寫成PF_INET).第二個參數(shù)指定Socket 類型,對于1.1版本的Socket, 它只支持兩種類型的套接字,SOCK_STREAM指定產(chǎn)生流式套接字(基于TCP/IP協(xié)議),SOCK_DGRAM產(chǎn)生數(shù)據(jù)報套接字(基于UDP 協(xié)議)。第三個參數(shù)是與特定的地址家族相關(guān)的協(xié)議,如果指定為0,那么它就會根據(jù)地址格式和套接字類別,自動為你選擇一個合適的協(xié)議。這是推薦使用的一種選擇協(xié)議的方法。
如果這個函數(shù)的調(diào)用成功,它將返回一個新的SOCKET 數(shù)據(jù)類型的套接字描述符。如果調(diào)用失敗,這個函數(shù)就會返回一個INVALID_SOCKET,錯誤信息可以通過WSAGetLastError 函數(shù)返回。關(guān)于函數(shù)
WSAGetLastError :
int WSAGetLastError(void);
關(guān)于bind 函數(shù)的說明
聲明:int bind(
__in SOCKET s ,
__in const struct sockaddr* name ,
__in int namelen
); 這個函數(shù)將本地地址和一個套接字關(guān)聯(lián)起來,該函數(shù)接收三個參數(shù),第一個參數(shù)指定要綁定的套接字,第二個參數(shù)指定了該套接字的本地地址信息,是指向sockaddr 結(jié)構(gòu)的指針變量,由于該地址結(jié)構(gòu)是為所有的地址家族準備的,這個結(jié)構(gòu)可能(通常會)隨所使用的網(wǎng)絡(luò)協(xié)議不同而不同,所以,要用第三個參數(shù)指定該地址結(jié)構(gòu)的長度。Sockaddr 結(jié)構(gòu)定義如下: Struct sockaddr{ u_short sa_farmily;char sa_data[14]};
Sockaddr 的第一個字段指定該地址家族,在這里必須設(shè)為AF_INET.第二個字段僅僅是標示要求一塊內(nèi)存分配區(qū),起到占位的作用,該區(qū)域中指定與協(xié)議相關(guān)的具體地址信息。由于實際要求的只是內(nèi)存區(qū),所以對于不同的協(xié)議家族,用不同的結(jié)構(gòu)來替換sockaddr. 處了第一字段外,sockaddr 是按網(wǎng)絡(luò)字節(jié)順序標示的,在TCP/IP中,我們可以用sockaddr_in結(jié)構(gòu)替換sockaddr, 以方便填寫信息。
關(guān)于sockaddr_in信息:
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct sin_addr;
char sin_zero[8];
,};第一個參數(shù)標示地址族,對于IP 地址,該成員將一直是AF_INET.第二個參數(shù)指定的是將要分配給套接字的端口,第三個參數(shù)給出的套接字的主機IP 地址。要注意這個IP 地址和端口號都需要用網(wǎng)絡(luò)字節(jié)順序來表示,是一個結(jié)構(gòu)體變量:in_addr:
typedef struct in_addr {
union {
struct {
u_char s_b1,s_b2,s_b3,s_b4;
} S_un_b;
struct {
u_short s_w1,s_w2;
} S_un_w;
u_long S_addr; } S_un;
} IN_ADDR,
*PIN_ADDR,
FAR *LPIN_ADDR;
而最后一個參數(shù)只是一個填充數(shù),以使該結(jié)構(gòu)和sockaddr 結(jié)構(gòu)的長度一樣。如果這個函數(shù)調(diào)用成功,它將返回0. 如果調(diào)用失敗,這個函數(shù)就會返回一個SOCKET_ERROR,錯誤信息可以通過WSAGetLastError 函數(shù)返回。
int listen(
__in SOCKET s ,
__in int backlog
);
功能是將套接字處于監(jiān)聽狀態(tài),監(jiān)聽鏈接請求,第一個參數(shù)是套接字描述符,第二個參數(shù)是等待連接隊列的最大長度,如果設(shè)置這個參數(shù)為SOMAXCONN, 那么下層的服務(wù)提供者將負責(zé)套接字設(shè)置backlog 成最大的合理的值。注意設(shè)置這個參數(shù)backlog 主要是用來設(shè)置等待連接隊列的最大長度,而不是說在一個端口上同時可以進行連接的數(shù)目。例如說將這個值設(shè)為2,那么這個時候有三個客戶連接請求到來,那么前面兩個連接請求就會被放到等待請求連接隊列中,然后由我們應(yīng)用程序依次為這些請求服務(wù),而第三個連接請求就被拒絕了, SOCKET accept(
__in SOCKET s ,
__out struct sockaddr* addr ,
__in_out int* addrlen
);
第一個參數(shù)是套接字描述符,第二個參數(shù)是指向一個buffer 指針,用來接收連接實體的地址,也就是說當一個客戶端向服務(wù)器端發(fā)起請求連接的時候,在我們接收連接的時候,用這個參數(shù)保存了發(fā)起連接的客戶端的IP 地址信息以及它的端口信息,第三個參數(shù)是指向一個整形的指針,用來包含所返回的地址結(jié)構(gòu)的長度。那么這個函數(shù)在msdn 中有一個問題就是最后一個參數(shù)是in out類型,表示這個參數(shù)必須要賦一個初始值,然后當你將這個實參傳遞進去之后,那么這個函數(shù)會返回一個值給你,如果說只是一個out, 那么在傳遞參數(shù)時候,不需要給它賦初始值,但是對于函數(shù)accept 在調(diào)用時候必須賦一個初始值,那么這個初始值大小就是結(jié)構(gòu)體的長度,如果說你沒有給這個參數(shù)賦初始值,那么這個調(diào)用就會失敗。
,將IP 地址指定為INADDR_ANY,允許套接字向任何分配給本地機器的IP 地址發(fā)送或接收數(shù)據(jù),多數(shù)情況下,每個機器只有一個IP 地址,但有的機器可能會有多個網(wǎng)卡每個網(wǎng)卡都可以有自己的IP 地址,用INADDR_ANY可以簡化應(yīng)用程序的編寫。將地址指定為INADDR_ANY,允許一個獨立應(yīng)用接受發(fā)自多個接口的回應(yīng),如果我們只想讓套接字使用多個IP 中的一個地址,就必須指定實際地址,要做到這一點,可以用inct_addr()函數(shù),這個函數(shù)需要一個字符串作為其參數(shù),該字符串指定了電分十進制表示的IP 地址(如192.168.0.16)。而且inet_addr()函數(shù)會返回一個適合分配給S_addr的u_long類型的數(shù)值。Inet_nota()函數(shù)會完成相反的轉(zhuǎn)換,它接受一個in_addr結(jié)構(gòu)體類型的參數(shù)并返回一個以電分十進制格式表示的IP 地址字符串。
在地址結(jié)構(gòu)體sockaddr_in中給各個字段賦值,要注意在地址結(jié)構(gòu)體中的這些成員除了sin_family之外,其他的成員都要使用網(wǎng)絡(luò)字節(jié)順序,要把INADDR_ANY轉(zhuǎn)化為網(wǎng)絡(luò)字節(jié)順序,可以利用函數(shù)htonl 來完成,關(guān)于這個函數(shù)說明:功能是轉(zhuǎn)換一個u_long類型從主機字節(jié)序到TCP/IP網(wǎng)絡(luò)字節(jié)序,還有一個函數(shù)為htons ,功能是轉(zhuǎn)換一個u_short類型,從主機字節(jié)序到TCP/IP網(wǎng)絡(luò)字節(jié)序.
inet_addr()函數(shù)
unsigned long inet_addr( __in const char* cp
);
char* FAR inet_ntoa( __in struct in_addr in
);
接下來調(diào)用bind 函數(shù);
將套接字設(shè)置為監(jiān)聽模式調(diào)用函數(shù)listen; 監(jiān)聽連接請求。
接下來用while 做一下死循環(huán),因為作為我們服務(wù)器端來說,需要不斷的等待客戶端連接請求的到來。持續(xù)的運行下去。在循環(huán)體中需要調(diào)用accept, 關(guān)于函數(shù)accept: SOCKET accept(__in SOCKET s ,__out struct sockaddr* addr ,
__in_out int* addrlen ); 第一個參數(shù)是套接字描述符,第二個參數(shù)是一個out ,是指向一個buffer 指針,用來接收連接實體的地址,也就是當一個客戶端向服務(wù)端發(fā)起連接請求,那么在接受這個連接的時候,通過該參數(shù)保存了發(fā)起連接的客戶端的IP 地址信息和端口信息,第三個參數(shù)也是一個out, 也是一個返回值,它指向一個整形的指針,用來包含所返回的地址結(jié)構(gòu)的長度,調(diào)用該函數(shù)的時候,需要定義一個地址結(jié)構(gòu)體的變量。用來接收客戶端的地址信息,還需要定義一個整形的變量,并賦予初始值為這個結(jié)構(gòu)體的長度。那么要注意MSDN 文檔有一個問題,通常我們看到如果前面寫上一個in, 逗號,out, 就表示這個參數(shù)必須要賦予一個初始值,然后當你將這個參數(shù)傳遞進去之后,那么這個函數(shù)會再返回一個值給你,如果說只是一個out, 那么在傳遞這個參數(shù)的時候,不需要給該參數(shù)賦初始值,但是對于這個accept 函數(shù)來說,第三個參數(shù)在傳遞之前必須要賦初始值,那么這個初始值大小就是這個結(jié)構(gòu)體長度,如果說沒有給這個參數(shù)賦初始值,那么這個調(diào)用就會失敗。該函數(shù)在接受客戶端連接請求之后會返回一個相對于新的連接的套接字描述符,然后利用這個套接字可以和客戶端進行通信了,而我們先前的套接字仍然繼續(xù)監(jiān)聽客戶端的連接請求。
到客戶端的連接請求到來之后,我們接受了這個連接請求,建立了這個連接,同時返回了相對于這個連接的套接字,接下來就可以通訊了。我們可以向客戶端發(fā)送數(shù)據(jù)使用函數(shù)
,send 來完成,所以在循環(huán)體中的調(diào)用accept 之后調(diào)用send.
Send 函數(shù)聲明:
int send( __in SOCKET s, __in const char* buf, __in int len, __in int flags
);
第一個參數(shù)是套接字描述符,第二個參數(shù)是一個buffer, 包含了將要被傳送的數(shù)據(jù),第三個參數(shù)是buffer 中數(shù)據(jù)長度,第四個參數(shù)是一個標記,設(shè)置這個參數(shù)將影響send 函數(shù)的行為,在我們的應(yīng)用中將其設(shè)置為零即可。
接收數(shù)據(jù)調(diào)用函數(shù)recv:
int recv( __in SOCKET s, __out char* buf, __in int len, __in int flags
); 第一個參數(shù)是建立連接之后的套接字,第二個是buffer 用來接收數(shù)據(jù)的,第三個參數(shù)是指示了buffer 的長度,第四個參數(shù)是標記和send 中的第四個參數(shù)類似,設(shè)置這個參數(shù)的值可以影響recv 之行為,在我們應(yīng)用中將此參數(shù)設(shè)置為零即可。
連接服務(wù)器端調(diào)用函數(shù)connect:
int connect( __in SOCKET s, __in const struct sockaddr* name, __in int namelen
);
功能是建立一個連接,第一個參數(shù)是套接字描述符,第二個參數(shù)是一個地址結(jié)構(gòu)體的一個指針,主要用來設(shè)定你所連接服務(wù)器端的地址信息,第三個參數(shù)是地址結(jié)構(gòu)體的長度, Crtl tab鍵可以用來切換窗口。
基于CTP 連接的實例兩個項目為TcpSrv,TcpClient, 分別為服務(wù)器端,客戶端。 源文件代碼為:
TcpSrv.cpp:
#include
#include"Winsock2.h"
#include
using namespace std;
void main()
{
//加載和請求套接字庫;
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
,err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return ;
}
if ( LOBYTE( wsaData.wVersion ) != 2 ||
HIBYTE( wsaData.wVersion ) != 2 ) {
WSACleanup( );
return ;
}
//創(chuàng)建套接字
SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0);
//將創(chuàng)建的套接字綁定到本地地址和端口上
//為綁定本地地址和端口調(diào)用bind 創(chuàng)造條件,準備向其第二個參數(shù)傳遞實參, 用結(jié)構(gòu)體類型sockaddr_in
//代替Sockaddr 以方便填寫信息
sockaddr_in addrSrv;
//給sockaddr_in成員賦值;而其第三個成員sin_addr也是一個結(jié)構(gòu)體, 在此結(jié)構(gòu)體中S_un也是一個結(jié)構(gòu)體,那么需要給這個
//結(jié)構(gòu)體成員分別賦值, 并且調(diào)用htonl 將ulong 類型轉(zhuǎn)化為網(wǎng)絡(luò)字節(jié)序。
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(10000);
//條件成熟,下面調(diào)用bind;
bind(sockSrv,(sockaddr*)&addrSrv,sizeof (sockaddr));
//創(chuàng)建基于服務(wù)器端的socket ,調(diào)用listen 讓服務(wù)器處于監(jiān)聽模式, 同時設(shè)置設(shè)置等待連接隊列的最大長度
listen(sockSrv,5);
//用while 做死循環(huán),讓服務(wù)器端持續(xù)不斷的運行下去;
//在循環(huán)之前需要為調(diào)用accept 創(chuàng)建條件,需要定義地址結(jié)構(gòu)sockaddr_in變量,從而接收客戶端的IP 和端口號。
//還需要定義一個整形變量來接收地址結(jié)構(gòu)體sockaddr_in的長度,調(diào)用accept 在接收客戶端連接請求之后,會返回
//一個相對于新的連接的套接字描述符。利用這個套接字描述符就可以和客戶端進行通信了。而先前的套接字會繼續(xù)監(jiān)聽
//客戶的連接請求。
sockaddr_in addrClient;
int len=sizeof (sockaddr_in);
while (1)
{
SOCKET sockConn=accept(sockSrv,(sockaddr*)&addrClient,&len);