“套接字”或者“插座”(Socket)也是一種軟件形式的抽象,用於表達兩台機器間一個連接的“終端”。針對一個特定的連接,每台機器上都有一個“套接字”,可以想象它們之間有一條虛擬的“線纜”。線纜的每一端都插入一個“套接字”或者“插座”裡。當然,機器之間的物理性硬件以及電纜連接都是完全未知的。抽象的基本宗旨是讓我們盡可能不必知道那些細節。
在Java中,我們創建一個套接字,用它建立與其他機器的連接。從套接字得到的結果是一個InputStream以及OutputStream(若使用恰當的轉換器,則分別是Reader和Writer),以便將連接作為一個IO流對象對待。有兩個基於數據流的套接字類:ServerSocket,服務器用它“偵聽”進入的連接;以及Socket,客戶用它初始一次連接。一旦客戶(程序)申請建立一個套接字連接,ServerSocket就會返回(通過accept()方法)一個對應的服務器端套接字,以便進行直接通信。從此時起,我們就得到了真正的“套接字-套接字”連接,可以用同樣的方式對待連接的兩端,因為它們本來就是相同的!此時可以利用getInputStream()以及getOutputStream()從每個套接字產生對應的InputStream和OutputStream對象。這些數據流必須封裝到緩沖區內。可按第10章介紹的方法對類進行格式化,就象對待其他任何流對象那樣。
對於Java庫的命名機制,ServerSocket(服務器套接字)的使用無疑是容易產生混淆的又一個例證。大家可能認為ServerSocket最好叫作“ServerConnector”(服務器連接器),或者其他什麼名字,只是不要在其中安插一個“Socket”。也可能以為ServerSocket和Socket都應從一些通用的基礎類繼承。事實上,這兩種類確實包含了幾個通用的方法,但還不夠資格把它們賦給一個通用的基礎類。相反,ServerSocket的主要任務是在那裡耐心地等候其他機器同它連接,再返回一個實際的Socket。這正是“ServerSocket”這個命名不恰當的地方,因為它的目標不是真的成為一個Socket,而是在其他人同它連接的時候產生一個Socket對象。
然而,ServerSocket確實會在主機上創建一個物理性的“服務器”或者偵聽用的套接字。這個套接字會偵聽進入的連接,然後利用accept()方法返回一個“已建立”套接字(本地和遠程端點均已定義)。容易混淆的地方是這兩個套接字(偵聽和已建立)都與相同的服務器套接字關聯在一起。偵聽套接字只能接收新的連接請求,不能接收實際的數據包。所以盡管ServerSocket對於編程並無太大的意義,但它確實是“物理性”的。
創建一個ServerSocket時,只需為其賦予一個端口編號。不必把一個IP地址分配它,因為它已經在自己代表的那台機器上了。但在創建一個Socket時,卻必須同時賦予IP地址以及要連接的端口編號(另一方面,從ServerSocket.accept()返回的Socket已經包含了所有這些信息)。