Socket編程基礎
本章以Berkeley Socket為主,主要介紹網絡編程時常用的調用和程序使用它們的方法及基本結構。網絡編程有兩種主要的編程接口,一種是Berkeley UNIX(BSD UNIX)的socket編程接口,另一種是AT&T的TLI接口(用于UNIXSYSV)。
1 、TCP/IP 基礎知識
這里先假定讀者對ISO的OSI七層模型已有了一定的了解,下面我們來看看TCP/IP模型。ISO的OSI對服務、接口和協議的概念區別十分明了,但它卻沒有真正的用戶群。TCP/IP模型對服務、接口和協議的概念區別不象OSI模型那樣明晰,但很實用。TCP/IP模型分為四層,對應于OSI七層模型如下圖所示:圖6-1 TCP/IP參考模型與OSI模型的近似對應關系在TCP/IP模型中,互聯網層是基于無連接互聯網絡層的分組交換網絡。在這一層中主機可以把報文(Packet)發往任何網絡,報文獨立地傳向目標。互聯網層定義了報文的格式和協議,這就是IP協議族(Internet Protocol)。互聯網層的功能是將報文發送到目的地,主要的設計問題是報文路由和避免阻塞。互聯網層上面是傳輸層,該層的主要功能和OSI模型的該層一樣,主要使源和目的主機之間可以進行會話。該層定義了兩個端到端的協議,一個是面向連接的傳輸控制協議TCP,另一個是無連接的用戶數據報協議UDP。TCP/IP協議模型中沒有會話層和表示層。傳輸層之上是應用層,它包含所有的高層協議,如遠程虛擬終端協議TELNET、文件傳輸協議FTP、簡單郵件傳輸協議SMTP等。這些高層協議中常見的如TELNET協議,用來允許用戶遠程登錄到另一臺UNIX機器;FTP協議用來傳輸文件,常見的有WU-FTP(Washington University的FTP服務器端程序,是一個免費程序);SMTP協議用來傳送email,常見的服務器端程序有netscape等公司制作的程序,也有免費使用的sendmail程序;還有域名系統服務DNS協議,新聞組傳送協議NNTP,用于WWW的超文本傳輸協議HTTP等。主機到網絡這一層,在TCP/IP模型中沒有詳細定義,這里不作介紹。
2、 Socket一般描述
由于越來越多的計算機廠商,特別是工作站制造商如Sun等公司采用了Berkeley UNIX,socket接口被廣泛采用,以至于現在,socket接口被廣泛認可并成為了事實上的工業標準。目前的SYSV、BSD、OSF都將socket接口作為系統的一部分。當時設計如何支持TCP/IP協議時,有兩種加入函數的方法,一種是直接加入支持TCP/IP協議的調用,另一種是加入支持一般網絡協議的函數,而用參數來指定支持TCP/IP協議。Berkeley采用了后者,這樣可以支持多協議族,TCP/IP是協議族之一(PF_INET)。
2.1 socket 描述符
前面已經提到過,在UNIX中,進程要對文件進行操作,一般使用open調用打開一個文件進行訪問,每個進程都有一個文件描述符表,該表中存放打開的文件描述符。用戶使用open等調用得到的文件描述符其實是文件描述符在該表中的索引號,該表項的內容是一個指向文件表的指針。應用程序只要使用該描述符就可以對指定文件進行操作。同樣,socket接口增加了網絡通信操作的抽象定義,與文件操作一樣,每個打開的socket都對應一個整數,我們稱它為socket描述符,該整數也是socket描述符在文件描述符表中的索引值。但socket描述符在描述符表中的表項并不指向文件表,而是指向一個與該socket有關的數據結構。BSD UNIX中新增加了一個socket調用,應用程序可以調用它來新建一個socket描述符,注意進程用open只能產生文件描述符,而不能產生socket描述符。socket調用只能完成建立通信的部分工作,一旦建立了一個socket,應用程序可以使用其他特定的調用來為它添加其他詳細信息,以完成建立通信的過程。
2.2 從概念上理解socket的使用網絡編程中最常見的是客戶/服務器模式。
以該模式編程時,服務端有一個進程(或多個進程)在指定的端口等待客戶來連接,服務程序等待客戶的連接信息,一旦連接上之后,就可以按設計的數據交換方法和格式進行數據傳輸。客戶端在需要的時刻發出向服務端的連接請求。
這里為了便于理解,提到了這些調用及其大致的功能。使用socket調用后,僅產生了一個可以使用的socket描述符,這時還不能進行通信,還要使用其他的調用,以使得socket所指的結構中使用的信息被填寫完。在使用TCP協議時,一般服務端進程先使用socket調用得到一個描述符,然后使用bind調用將一個名字與socket描述符連接起來,對于Internet域就是將Internet地址聯編到socket。之后,服務端使用listen調用指出等待服務請求隊列的長度。然后就可以使用accept調用等待客戶端發起連接(一般是阻塞等待連接,后面章節會講到非阻塞的方式),一旦有客戶端發出連接,accept返回客戶的地址信息,并返回一個新的socket描述符,該描述符與原先的socket有相同的特性,這時服務端就可以使用這個新的socket進行讀寫操作了。一般服務端可能在accept返回后創建一個新的進程進行與客戶的通信,父進程則再到accept調用處等待另一個連接。客戶端進程一般先使用socket調用得到一個socket描述符,然后使用connect向指定的服務器上的指定端口發起連接,一旦連接成功返回,就說明已經建立了與服務器的連接,這時就可以通過socket描述符進行讀寫操作了。下面是在客戶和服務端使用TCP時,客戶進程和服務進程使用系統調用的該程。
使用TCP的客戶和服務端使用系統調用的圖示使用無連接的UDP協議時,服務端進程創建一個socket,之后調用recvfrom接收客戶端的數據報,然后調用sendto將要返回客戶端的消息發送給客戶進程。客戶端也要先創建一個socket,再使用sendto向服務端進程發出請求,使用recvfrom得到返回的消息。