更新時(shí)間:2023年04月19日13時(shí)48分 來源:傳智教育 瀏覽次數(shù):
TCP粘包是指發(fā)送方在發(fā)送數(shù)據(jù)時(shí),將多個(gè)小數(shù)據(jù)包粘合成一個(gè)大數(shù)據(jù)包發(fā)送到接收方,或者接收方在接收數(shù)據(jù)時(shí),將一個(gè)大數(shù)據(jù)包拆分成多個(gè)小數(shù)據(jù)包。這種情況常常發(fā)生在TCP數(shù)據(jù)流傳輸?shù)倪^程中。
TCP協(xié)議本身是一種基于流的傳輸協(xié)議,它并沒有像UDP那樣的數(shù)據(jù)報(bào)文概念。在發(fā)送方發(fā)送數(shù)據(jù)時(shí),TCP會(huì)將數(shù)據(jù)分成一個(gè)個(gè)小的TCP數(shù)據(jù)包進(jìn)行傳輸,并且不保證這些小的TCP數(shù)據(jù)包按照發(fā)送順序到達(dá)接收方。在接收方接收數(shù)據(jù)時(shí),TCP會(huì)將接收到的數(shù)據(jù)按照TCP數(shù)據(jù)包的順序重新組合成一個(gè)完整的數(shù)據(jù)流。
由于TCP傳輸?shù)臄?shù)據(jù)是一個(gè)流,而不是數(shù)據(jù)包,所以在數(shù)據(jù)發(fā)送和接收的過程中,會(huì)存在數(shù)據(jù)包的大小和數(shù)量不一致的情況,從而導(dǎo)致TCP粘包的問題。
以下是一個(gè)簡單的Java代碼演示TCP粘包的問題:
import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class TcpServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8888); while (true) { Socket socket = serverSocket.accept(); new Thread(() -> { try { InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = inputStream.read(buffer)) != -1) { System.out.println(new String(buffer, 0, len)); outputStream.write(buffer, 0, len); outputStream.flush(); } socket.close(); } catch (IOException e) { e.printStackTrace(); } }).start(); } } }
上述代碼中,我們創(chuàng)建了一個(gè)TCP服務(wù)器,監(jiān)聽本地的8888端口。在接收到客戶端的連接之后,我們使用一個(gè)新的線程來處理這個(gè)連接。在這個(gè)線程中,我們通過輸入流從客戶端接收數(shù)據(jù),并且通過輸出流將數(shù)據(jù)發(fā)送回客戶端。
現(xiàn)在,我們使用另外一個(gè)Java程序來模擬一個(gè)客戶端連接:
import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; public class TcpClient { public static void main(String[] args) throws IOException { Socket socket = new Socket("localhost", 8888); OutputStream outputStream = socket.getOutputStream(); for (int i = 0; i < 10; i++) { String message = "Hello, World!"; byte[] buffer = message.getBytes(); outputStream.write(buffer); outputStream.flush(); } InputStream inputStream = socket.getInputStream(); byte[] buffer = new byte[1024]; int len = inputStream.read(buffer); System.out.println(new String(buffer, 0, len)); socket.close(); } }
上述代碼中,我們創(chuàng)建了一個(gè)TCP客戶端,連接到本地的8888端口。在連接建立之后,我們向服務(wù)器發(fā)送10個(gè)"Hello, World!"字符串。在發(fā)送完這些數(shù)據(jù)之后,我們從服務(wù)器端的角度來看,當(dāng)接收到客戶端發(fā)送的數(shù)據(jù)時(shí),我們使用一個(gè)1024字節(jié)大小的緩沖區(qū)來接收數(shù)據(jù)。在讀取數(shù)據(jù)時(shí),我們使用一個(gè)while循環(huán)來不斷從輸入流中讀取數(shù)據(jù),并將其輸出到控制臺(tái)上。在輸出數(shù)據(jù)時(shí),我們并沒有做任何的數(shù)據(jù)處理,而是直接將數(shù)據(jù)輸出到控制臺(tái)上。
在客戶端發(fā)送數(shù)據(jù)時(shí),我們發(fā)送了10個(gè)"Hello, World!"字符串。由于TCP是一種基于流的協(xié)議,所以這些數(shù)據(jù)可能會(huì)被組合成一個(gè)大的數(shù)據(jù)包發(fā)送到服務(wù)器端。在接收數(shù)據(jù)時(shí),服務(wù)器端每次從輸入流中讀取1024個(gè)字節(jié)大小的數(shù)據(jù),然后直接輸出到控制臺(tái)上。如果客戶端發(fā)送的數(shù)據(jù)包的大小小于1024字節(jié),那么這些數(shù)據(jù)可能會(huì)和后面的數(shù)據(jù)一起被讀取,從而導(dǎo)致粘包的問題。
為了演示這個(gè)問題,我們可以將客戶端發(fā)送的數(shù)據(jù)包大小改為比緩沖區(qū)大小小的數(shù)據(jù),例如:
for (int i = 0; i < 10; i++) { String message = "Hello, World!"; byte[] buffer = message.getBytes(); outputStream.write(buffer, 0, buffer.length / 2); outputStream.write(buffer, buffer.length / 2, buffer.length / 2); outputStream.flush(); }
在上述代碼中,我們將每個(gè)"Hello, World!"字符串拆分成兩個(gè)大小相等的字節(jié)數(shù)組發(fā)送到服務(wù)器端。這樣,每個(gè)數(shù)據(jù)包的大小就會(huì)小于1024字節(jié)。運(yùn)行客戶端程序后,我們可以看到服務(wù)器端輸出了一些類似于以下的輸出:
Hello, Wo rld!Hello, World!Hel lo, World!H ello, Worl d!Hello, Wo rld!Hello, World!Hello , World!Hel lo, World!H ello, Worl d!Hello, Wo rld!Hello, World!Hello , World!Hel lo, World!
從上面的輸出中,我們可以看到"Hello, World!"字符串被拆分成了多個(gè)部分,從而導(dǎo)致了TCP粘包的問題。
北京校區(qū)