Socket網路通訊基礎(第八天)
什麼是網路模型
網路程式設計的本質是兩個裝置之間的資料交換,當然,在**計算機網路**中,裝置主要指計算機。資料傳遞本身沒有多大的難度,不就是把一個裝置中的資料傳送給兩外一個裝置,然後接受另外一個裝置反饋的資料。
現在的網路程式設計基本上都是基於請求/響應方式的,也就是一個裝置傳送請求資料給另外一個,然後接收另一個裝置的反饋。
在網路程式設計中,發起連線程式,也就是傳送第一次請求的程式,被稱作客戶端(Client),等待其他程式連線的程式被稱作伺服器(Server)。客戶端程式可以在需要的時候啟動,而伺服器為了能夠時刻相應連線,則需要一直啟動。例如以打電話為例,首先撥號的人類似於客戶端,接聽電話的人必須保持電話暢通類似於伺服器。
連線一旦建立以後,就客戶端和伺服器端就可以進行資料傳遞了,而且兩者的身份是等價的。
在一些程式中,程式既有客戶端功能也有伺服器端功能,最常見的軟體就是BT、emule這類軟體了。
IP地址與域名
在現實生活中,如果要打電話則需要知道對應人的電話號碼,如果要寄信則需要知道收信人的地址。在網路中也是這樣,需要知道一個裝置的位置,則需要使用該裝置的IP地址,具體的連線過程由硬體實現,程式設計師不需要過多的關心。
IP地址是一個規定,現在使用的是IPv4,既由4個0-255之間的數字組成,在計算機內部儲存時只需要4個位元組即可。在計算機中,IP地址是分配給網絡卡的,每個網絡卡有一個唯一的IP地址,如果一個計算機有多個網絡卡,則該臺計算機則擁有多個不同的IP地址,在同一個網路內部,IP地址不能相同。IP地址的概念類似於電話號碼、身份證這樣的概念。
由於IP地址不方便記憶,所以有專門創造了域名(Domain Name)的概念,其實就是給IP取一個字元的名字,例如163.com、sina.com等。IP和域名之間存在一定的對應關係。如果把IP地址類比成身份證號的話,那麼域名就是你的姓名。
其實在網路中只能使用IP地址進行資料傳輸,所以在傳輸以前,需要把域名轉換為IP,這個由稱作DNS的伺服器專門來完成。
IP、域名:所以在網路程式設計中,可以使用IP或域名來標識網路上的一臺裝置。
埠號的概念
網路程式設計就是使用IP地址,或域名,和埠連線到另一臺計算機上對應的程式,按照規定的協議(資料格式)來交換資料,實際程式設計中建立連線和傳送、接收資料在語言級已經實現,做的更多的工作是設計協議,以及編寫生成和解析資料的程式碼罷了,然後把資料轉換成邏輯的結構顯示或控制邏輯即可。
埠:定位到具體應用程式。
Socket入門
什麼是Socket
- Socket就是為網路服務提供的一種機制。
- 通訊的兩端都有Sokcet。
- 網路通訊其實就是Sokcet間的通訊。
- 資料在兩個Sokcet間通過IO傳輸。
TCP與UDP在概念上的區別
**udp: **
- 是面向無連線, 將資料及源的封裝成資料包中,不需要建立連線;
- 每個資料報的大小在限制64k內;
- 因無連線,是不可靠協議;
- 不需要建立連線,速度快。
tcp
- 建議連線,形成傳輸資料的通道;
- 在連線中進行大資料量傳輸,以位元組流方式;
- 通過三次握手完成連線,是可靠協議;
- 必須建立連線m效率會稍低。
UDP協議
//socket伺服器端
class UdpSocketServer {
public static void main(String[] args) throws IOException {
System.out.println("udp伺服器端啟動連線....");
DatagramSocket ds = new DatagramSocket(8080);
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
// 阻塞,等待接受客戶端傳送請求
ds.receive(dp);
System.out.println("來源:"+dp.getAddress()+",埠號:"+dp.getPort());
// 獲取客戶端請求內容
String str=new String(dp.getData(),0,dp.getLength());
System.out.println("str:"+str);
ds.close();
}
}
// udp客戶端程式碼
public class UdpClient {
public static void main(String[] args) throws IOException {
System.out.println("udp客戶端啟動連線....");
DatagramSocket ds = new DatagramSocket();
String str="李四你好";
byte[] bytes= str.getBytes();
DatagramPacket dp= new DatagramPacket(bytes, bytes.length,InetAddress.getByName("127.0.0.1"),8080);
ds.send(dp);
ds.close();
}
}
TCP協議
TCP握手協議
在TCP/IP協議中,TCP協議採用三次握手建立一個連線。
- 建立連線時,客戶端傳送SYN包(SYN=J)到伺服器,並進入SYN_SEND狀態,等待伺服器確認;
- 伺服器收到SYN包,必須確認客戶的SYN(ACK=J+1),同時自己也傳送一個SYN包(SYN=K),即SYN+ACK包,此時伺服器V狀態;
- 客戶端收到伺服器的SYN+ACK包,向伺服器傳送確認包ACK(ACK=K+1),此包傳送完畢,客戶端和伺服器進入ESTABLISHED狀態,完成三次握手。
完成三次握手,客戶端與伺服器開始傳送資料。
TCP分手協議
- 客戶端A傳送一個FIN,用來關閉客戶A到伺服器B的資料傳送。
- 伺服器B收到這個FIN,它發回一個ACK,確認序號為收到的序號加1。和SYN一樣,一個FIN將佔用一個序號。
- 伺服器B關閉與客戶端A的連線,傳送一個FIN給客戶端A。
- 客戶端A發回ACK報文確認,並將確認序號設定為收到序號加1。
//tcp伺服器端...
class TcpServer {
public static void main(String[] args) throws IOException {
System.out.println("socket tcp伺服器端啟動....");
ServerSocket serverSocket = new ServerSocket(8080);
// 等待客戶端請求
Socket accept = serverSocket.accept();
InputStream inputStream = accept.getInputStream();
// 轉換成string型別
byte[] buf = new byte[1024];
int len = inputStream.read(buf);
String str = new String(buf, 0, len);
System.out.println("伺服器接受客戶端內容:" + str);
serverSocket.close();
}
}
// TCP客戶端
public class TcpClient {
public static void main(String[] args) throws UnknownHostException, IOException {
System.out.println("socket tcp 客戶端啟動....");
Socket socket = new Socket("127.0.0.1", 8080);
OutputStream outputStream = socket.getOutputStream();
outputStream.write("我是張三".getBytes());
socket.close();
}
}
BIO與NIO
IO(BIO):和NIO區別:其本質就是阻塞和非阻塞的區別
阻塞概念:應用程式在獲取網路資料的時候,如果網路傳輸資料很慢,就會一直等待,直到傳輸完畢為止。
非阻塞概念:應用程式直接可以獲取已經準備就緒好的資料,無需等待。
IO為同步阻塞形式,NIO為同步非阻塞形式,NIO並沒有實現非同步,在JDK1.7後升級NIO庫包,支援非同步非阻塞
同步模型NIO2.0(AIO)
BIO:同步阻塞式IO,伺服器實現模式為一個連線一個執行緒,即客戶端有連線請求時伺服器端就需要啟動一個執行緒進行處理,如果這個連線不做任何事情會造成不必要的執行緒開銷,當然可以通過執行緒池機制改善。 NIO:同步非阻塞式IO,伺服器實現模式為一個請求一個執行緒,即客戶端傳送的連線請求都會註冊到多路複用器上,多路複用器輪詢到連線有I/O請求時才啟動一個執行緒進行處理。 AIO(NIO.2):非同步非阻塞式IO,伺服器實現模式為一個有效請求一個執行緒,客戶端的I/O請求都是由OS先完成了再通知伺服器應用去啟動執行緒進行處理。
同步時,應用程式會直接參與IO讀寫操作,並且我們的應用程式會直接阻塞到某一個方法上,直到資料準備就緒;或者採用輪訓的策略實時檢查資料的就緒狀態,如果就緒則獲取資料。
非同步時,則所有的IO讀寫操作交給作業系統,與我們的應用程式沒有直接關係,我們程式不需要關係IO讀寫,當作業系統完成了IO讀寫操作時,會給我們應用程式傳送通知,我們的應用程式直接拿走資料極即可。
筆試題
-
為什麼建立連線協議是三次握手,而關閉連線卻是四次握手呢?
這是因為服務端的LISTEN狀態下的SOCKET當收到SYN報文的建連請求後,它可以把ACK和SYN(ACK起應答作用,而SYN起同步作用)放在 一個報文裡來發送,但關閉連線時,當收到對方的FIN報文通知時,它僅僅表示對方沒有資料傳送給你了;但未必你所有的資料都全部發送給對方了,所以你可以 未必會馬上會關閉SOCKET,也即你可能還需要傳送一些資料給對方之後,再發送FIN報文給對方來表示你同意現在可以關閉連線了,所以它這裡的ACK報 文和FIN報文多數情況下都是分開發送的。
-
為什麼TIME_WAIT狀態還需要等2MSL後才能返回到CLOSED狀態?
這是因為雖然雙方都同意關閉連線了,而且握手的4個報文也都協調和傳送完畢,按理可以直接回到CLOSED狀態(就好比從SYN_SEND狀態到ESTABLISH狀態那樣);但是因為我們必須要假想網路是不可靠的,你無法保證你最後傳送的ACK報文會一定被對方收到,因此對方處於LAST_ACK狀態下的SOCKET可能會因為超時未收到ACK報文,而重發FIN報文,所以這個TIME_WAIT狀態的作用就是用來重發可能丟失的ACK報文。