java网络原理4
一、TCP连接异常处理
1.1四次挥手异常:
正常的四次挥手完成后,双方可确认连接关闭并删除保存的信息。若挥手过程中,一方发送的FIN未收到ack,会尝试重传几次FIN。若最终仍未成功挥手,至少自己可删除保存的信息,而对端若关机,其内存数据会丢失。
1.2掉电情况处理
- 接收方掉电:若接收方掉电,发送方继续发送数据却收不到ack,超时重传后仍无ack,达到一定程度,发送方会发送复位报文,放弃当前连接并重新连接。
- 发送方掉电:接收方会感知到发送方突然停止发送数据,会继续阻塞等待新数据。此时引入心跳包机制,接收方和发送方周期性交换“心跳包” (A发无业务数据报文,B返回ack ) ,若对方无应答,接收方可以单方面释放连接。此机制在分布式系统中尤为重要,用于判断节点存活。
二、IP协议基础
2.1 IP报头:
IP报头最大长度60字节,其中0 - 15位占4字节,且只有4位有效。8位服务类型(ToS )字段中,4位优先权字段已弃用,剩下4位分别表示最小延时、最大吞吐量、最高可靠性、最小成本(四者互斥,只能选其一 ) 。例如,对于ssh/telnet这类应用,最小延时较重要;对于文件传输等,最大吞吐量更关键。
2.2 IP数据包长度与拆包:
IP数据包由报头和载荷组成,单个IP数据报有长度限制,但当携带超过64KB载荷数据时,IP协议支持拆包/组包。传输层数据包太长时,IP自动拆成多个数据报,每个携带部分数据,接收方依据16位标识、3位标志、13位片偏移三个属性进行组包。
2.3生存时间(TTL ):
TTL表示当前IP数据报在网络上的存活次数,每经路由器转发一次,TTL减1 ,为0时若未到达对方便丢弃。可通过 traceroute (Linux )或 tracert (Windows )命令追踪数据报转发路径及中间节点。
三、TCP传输效率优化机制
3.1延时应答:
为提高传输效率,TCP不立即返回ack,而是稍作等待。这样给接收方时间处理更多数据,使接收缓冲区剩余空间更大,让窗口尽量大些(在保证可靠性前提下 ) 。
3.2 捎带应答:
基于延时应答,进一步优化传输效率。在网络通信“一问一答”模型中,服务器收到客户端请求后,不是立即返回ack,而是等计算出响应后,将ack和响应一起返回,减少报文数量。但捎带应答不是100%触发,取决于延时应答和应用程序处理逻辑。
四、TCP面向字节流及粘包问题
4.1 面向字节流特性:
TCP读取/写入操作灵活,如读100字节数据,可一次读10字节,分10次完成;或一次读20字节,分5次完成等。
4.2粘包问题:
接收方接收多个TCP数据报时,去除报头后将载荷放接收缓冲区,由于TCP字节流特性,应用层数据包边界模糊,出现粘包现象。例如发送方发送aaa、bbb、ccc三个应用层载荷,接收方读取时可能有多种组合方式。
- 粘包问题解决:从应用层入手,合理设计协议。如使用特殊分隔符(如 \n )区分包边界;或在应用层数据包开头约定固定长度,接收方先读固定长度确定后续读取字节数,确保读到完整应用层数据包 。UDP不存在粘包问题,因其接收缓冲区机制与TCP不同。
五、TCP协议总结及应用场景
5.1 核心机制总结:
TCP协议包括确认应答、超时重传、连接管理(三次握手、四次挥手 ) 、滑动窗口、流量控制、拥塞控制、延时应答、捎带应答、面向字节流(粘包问题 ) 、异常情况(心跳包 )等核心机制。
5.2 协议对比与应用选择:
TCP有连接、可靠传输、面向字节流,适用于大部分需可靠传输场景;UDP无连接、不可靠传输、面向数据报,传输效率高,适用于机房内部等对丢包不敏感、效率要求高场景。还有如KCP等协议,适用于既需一定可靠性又要求高效率的场景,如竞技类网游。
六、代码示例(以Java为例解决粘包问题 )
6.1.使用分隔符解决粘包问题
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class DelimiterBasedServer {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8080)) {
System.out.println("Server started. Waiting for client...");
try (Socket clientSocket = serverSocket.accept()) {
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
String request;
while ((request = in.readLine()) != null) {
// 处理请求逻辑
System.out.println("Received: " + request);
// 构造响应并发送
out.println("Response to: " + request);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class DelimiterBasedClient {
public static void main(String[] args) {
try (Socket socket = new Socket("localhost", 8080)) {
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
String request = "Hello, Server";
// 发送请求
out.println(request);
// 接收响应
String response = in.readLine();
System.out.println("Received response: " + response);
} catch (IOException e) {
e.printStackTrace();
}
}
}
上述代码中,服务器端用 BufferedReader 的 readLine 方法读取数据,以 \n 作为分隔符,客户端通过 println 方法发送数据,保证数据按行分隔,解决粘包问题。
6.2.使用固定长度解决粘包问题
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class FixedLengthBasedServer {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8080)) {
System.out.println("Server started. Waiting for client...");
try (Socket clientSocket = serverSocket.accept()) {
DataInputStream in = new DataInputStream(clientSocket.getInputStream());
DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream());
// 假设约定数据长度为10字节
byte[] buffer = new byte[10];
in.readFully(buffer);
String request = new String(buffer).trim();
// 处理请求逻辑
System.out.println("Received: " + request);
// 构造响应并发送
String response = "Response to: " + request;
byte[] responseBytes = response.getBytes();
out.write(responseBytes);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
public class FixedLengthBasedClient {
public static void main(String[] args) {
try (Socket socket = new Socket("localhost", 8080)) {
DataInputStream in = new DataInputStream(socket.getInputStream());
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
String request = "Hello, Server";
// 补全到固定长度10字节
request = String.format("%10s", request);
byte[] requestBytes = request.getBytes();
out.write(requestBytes);
// 接收响应
byte[] responseBuffer = new byte[10];
in.readFully(responseBuffer);
String response = new String(responseBuffer).trim();
System.out.println("Received response: " + response);
} catch (IOException e) {
e.printStackTrace();
}
}
}
在该代码中,服务器端和客户端约定数据长度(这里假设为10字节 ) DataInputStream 和 DataOutputStream 按固定长度读写数据,避免粘包问题。