導讀目錄
- ServerSocket
- Socket
- 半關閉的Socket
TCP/IP是一種可靠的網(wǎng)絡協(xié)議骨望,它在通信的兩端各建立一個Socket令漂,從而在通信的兩端之間行形成網(wǎng)絡虛擬鏈路。該虛擬鏈路一旦建立成功鳍寂,兩端的程序就可以通過虛擬鏈路進行通信拌蜘。
通過使用IP協(xié)議彼棍,從而使Internet成為一個允許連接不同類型的計算機和操作系統(tǒng)的網(wǎng)絡。
IP協(xié)議負責將消息從一個主機傳送到另一個主機跷乐,消息在傳送過程中被分割成一個個小包(IP數(shù)據(jù)包)鸿摇,該協(xié)議只是保證了計算計之間可以發(fā)送和接受數(shù)據(jù),但無法解決數(shù)據(jù)分組在傳輸過程中可能出現(xiàn)的問題劈猿,因此拙吉,還需要安裝TCP協(xié)議來提供可靠并且無差錯的通信服務,
TCP協(xié)議被稱為一種端對端協(xié)議揪荣,它會讓兩臺計算計之間建立一個連接:用于發(fā)送和接受數(shù)據(jù)的虛擬鏈路筷黔。TCP協(xié)議負責收集這些信息包,并將其安適當?shù)拇涡蚍藕脗魉驼叹保邮斩耸艿胶笤賹⑵湔_的還原佛舱,TCP協(xié)議保證了數(shù)據(jù)包在傳送中準確無誤。
Java使用Socket對象來代表兩端的通信端口挨决,并通過Socket產(chǎn)生IO流來進行網(wǎng)絡通信
1.使用ServerSocket創(chuàng)建TCP服務器端
在客戶/服務器通信模式中, 服務器端需要創(chuàng)建監(jiān)聽端口的 ServerSocket, ServerSocket 負責接收客戶連接請求
異常類型
在了解Socket的內(nèi)容之前请祖,先要了解一下涉及到的一些異常類型。以下四種類型都是繼承于IOException脖祈,所以很多之后直接彈出IOException即可肆捕。
UnkownHostException //主機名字或IP錯誤
ConnectException //服務器拒絕連接、服務器沒有啟動盖高、(超出隊列數(shù)慎陵,拒絕連接)
SocketTimeoutException //連接超時
BindException //Socket對象無法與制定的本地IP地址或端口綁定
ServerSocket的構造器:
ServerSocket() throws IOException;
ServerSocket(int port) throws IOException;//用指定的端口來創(chuàng)建一個ServerSocket, port的有效值:0~65535
ServerSocket(int port, int backlog) throws IOException;//增加一個用于改變連接隊列長度的參數(shù)backlog
ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException;//在機器上存在多個IP地址的情況下眼虱,允許通過bindAddr參數(shù)來將ServerSocket綁定到指定的IP地址。
注意點:
- port服務端是要監(jiān)聽的端口席纽;backlog客戶端連接請求的隊列長度捏悬;bindAddr服務端綁定IP
- 如果端口被占用或者沒有權限使用某些端口會拋出BindException錯誤。譬如1~1023的端口需要管理員才擁有權限綁定润梯。
- 如果設置端口為0过牙,則系統(tǒng)會自動為其分配一個端口;
- bindAddr用于綁定服務器IP纺铭,為什么會有這樣的設置呢寇钉,譬如有些機器有多個網(wǎng)卡。
- ServerSocket一旦綁定了監(jiān)聽端口彤蔽,就無法更改摧莽。ServerSocket()可以實現(xiàn)在綁定端口前設置其他的參數(shù)。
ServerSocket的方法:
Socket accept(); //如果接受到一個客戶端Socket的連接請求顿痪,該方法會返回一個與客戶端Socket對應的Socket;否則該方法將會一直處于等待狀態(tài)镊辕,線程也會阻塞(即該方法所在的線程)
void close(); //關閉該Socket
注意:accept()方法是ServerSocket的方法,Socket沒有該方法
2.使用Socket創(chuàng)建客戶端
構造器
Socket();//創(chuàng)建一個默認使用本地主機的默認IP,系統(tǒng)動態(tài)分配的端口的Socket
//創(chuàng)建連接到指定遠程主機蚁袭、端口的Socket,區(qū)別只是IP的傳入方式
Socket(InetAddress address, int port)throws UnknownHostException, IOException
Socket(String host, int port)throws UnknownHostException, IOException
//創(chuàng)建連接到指定遠程主機和遠程端口的Socket征懈,并指定本地IP地址和本地端口,適用于本地主機有多個IP地址的情形
Socket(InetAddress address, int port, InetAddress localAddress, int localPort)throws IOException
Socket(String host, int port, InetAddress localAddress, int localPort)throws IOException
Socket方法
InetAddress getInetAddress();//獲取遠程服務端的IP地址
int getPort();//獲取遠程服務端的端口
InetAddress getLocalAddress();//獲取本地客戶端的IP地址
int getLocalPort();//獲取本地客戶端的端口
void connect(SocketAddress endpoint); //
void connect(SocketAddress endpoint, int timeout)
**InputStream getInputStream();//獲得輸入流 **
**OutputStream getOutputStream();//獲得輸出流 **
值得注意的是揩悄,在這些方法里面卖哎,最重要的就是getInputStream()和getOutputStream()了。
Socket狀態(tài)
boolean isClosed();//連接是否已關閉删性,若關閉亏娜,返回true;否則返回false
boolean isConnect();//如果曾經(jīng)連接過蹬挺,返回true维贺;否則返回false
boolean isBound();//如果Socket已經(jīng)與本地一個端口綁定,返回true巴帮;否則返回false
如果要確認Socket的狀態(tài)是否處于連接中溯泣,下面語句是很好的判斷方式。
boolean isConnection = socket.isConnected() && !socket.isClosed();//判斷當前是否處于連接
*例子
服務器端
import java.net.ServerSocket;
import java.net.Socket;
import java.io.*;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(3006);
while(true) {
System.out.println("等待連接...");
Socket client = ss.accept();
System.out.println("已有客戶端連接成功榕茧,正在接受消息...");
BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));//接受
String line;
while((line = br.readLine()) != null) {
if("exit".equals(line)) {
client.close();
}else {
System.out.println("對方發(fā)來的消息:" + line);
}
}
}
}
}
客戶端
import java.net.Socket;
import java.net.InetAddress;
import java.io.*;
public class Client {
public static void main(String[] args) throws IOException {
Socket s = new Socket(InetAddress.getLocalHost(), 3006);//由于服務器端是在本地垃沦,故這里使用InetAddress.getLocalHost()
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;
PrintStream ps = new PrintStream(s.getOutputStream());//因為發(fā)送的是文本內(nèi)容,采用這個打印流的用押,要是其他文件最好使用字節(jié)輸出流肢簿,程序更有健壯性
while((line = br.readLine()) != null) {
ps.println(line);
}
}
}
3.半關閉的Socket
在IO流中,如果要表示輸出已經(jīng)結束,則可以通過關閉輸出流來實現(xiàn)译仗,但在網(wǎng)絡通信中則不能通過關閉輸出流來表示輸出已經(jīng)結束抬虽,因為關閉輸出流官觅,該輸出流對應的Socket也將隨之關閉纵菌,
因此,Socket提供了兩個半關閉的方法休涤,只關閉Socket的輸入流或輸出流咱圆,用以表示輸出數(shù)據(jù)已經(jīng)發(fā)送完成,
void shutdownInput(); //關閉Socket的輸入流功氨,該Socket還可以輸出數(shù)據(jù), 進入半讀狀態(tài)
void shutdownOutput(); //關閉該Socket的輸出流序苏,該Socket還可以輸入數(shù)據(jù), 進入半寫狀態(tài)
調(diào)用這兩個方法后的關閉Sockets的輸入流或輸出流,該Socket處于"半關閉"狀態(tài)
注意:即使先后調(diào)用這兩個方法也不會關閉Socket捷凄,只不過處于既不能輸出也不能輸入的狀態(tài)忱详;但是如果關閉Socket對應的輸出流(getOutputStream())或輸入流(getInputStream())關閉之后,就會徹底將Socket關閉跺涤。
判斷該Socket的狀態(tài)
boolean isInputShutdown(); //判斷Socket是否處于半讀狀態(tài)(read-half)
boolean isOutputShutdown(); //判斷Socket是否處于半寫狀態(tài)(write-half)
注意:當Socket關閉了輸入流或輸出流后匈睁,該Socket無法再次打開輸出流或輸入流
(2)半關閉適用于哪些場景:
1)適用于那些不需要長久保持通信狀態(tài)的一站式服務;
2)特別適用于HTTP通信桶错,而且特別適用于客戶端航唆,因為客戶端在發(fā)送請求后就無需再發(fā)送其它數(shù)據(jù)了,往往只是等待服務器端返回想要的資源院刁;
3)因此在客戶端發(fā)送完請求之后就可以立馬半關閉輸出流了(半寫狀態(tài))糯钙;