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 按固定长度读写数据,避免粘包问题。java网络原理4_第1张图片

 

 

你可能感兴趣的:(java,开发语言,学习,网络,网络)