一、面向字节流
如何理解TCP是面向字节流协议
之所以说TCP是面向字节流协议,UDP是面向报文协议。主要是因为发送方的发送消息的机制不同
UDP是面向报文协议
udp在发送消息时,在传输层直接就将一个消息打包成一个完整的包,组装好udp头部,不进行切割,就转发给网络层。也就是每一个UDP报文就是一个消息
。服务端在接收到udp报文时,会将它放到一个队列中,一个元素就是一个udp报文。每次读取时,读取一个元素
TCP是面向字节流协议
当tcp在传输层发送消息时,一个消息可能会被分割成多个tcp报文进行转发给网络层。我们不能认为一个tcp报文就是一个消息,所以tcp是面向字节流协议
由于一个消息对应的不是一个tcp报文,如果接受方不知道一个消息的长度或者分割的边界在哪里,就会无法组装成一个消息,这就形成了粘包问题
二、粘包问题解决
三种方式:
- 固定长度的消息
- 特殊字符当做边界
- 自定义消息结构
固定长度的消息
将消息长度固定,比如规定一个消息长度为64字节,那么只要接收到了64字节,就认为这个内容是一个完整的消息
特殊字符作为边界
我们可以在两个消息之间加入一个特殊字符,如果读到了这个特殊字符,则认为一个消息已经完整的接收到了。
比如我们的http
HTTP通过回车换行来判断消息边界。
需要注意的是,如果消息内容中含有分割消息的特殊字符的话,需要进行转义处理,否则则会错误判断消息边界
自定义消息结构
我们可以自定义消息结构,由包头和数据来构成,包头里有一个字段表示紧随其后的一个消息有多大。
当接收方接收到包头时,就会解析包头里的数据长度,接下来就读取数据,知道读取到指定长度时,就是一个完整的消息
TCP是一种面向字节流的协议,它将数据视为连续的字节流,而不是分割成消息。这意味着在发送端,应用程序将数据写入TCP连接时,TCP会将数据转换为字节流,并在接收端将字节流重新转换回数据。
面向字节流的特性使得TCP非常灵活,可以传输任意类型的数据,包括文本、图像、音频等。然而,正是由于面向字节流的特性,导致了粘包问题的出现。
下面是一个简单的Java代码示例,演示了粘包问题的可能场景:
import java.io.*;
import java.net.*;
public class TCPServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8888);
Socket socket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println("Received message: " + inputLine);
}
socket.close();
serverSocket.close();
}
}
import java.io.*;
import java.net.*;
public class TCPClient {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("localhost", 8888);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("Message 1");
out.println("Message 2");
out.println("Message 3");
socket.close();
}
}
在这个示例中,客户端连续发送了三条消息给服务器端,但由于TCP的面向字节流特性,这三条消息可能会被合并成一个数据包发送给服务器端,从而造成粘包问题。
三、解决方法
解决粘包问题的方法有多种,常用的方法包括消息边界标记、消息长度标记和使用消息头等。其中,消息边界标记是一种常见且简单的解决方法,即在每条消息的末尾添加特定的分隔符,如换行符(\n),接收端根据分隔符来识别消息的边界。
下面是一个使用消息边界标记解决粘包问题的Java代码示例:
import java.io.*;
import java.net.*;
public class TCPClient {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("localhost", 8888);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("Message 1\n");
out.println("Message 2\n");
out.println("Message 3\n");
socket.close();
}
}
在这个示例中,客户端在每条消息的末尾添加了换行符(\n),接收端可以根据换行符来识别消息的边界,从而正确解析数据,避免了粘包问题的出现。