學(xué)習(xí)鏈接
這篇介紹TPC和UDP的文章,講解得十分詳細(xì)易懂:Android 網(wǎng)絡(luò)編程之TCP、UDP詳解。
我在這篇文章的基礎(chǔ)上,做一些自己的總結(jié)和補(bǔ)充。
TCP部分
TCP部分主要是總結(jié)一下建立TCP連接時(shí),發(fā)送的序號(hào)值和標(biāo)志位的意思,然后在Java中一個(gè)TCP運(yùn)用的實(shí)例。
連接時(shí)的序號(hào)和標(biāo)志位
-
第一次握手:客戶端發(fā)送
SYN=1,seq=xxx,其中,SYN表示標(biāo)志位,我理解是連接有效的標(biāo)志,seq表示這次消息的序列號(hào);
第一次握手 -
第二次握手:服務(wù)端發(fā)送
SYN=1,seq=yyy ACK=1,ack=xxx+1,其中SYN和seq和前一次意思相同,這次的seq表示的是服務(wù)端的序列號(hào),ACKb表示確認(rèn)了客戶端的序列號(hào),ack的值是客戶端的序列號(hào)值+1,表示收到了此序列號(hào),希望下次傳過(guò)來(lái)的序列號(hào)是之前的+1;
第二次握手 -
第三次握手:客戶端發(fā)送
ACK=1,ack=yyy+1 seq=xxx+1,示意和上述一致。
第三次握手
為什么要三次握手
當(dāng)客戶端第一次握手時(shí),若此時(shí)網(wǎng)絡(luò)堵塞,導(dǎo)致服務(wù)端很久才收到信息,那此時(shí)客戶端可能已經(jīng)超時(shí)或其他原因放棄此次連接,而服務(wù)端并不知道客戶端等待了多久,依然和客戶端建立連接,那么此時(shí)的報(bào)文可能是失效的,導(dǎo)致此次連接是無(wú)效的,這樣會(huì)大量浪費(fèi)網(wǎng)絡(luò)資源和時(shí)間。
另外,為了保證可靠性傳輸,TCP雙方連接時(shí)都必須保證先有一個(gè)對(duì)方的起始序列號(hào)和期望的下次序列號(hào),這樣在以后的傳輸中,才能知道哪些序號(hào)的報(bào)文是接收了,哪些是沒(méi)有接收的。
因此,建立三次握手的原因是:
- 保證可靠性傳輸,獲取對(duì)方的起始序列號(hào)和下次期望收到的序列號(hào);
- 避免資源浪費(fèi),保證此次連接是有效的。
斷開(kāi)連接時(shí)的序號(hào)和標(biāo)志位
若主機(jī)A要和主機(jī)B斷開(kāi)連接,那么主機(jī)A和主機(jī)B需要發(fā)送四次消息來(lái)完成斷開(kāi)連接。
- 第一次:主機(jī)A發(fā)送信息
FIN=1 seq=p給主機(jī)B,其中FIN表示要斷開(kāi)連接,seq表示序列號(hào); - 第二次:主機(jī)B發(fā)送給主機(jī)A
ACK=1 ack=p+1,表示收到了此信息; - 第三次:主機(jī)B發(fā)送給主機(jī)A
FIN=1, seq=q, ACK=1, ack=p+1; - 第四次:主機(jī)A發(fā)送給主機(jī)B:
ACK=1,ack=q+1,seq=u+1,并等待2*最大報(bào)文壽命時(shí)間后關(guān)閉連接。
其中:
- 第一次消息主機(jī)A告訴主機(jī)B我要斷開(kāi)連接了;
- 第二次消息主機(jī)B告訴主機(jī)A我收到了,此時(shí)主機(jī)A關(guān)閉了寫通道,主機(jī)B關(guān)閉了讀通道;
- 第三次消息主機(jī)B告訴主機(jī)A我要斷開(kāi)連接了;
- 第四次消息主機(jī)A告訴主機(jī)B我收到了,此時(shí)主機(jī)A關(guān)閉讀通道,主機(jī)B關(guān)閉寫通道。
為什么要四次才釋放連接
若主機(jī)A第一次消息告訴主機(jī)B我要斷開(kāi)連接后,就關(guān)閉讀和寫通道,則由于網(wǎng)絡(luò)原因,主機(jī)B可能收不到消息,還在等待A的消息,或者還在給A發(fā)消息,此時(shí)就會(huì)引發(fā)網(wǎng)絡(luò)錯(cuò)誤,因此主機(jī)A不能關(guān)閉讀通道,需要等待B發(fā)過(guò)來(lái)的確認(rèn)消息后才能關(guān)閉。因此,A關(guān)閉寫通道需要2次消息,同理,B關(guān)閉寫通道也需要2次。
TCP在Java中的代碼
服務(wù)端:
ServerSocket serverSocket = null;
boolean isEnd = false;
try {
serverSocket = new ServerSocket(14455);
while (!isEnd) {
Socket dataSocket = serverSocket.accept(); // 等待客戶端連接
// 讀取客戶端傳來(lái)的數(shù)據(jù)
InputStream is = dataSocket.getInputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(is));
String message = input.readLine();
System.out.println("服務(wù)端收到消息:" + message);
// 給客戶端寫數(shù)據(jù)
OutputStream os = dataSocket.getOutputStream();
PrintWriter output = new PrintWriter(os, true);
output.println("服務(wù)端收到了!");
dataSocket.close();
}
serverSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
客戶端:
try {
Socket socket = new Socket("localhost", 14455);
OutputStream os = socket.getOutputStream();
PrintWriter output = new PrintWriter(os, true);
output.println("我是客戶端!");
InputStream is = socket.getInputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(is));
String message = input.readLine();
System.out.println("客戶端收到消息: " + message);
input.close();
} catch (IOException e) {
e.printStackTrace();
}
執(zhí)行結(jié)果:
服務(wù)端收到消息:我是客戶端!
客戶端收到消息: 服務(wù)端收到了!
UDP部分
UDP和TCP之間的區(qū)別:
| 對(duì)比 | TCP | UDP |
|---|---|---|
| 連接 | 有連接 | 無(wú)連接 |
| 可靠性 | 可靠 | 不可靠 |
| 性能 | 相對(duì)較低 | 相對(duì)較高 |
| 流量 | 相對(duì)多 | 相對(duì)少 |
| 連接對(duì)象 | 一對(duì)一 | 一對(duì)一、多對(duì)多 |
UDP在Java中的代碼
服務(wù)端:
byte[] bytes = new byte[1024];
try {
DatagramSocket socket = new DatagramSocket(14455);
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
while (true) {
socket.receive(packet);
System.out.println("服務(wù)端收到:" + new String(packet.getData(), "utf-8"));
}
} catch (Exception e) {
e.printStackTrace();
}
客戶端代碼:
DatagramSocket socket = null;
try {
socket = new DatagramSocket();
InetAddress address = InetAddress.getByName("localhost");
String message = new String("聽(tīng)媽媽的話");
byte[] bytes = message.getBytes();
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, address, 14455);
//客戶端發(fā)消息
socket.send(packet);
} catch (Exception e) {
e.printStackTrace();
} finally {
socket.close();
}


