背景

本文是《Java 后端从小白到大神》修仙系列第十八篇,正式进入Java后端世界,本篇文章主要聊Java基础。若想详细学习请点击首篇博文,我们开始把。

文章概览

  1. 网络编程

Java 网络编程

1. 网络分层

先认识网络分层,共4层。

graph BT
    A[物理层] --> B[网际层 IP] --> C[传输层 TCP UDP] --> D[应用层]

2. 网络中不同层的协议

graph BT
    A[The Internet] --> B[PPP]
    A --> C[WIFI]
    A --> D[Ethernet]
    B <--> E[IP]
    C <--> E 
    D <--> E
    D <--> F[ARP]
    E --> G[TCP]
    E <--> H[UDP]
    E --> I[ICMP]
    G <--> J[FTP]
    G <--> K[HTTP]
    G <--> L[SMTP]
    H <--> M[DNS]
    H <--> N[NFS]
    I <--> O[TraceRoute]
    I <--> P[Ping]
    K --> Q[SOAP]
    K --> R[XML-RPC]
    K --> S[REST APIs]

3. Web向服务器请求流程

flowchart TB
    subgraph 模块1
        A[Web浏览器] --> B[本地传输层<br>(将请求分解为TCP片,并向数据添加序列号校验和)]
        B --> C[本地网际层]
    end

    subgraph 模块2
        D[本地网际层<br>(将各TCP分片分成IP数据报)] --> E[本地主机网络层<br>(将IP数据报转码为模拟信号)]
        E --> F[线缆]
    end

    subgraph 模块3
        G[线缆] --> H[目标主机网络层<br>(将模拟信号解码为IP数据报)]
        H --> I[目标主机网际层]
    end

    subgraph 模块4
        J[目标主机网际层] --> K[目标主机传输层]
        K --> L[数据重组写入流] --> M[应用层Web服务器]
    end

    模块1 -->|流转| 模块2
    模块2 -->|流转| 模块3
    模块3 -->|流转| 模块4

4. HTTP 协议

HTTP(HyperText Transfer Protocol) 是用于客户端和服务器之间传输超文本(如 HTML)的应用层协议。它是无状态的(默认不保留会话信息),基于 TCP/IP,采用请求-响应模型。

1. 请求格式

请求行 + 请求头 + 空行 + 请求体(可选)

请求行:方法 URI HTTP版本

1
GET /index.html HTTP/1.1
  • 方法:如 GET、POST。
  • URI:请求资源的路径(如 /index.html)。
  • HTTP版本:如 HTTP/1.1。

请求头:键值对形式,每行一个字段

1
2
3
4
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html
Cookie: name=value
  • Host:目标服务器域名(必填)。
  • User-Agent:客户端标识。
  • Accept:客户端可接受的内容类型。
  • Cookie:携带服务器设置的 Cookie。

空行:表示请求头结束,格式为 \r\n。

请求体(Body):POST/PUT 等方法携带数据:username=admin&password=123

完整请求示例

1
2
3
4
5
6
POST /login HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 28

username=admin&password=123

2. 响应格式

响应格式:状态行 + 响应头 + 空行 + 响应体。

状态行:HTTP版本 状态码 状态描述

1
HTTP/1.1 200 OK
  • HTTP/1.1 200 OK
  • 状态码:如 200(成功)、404(未找到)

响应头:键值对形式

1
2
Content-Type: text/html
Set-Cookie: sessionid=abc123
  • Content-Type:响应体的类型(如 text/html)。
  • Set-Cookie:向客户端设置 Cookie。

空行:分隔头和响应体。

响应体:实际返回的数据(如 HTML、JSON)

1
<html>Welcome</html>

完整响应示例

1
2
3
4
5
6
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 20
Set-Cookie: sessionid=abc123

<html>Welcome</html>

3. HTTP 常用方法

方法 作用 代码示例(Python requests)
GET 获取资源 requests.get(url)
POST 提交数据(如表单) requests.post(url, data={'key':'value'})
PUT 替换整个资源 requests.put(url, json={'data':'new'})
DELETE 删除资源 requests.delete(url)
PATCH 部分更新资源 requests.patch(url, json={'field':'value'})
HEAD 获取响应头(无 Body) requests.head(url)
OPTIONS 查询服务器支持的方法 requests.options(url)

4. Cookie作用

作用

  1. 会话管理(如用户登录状态)。
  2. 个性化设置(如语言偏好)。
  3. 行为跟踪(如广告推荐)。

在请求和响应中的位置

  1. 通过 Cookie: name=value 头字段携带。
  2. 服务器通过 Set-Cookie: name=value; [属性] 头字段设置 Cookie。

示例

  • 请求头携带 Cookie:

    1
    2
    3
    
    GET /profile HTTP/1.1
    Host: www.example.com
    Cookie: sessionid=abc123
    
  • 响应头设置 Cookie:

    1
    2
    
    HTTP/1.1 200 OK
    Set-Cookie: sessionid=abc123; Path=/; HttpOnly
    

5. HTTP 客户端工具

1. 命令行工具

curl:是一个强大的命令行工具,用于传输数据与服务器进行通信。

  • 特点:

    • 支持多种协议(HTTP、HTTPS、FTP 等)。
    • 可以发送 GET、POST、PUT、DELETE 等请求。
    • 支持自定义请求头、表单数据、文件上传等。
1
2
curl -X GET http://example.com/api/data
curl -X POST -d "param1=value1&param2=value2" http://example.com/api/data

wget:是一个非交互式的网络下载工具。

  • 特点
    • 主要用于下载文件。
    • 支持 HTTP、HTTPS 协议。
    • 可以递归下载整个网站。
1
2
wget http://example.com/file.txt
wget -O output.txt http://example.com/file.txt

httpie:是一个用户友好的命令行 HTTP 客户端。

  • 特点
    • 支持多种请求方法(GET、POST、PUT、DELETE 等)。
    • 自动格式化 JSON 响应。
    • 支持自定义请求头、表单数据、文件上传等。
1
2
http GET http://example.com/api/data
http POST http://example.com/api/data param1=value1 param2=value2
2. Java 库

HttpURLConnection:Java 标准库中的 java.net.HttpURLConnection 类用于发送 HTTP 请求。

  • 特点
    • 内置在 Java 标准库中,无需额外依赖。
    • 支持 HTTP 和 HTTPS 协议。
    • 支持多种请求方法(GET、POST、PUT、DELETE 等)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class HttpURLConnectionExample {
    public static void main(String[] args) {
        try {
            URL url = new URL("http://example.com/api/data");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");

            int responseCode = connection.getResponseCode();
            System.out.println("Response Code: " + responseCode);

            BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String inputLine;
            StringBuffer response = new StringBuffer();

            while ((inputLine = in.readLine()) != null) {
                response.append(inputLine);
            }
            in.close();

            System.out.println("Response: " + response.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
3. HttpClient

HttpClient:Apache HttpClient 是一个功能强大的 HTTP 客户端库。

  • 特点
    • 支持 HTTP 和 HTTPS 协议。
    • 支持多种请求方法(GET、POST、PUT、DELETE 等)。
    • 支持连接池、认证、代理等高级功能。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class ApacheHttpClientExample {
    public static void main(String[] args) {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpGet request = new HttpGet("http://example.com/api/data");

            try (CloseableHttpResponse response = httpClient.execute(request)) {
                System.out.println("Response Code: " + response.getStatusLine().getStatusCode());

                String responseBody = EntityUtils.toString(response.getEntity());
                System.out.println("Response Body: " + responseBody);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
4. OkHttp

OkHttp:是一个高效的 HTTP 客户端库。

  • 特点:
    • 支持 HTTP/2 协议。
    • 支持连接池、缓存、重定向等高级功能。
    • 支持同步和异步请求。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class OkHttpExample {
    public static void main(String[] args) {
        OkHttpClient client = new OkHttpClient();

        Request request = new Request.Builder()
                .url("http://example.com/api/data")
                .build();

        try (Response response = client.newCall(request).execute()) {
            if (response.isSuccessful()) {
                System.out.println("Response Code: " + response.code());
                System.out.println("Response Body: " + response.body().string());
            } else {
                System.out.println("Request failed: " + response.code());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

6. 与HTTPS对比

对比项 HTTP HTTPS
安全性 明文传输,无加密 加密传输,防窃听、篡改
默认端口 80 443
证书 无需证书 需 CA 签发 SSL/TLS 证书
性能 更快 略慢(现代优化后差距缩小)
SEO 与信任度
适用场景 非敏感信息传输 登录、支付、API 等安全场景

5. WebSocket 协议

WebSocket:是一种基于 TCP 的应用层协议,提供全双工通信(客户端和服务器可同时双向传输数据),旨在解决 HTTP 协议在实时通信中的局限性(如频繁轮询、高延迟)。

  • 核心特点
    1. 持久化连接:一次握手后保持长连接,避免重复建立连接的开销。
    2. 低延迟:支持服务器主动推送数据,无需客户端轮询。
    3. 轻量级:数据帧头部开销小(最小仅 2 字节)。

1. 协议流程

  1. 握手阶段

    • 客户端通过 HTTP 请求升级协议(Upgrade: websocket)。

    • 服务器返回 101 Switching Protocols 确认协议升级。

    • 示例:

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      
      # 客户端握手请求
      GET /chat HTTP/1.1
      Host: example.com
      Upgrade: websocket
      Connection: Upgrade
      Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
      Sec-WebSocket-Version: 13
      
      # 服务器握手响应
      HTTP/1.1 101 Switching Protocols
      Upgrade: websocket
      Connection: Upgrade
      Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
      
  2. 数据传输阶段

    • 通过数据帧(Frame)传输消息,支持文本、二进制等格式。

    • 数据帧格式(简化为关键字段):

      1
      2
      
      | FIN (1bit) | RSV (3bits) | Opcode (4bits) | Mask (1bit) | Payload Len (7/16/64 bits) | 
      | Masking Key (0/4 bytes) | Payload Data (N bytes) |
      
      • FIN:是否为消息的最后一帧。
      • Opcode:数据类型(0x1=文本,0x2=二进制,0x8=关闭连接)。
      • Mask:客户端发送数据时需掩码(服务器返回无需掩码)。
      • Payload Len:数据长度(根据长度决定占用字节数)。

2. 发送与返回数据格式

  1. 客户端发送文本消息(掩码处理):

    1
    2
    3
    
    Frame Header: 0x81 0x85 (FIN=1, Opcode=0x1, Mask=1, Payload Len=5)
    Masking Key: 0x37 0xfa 0x21 0x3d
    Payload Data: [0x7f 0x9a 0x53 0x54 0x65] → 解码后为 "Hello"
    
  2. 服务器返回文本消息(无掩码):

    1
    2
    
    Frame Header: 0x81 0x05 (FIN=1, Opcode=0x1, Payload Len=5)
    Payload Data: "World"
    

3. 代码示例

基于 Jetty WebSocket API 的 Java 服务端和客户端实现。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// 服务端代码
public class WebSocketClientExample {

    public static void main(String[] args) {
        WebSocketClient client = new WebSocketClient();
        CountDownLatch latch = new CountDownLatch(1); // 创建一个计数器
        try {
            client.start(); // 启动客户端

            URI serverUri = new URI("ws://localhost:8080/"); // 服务器地址
            ClientSocket socket = new ClientSocket(latch);

            // 发起连接请求
            ClientUpgradeRequest request = new ClientUpgradeRequest();
            client.connect(socket, serverUri, request);

            // 等待连接关闭(最多 10 秒)
            if (!latch.await(10, TimeUnit.SECONDS)) {
                System.out.println("连接超时");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                client.stop(); // 关闭客户端
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // 客户端 WebSocket 处理类
    @WebSocket
    public static class ClientSocket {

        private final CountDownLatch latch;

        public ClientSocket(CountDownLatch latch) {
            this.latch = latch;
        }

        @OnWebSocketConnect
        public void onConnect(Session session) {
            System.out.println("已连接到服务器");
            try {
                // 发送测试消息
                session.getRemote().sendString("Hello from Java Client");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @OnWebSocketMessage
        public void onMessage(String message) {
            System.out.println("收到服务器回复: " + message);
        }

        @OnWebSocketClose
        public void onClose(int statusCode, String reason) {
            System.out.println("连接关闭: " + reason);
            latch.countDown(); // 连接关闭时减少计数器
        }
    }
}

// 客户端代码
public class WebSocketServer {

    public static void main(String[] args) throws Exception {
        Server server = new Server(8080); // 监听 8080 端口

        // 配置 WebSocket 处理器
        WebSocketHandler wsHandler = new WebSocketHandler() {
            @Override
            public void configure(WebSocketServletFactory factory) {
                factory.register(ChatSocket.class); // 注册 WebSocket 类
            }
        };

        // 设置上下文路径
        ContextHandler context = new ContextHandler();
        context.setContextPath("/");
        context.setHandler(wsHandler);
        server.setHandler(context);

        server.start(); // 启动服务器
        server.join(); // 阻塞主线程
    }

    // WebSocket 处理类
    @WebSocket
    public static class ChatSocket {

        @OnWebSocketConnect
        public void onConnect(Session session) {
            System.out.println("客户端连接: " + session.getRemoteAddress());
        }

        @OnWebSocketMessage
        public void onMessage(Session session, String message) {
            System.out.println("收到消息: " + message);
            try {
                // 回复客户端
                session.getRemote().sendString("服务器已收到: " + message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @OnWebSocketClose
        public void onClose(int statusCode, String reason) {
            System.out.println("连接关闭: " + reason);
        }
    }
}

3. 使用场景

  1. 实时聊天应用:消息即时推送(如微信、Slack)。
  2. 在线游戏:玩家状态同步、实时对战。
  3. 协同编辑工具:多人同时编辑文档(如 Google Docs)。
  4. 股票行情推送:实时更新价格、交易数据。
  5. IoT 设备监控:传感器数据实时上报与控制。

6. Socket 编程(TCP/UDP)

基于 java.net 的网络编程,基本类:

  • InetAddress:表示 IP 地址。
  • Socket:用于客户端与服务器之间的通信。
  • ServerSocket:用于服务器端,监听客户端连接请求。
  • DatagramSocket:用于 UDP 通信。
  • DatagramPacket:表示 UDP 数据包。

1.TCP

  • TCP (Transmission Control Protocol),面向连接的协议。
  • 提供可靠的数据传输,保证数据按顺序到达且无错误。
  • 适用于需要可靠传输的应用,如 Web 浏览器和服务器之间的通信。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 基于 java.net 的网络编程:TCP客户端
public class TCPSocketClient {
    public static void main(String[] args) {
        String serverAddress = "127.0.0.1";
        int port = 12345;

        try (Socket socket = new Socket(serverAddress, port);
             PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in))) {

            String userInput;
            while ((userInput = stdIn.readLine()) != null) {
                out.println(userInput);
                System.out.println("Server response: " + in.readLine());
            }
        } catch (UnknownHostException e) {
            System.err.println("Unknown host: " + serverAddress);
        } catch (IOException e) {
            System.err.println("Couldn't get I/O for the connection to " + serverAddress);
        }
    }
}

// 基于 java.net 的网络编程:TCP服务端
public class TCPSocketServer {
    public static void main(String[] args) {
        int port = 12345;

        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("Server is listening on port " + port);

            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println("New client connected");

                new Thread(new ClientHandler(socket)).start();
            }
        } catch (IOException e) {
            System.err.println("Server exception: " + e.getMessage());
        }
    }
}

class ClientHandler implements Runnable {
    private Socket socket;

    public ClientHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try (PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {

            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                System.out.println("Received from client: " + inputLine);
                out.println("Echo: " + inputLine);
            }
        } catch (IOException e) {
            System.err.println("Exception in client handler: " + e.getMessage());
        }
    }
}

2.UDP

  • UDP (User Datagram Protocol),无连接的协议。
  • 提供不可靠的数据传输,不保证数据按顺序到达且无错误。
  • 适用于实时性要求高的应用,如视频流、在线游戏。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// UDP 客户端
import java.io.*;
import java.net.*;

public class UDPSocketClient {
    public static void main(String[] args) {
        String serverAddress = "127.0.0.1";
        int port = 12345;

        try (DatagramSocket socket = new DatagramSocket()) {
            byte[] sendData = "Hello, Server!".getBytes();
            InetAddress serverInetAddress = InetAddress.getByName(serverAddress);
            DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, serverInetAddress, port);
            socket.send(sendPacket);

            byte[] receiveData = new byte[1024];
            DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
            socket.receive(receivePacket);

            String response = new String(receivePacket.getData(), 0, receivePacket.getLength());
            System.out.println("Server response: " + response);
        } catch (UnknownHostException e) {
            System.err.println("Unknown host: " + serverAddress);
        } catch (IOException e) {
            System.err.println("IOException: " + e.getMessage());
        }
    }
}

// UDP 服务端
import java.io.*;
import java.net.*;

public class UDPSocketServer {
    public static void main(String[] args) {
        int port = 12345;

        try (DatagramSocket socket = new DatagramSocket(port)) {
            System.out.println("UDP Server is listening on port " + port);

            byte[] receiveData = new byte[1024];
            DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);

            while (true) {
                socket.receive(receivePacket);
                String received = new String(receivePacket.getData(), 0, receivePacket.getLength());
                System.out.println("Received from client: " + received);

                InetAddress clientAddress = receivePacket.getAddress();
                int clientPort = receivePacket.getPort();
                String response = "Echo: " + received;
                byte[] sendData = response.getBytes();
                DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, clientAddress, clientPort);
                socket.send(sendPacket);
            }
        } catch (IOException e) {
            System.err.println("Server exception: " + e.getMessage());
        }
    }
}

3. NIO

参考第八篇NIO流程图

基于 java.nio 的网络编程,基本类:

  • Selector:用于管理多个通道。
  • Channel:表示一个连接到实体(如文件、套接字)的开放连接。
  • ByteBuffer:用于读写数据。
  • SocketChannel:用于 TCP 通信。
  • ServerSocketChannel:用于 TCP 服务器。
  • DatagramChannel:用于 UDP 通信
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// NIO TCP 服务器示例
public class NIOServer {
    public static void main(String[] args) {
        int port = 12345;

        try (Selector selector = Selector.open();
             ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {

            serverSocketChannel.bind(new InetSocketAddress(port));
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("NIO Server is listening on port " + port);

            while (true) {
                selector.select();

                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

                while (keyIterator.hasNext()) {
                    SelectionKey key = keyIterator.next();

                    if (key.isAcceptable()) {
                        acceptConnection(serverSocketChannel, selector);
                    } else if (key.isReadable()) {
                        readData(key);
                    }

                    keyIterator.remove();
                }
            }
        } catch (IOException e) {
            System.err.println("Server exception: " + e.getMessage());
        }
    }

    private static void acceptConnection(ServerSocketChannel serverSocketChannel, Selector selector) throws IOException {
        SocketChannel clientChannel = serverSocketChannel.accept();
        clientChannel.configureBlocking(false);
        clientChannel.register(selector, SelectionKey.OP_READ);
        System.out.println("New client connected");
    }

    private static void readData(SelectionKey key) throws IOException {
        SocketChannel clientChannel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int bytesRead = clientChannel.read(buffer);

        if (bytesRead == -1) {
            clientChannel.close();
            System.out.println("Client disconnected");
        } else {
            buffer.flip();
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
            String received = new String(data);
            System.out.println("Received from client: " + received);

            String response = "Echo: " + received;
            ByteBuffer responseBuffer = ByteBuffer.wrap(response.getBytes());
            clientChannel.write(responseBuffer);
        }
    }
}

// NIO TCP 客户端示例
public class NIOlient {
    public static void main(String[] args) {
        String host = "localhost";
        int port = 12345;

        try (Selector selector = Selector.open();
                SocketChannel socketChannel = SocketChannel.open()) {

            socketChannel.configureBlocking(false);
            socketChannel.connect(new InetSocketAddress(host, port));
            socketChannel.register(selector, SelectionKey.OP_CONNECT);

            while (true) {
                selector.select();

                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

                while (keyIterator.hasNext()) {
                    SelectionKey key = keyIterator.next();

                    if (key.isConnectable()) {
                        finishConnection(socketChannel, selector);
                    } else if (key.isWritable()) {
                        sendData(socketChannel, key);
                    } else if (key.isReadable()) {
                        readData(socketChannel, key);
                    }

                    keyIterator.remove();
                }
            }
        } catch (IOException e) {
            System.err.println("Client exception: " + e.getMessage());
        }
    }

    private static void finishConnection(SocketChannel socketChannel, Selector selector) throws IOException {
        if (socketChannel.finishConnect()) {
            System.out.println("Connected to server");
            socketChannel.register(selector, SelectionKey.OP_WRITE);
        } else {
            throw new IOException("Failed to connect to server");
        }
    }

    private static void sendData(SocketChannel socketChannel, SelectionKey key) throws IOException {
        String message = "Hello from NIClient";
        ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
        socketChannel.write(buffer);

        if (!buffer.hasRemaining()) {
            System.out.println("Message sent to server: " + message);
            key.interestOps(SelectionKey.OP_READ);
        }
    }

    private static void readData(SocketChannel socketChannel, SelectionKey key) throws IOException {
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int bytesRead = socketChannel.read(buffer);

        if (bytesRead == -1) {
            socketChannel.close();
            System.out.println("Server disconnected");
        } else {
            buffer.flip();
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
            String received = new String(data);
            System.out.println("Received from server: " + received);

            // Optionally, you can close the connection after receiving the response
            socketChannel.close();
            System.out.println("Closing connection");
        }
    }
}

7. Netty框架

1. Netty介绍

Netty 是 JBoss 开源项目,是一个异步事件驱动的高性能网络框架,基于 Reactor 线程模型设计,同时也是基于Java NIO构建的。学习Netty之前,最好对IO、TCP、Java NIO、JUC这些有一定的了解。

2. Reactor模型

Reactor模型是一种用于处理并发I/O事件的事件驱动架构模式。它最初由Doug Lea在1996年提出,广泛应用于高性能网络服务器的设计中。Reactor模型的核心思想是使用一个或多个线程来处理多个I/O事件(Java NIO也是对Reactor模型的应用),从而实现高效的并发处理。

主要特点

  • 事件驱动:Reactor模型通过事件驱动的方式处理I/O操作,当某个I/O事件(如读、写)准备好时,系统会通知应用程序进行相应的处理。
  • 单线程或多线程:Reactor模型可以基于单线程或多线程实现,但核心思想是通过一个或少数几个线程来管理大量的I/O事件。
  • 高效性:通过使用操作系统的多路复用机制(如select、poll、epoll等),Reactor模型能够高效地处理大量并发连接。
  • 可扩展性:适用于需要处理大量并发连接的场景,如Web服务器、游戏服务器等。
2. Reactor模型的组成部分
  • Reactor:负责监视多个文件描述符(如网络套接字),并分发事件到相应的处理器。
  • Handler:处理具体的I/O事件,如读取数据、写入数据等。
  • Event Source:产生I/O事件的来源,如网络套接字、文件句柄等。
3. Reactor模型的变体

单 Reactor 单线程:所有I/O操作和事件处理都在一个线程中完成。适用于简单的应用场景,但处理复杂业务逻辑时可能会成为瓶颈。

1
Reactor 线程 → 监听事件 → 分发事件 → 处理事件(I/O + 业务)

单 Reactor 多线程:1 个 Reactor 线程负责监听和分发事件,而使用线程池来处理具体的业务逻辑。

1
Reactor 线程 → 监听事件 → 分发 I/O 事件 → 线程池处理业务

主从 Reactor 多线程(Netty 采用):使用一个主Reactor来处理连接请求,使用一个或多个从Reactor来处理具体的I/O事件。

1
主 Reactor → 接收连接 → 注册到从 Reactor → 从 Reactor 处理 I/O → 线程池处理业务

3. I/O多路复用

  1. I/O多路介绍: 操作系统通过 select、poll、epoll(Linux)或 kqueue(BSD/macOS)等系统调用实现。允许单线程监控多个文件描述符(如 Socket),当任意一个描述符就绪(可读、可写)时通知线程处理。属于操作系统层面的底层技术,直接与内核交互。

  2. IO 多路复用的实现方式

  • select:最早的多路复用机制之一,通过 select 系统调用监听多个文件描述符,返回就绪的数量,文件描述符上限(通常 1024),需遍历所有描述符检查状态(时间复杂度 O(n)),需遍历所有描述符检查状态(时间复杂度 O(n))。
  • poll:与 select 类似,但使用链表存储描述符,无数量限制,仍需遍历所有描述符(性能问题依然存在)。
  • epoll(Linux 特有):通过 epoll_create 创建上下文,epoll_ctl 注册事件,epoll_wait 等待事件,内核维护就绪队列,直接返回有效事件(时间复杂度 O(1)),只要描述符就绪,每次 epoll_wait 都会通知,高效处理海量连接(无遍历开销)。
  • kqueue(BSD、macOS):类似 epoll,采用事件驱动机制,高效处理高并发。
  • IOCP(Windows):Windows系统中的异步I/O机制,类似于epoll。
  1. Java NIO中的IO多路复用: Java NIO 的 Selector 是对操作系统 IO 多路复用机制的封装。在 Linux 上,Selector 使用 epoll,Channel.register() 对应 epoll_ctl 注册事件。在 Windows 上,使用 IOCP(完成端口)。在 macOS 上,使用 kqueue。IO 多路复用是操作系统提供的底层机制,直接管理文件描述符的事件监听。Java NIO则是通过 Selector 和 Channel 封装底层IO,提供统一 API。

4. 零拷贝技术

1. 零拷贝介绍

是一种优化数据传输的技术,旨在减少或消除数据在内存中的冗余拷贝操作,从而降低 CPU 负载并提升系统性能。其核心思想是通过操作系统的支持,直接在内核态完成数据的传输,避免用户态与内核态之间的数据拷贝。

2. 传统数据拷贝

过程共4次拷贝,发生2次CPU拷贝,4次切换。

graph LR
    subgraph 用户空间
        A[应用程序<br>(JVM内存)]
    end

    subgraph 内核空间
            B[内存缓冲区<br>(Linux Page Cache)]
            C[Socket缓冲区]
    end

    D[(磁盘)]
    E{{网络}}

    D --DMA直接读取<br>(拷贝)--> B
    B --CUP拷贝--> 用户空间
    用户空间 --CUP拷贝--> C
    C --DMA发送<br>(拷贝)--> E
3. 常见零拷贝实现
  1. mmap(内存映射)

过程共3次拷贝,发生1次CPU拷贝,3次切换。

graph LR
    subgraph 用户空间
        A[应用程序<br>(JVM内存)]
    end

    subgraph 内核空间
            B[内存缓冲区<br>(Linux Page Cache)]
            C[Socket缓冲区]
    end

    D[(磁盘)]
    E{{网络}}

    D --DMA直接读取<br>(拷贝)--> B
    B --CUP拷贝--> C
    用户空间 <--内存映射<br>(用户线程直接操作不再拷贝)--> B
    C --DMA发送<br>(拷贝)--> E
  1. sendfile(Linux系统调用)

不经过用户空间,仅发生2次DMA拷贝。内核空间映射需要硬件支持 Scatter-Gather DMA。

graph LR
    subgraph 整体空间
        subgraph 用户空间
            A[应用程序<br>(JVM内存)]
        end

        subgraph 内核空间
                B[内存缓冲区<br>(Linux Page Cache)]
                C[Socket缓冲区]
        end
    end

    D[(磁盘)]
    E{{网络}}

    D --DMA直接读取<br>(拷贝)--> B
    B <-- 映射-->C
    C --DMA发送<br>(拷贝)--> E
  1. Netty中零拷贝
  • 堆外内存(Direct Buffer):Java 的 Heap ByteBuffer 在发送数据时,需先拷贝到堆外内存(JNI 调用)。JNI 调用本质是 Java 的 Socket 操作(如 SocketChannel.write())底层通过 JNI(Java Native Interface) 调用操作系统函数(如 Linux 的 send()),再通过 Socket 发送。而 Heap ByteBuffer 分配在 JVM 堆内存中,由JVM管理,为虚拟内存,地址不稳定。而操作系统的内核 Socket 发送需要直接访问稳定的内存地址。Netty 的解决方案使用 DirectByteBuf 直接在堆外分配内存, 避免 JVM 堆与本地内存拷贝。

    1
    2
    3
    
    ByteBuf directBuf = ByteBufAllocator.DEFAULT.directBuffer();
    directBuf.writeBytes(data); // 数据直接写入堆外内存
    channel.writeAndFlush(directBuf);
    
  • 组合缓冲区(CompositeByteBuf):传统合并多个 ByteBuf 时,需要将数据拷贝到新缓冲区。Netty 使用 CompositeByteBuf 逻辑上组合多个 ByteBuf ,物理上仍保持分散,减少内存复制。

    1
    2
    3
    
    CompositeByteBuf compositeBuf = ByteBufAllocator.DEFAULT.compositeBuffer();
    compositeBuf.addComponents(true, buf1, buf2); // 逻辑组合,无拷贝
    channel.writeAndFlush(compositeBuf);
    
  • 文件传输优化(FileRegion):传统方式文件先从磁盘到用户空间,再写入内核 Socket 缓冲区。Netty 使用 DefaultFileRegion 封装 FileChannel.transferTo() 方法,直接调用系统的sendfile机制,绕过用户空间,通过 DMA 直接发送到网络。

    1
    2
    3
    4
    
    File file = new File("largefile.bin");
    FileChannel channel = new RandomAccessFile(file, "r").getChannel();
    FileRegion region = new DefaultFileRegion(channel, 0, file.length());
    ctx.writeAndFlush(region);
    
  • 内存池(ByteBuf Pool):以往频繁创建和销毁 ByteBuf 会导致内存碎片和 GC 压力,Netty 通过内存池(如 PooledByteBufAllocator)复用已分配的 ByteBuf,减少内存分配开销。

    1
    2
    3
    
    // 使用内存池分配器
    ByteBufAllocator allocator = PooledByteBufAllocator.DEFAULT;
    ByteBuf pooledBuf = allocator.directBuffer(1024);
    

5. Netty工作原理

1. 核心流程(客户端请求)
  1. 连接建立
    • 客户端连接 → BossGroup 的 EventLoop 接收 → 创建 Channel → 注册到 WorkerGroup 的 EventLoop。
  2. 数据读取
    • Channel 触发 OP_READ 事件 → EventLoop 调用 ChannelPipeline 处理入站数据。
    • 流程:ByteBuf → Decoder → BusinessHandler → 业务处理。
  3. 数据写出
    • 业务生成响应 → 通过 ChannelPipeline 处理出站数据。
    • 流程:BusinessHandler → Encoder → ByteBuf → 通过 Channel 写出到网络。
  4. 异步处理
    • 若业务逻辑耗时 → 提交到 Business ThreadPool → 处理完成后通过 EventLoop 写回结果。
2. 架构分层解析
1. 线程模型层(Reactor 核心)
  • BossGroup
    • 角色:主 Reactor,负责监听并接收客户端连接(处理 OP_ACCEPT 事件)。
    • 线程数:通常为 1 个 EventLoop(单线程)。
  • WorkerGroup
    • 角色:从 Reactor,处理已建立连接的 I/O 读写事件(OP_READ/OP_WRITE)。
    • 线程数:默认 CPU 核心数 × 2,可配置。
  • Business ThreadPool
    • 角色:处理耗时业务逻辑(如数据库操作),防止阻塞 EventLoop。
    • 非必需:若业务无阻塞操作,可直接在 EventLoop 线程处理。
2. 事件处理层(EventLoop)
  • EventLoop
    • 单线程运行,绑定多个 Channel。
    • 执行任务:
      • 监听 Channel 的 I/O 事件。
      • 执行提交到该线程的任务(如定时任务)。
  • 工作流程:
    1. BossGroup 的 EventLoop 接收新连接 → 创建 Channel
    2. 将 Channel 注册到 WorkerGroup 的某个 EventLoop。
    3. WorkerGroup 的 EventLoop 监听 Channel 的 I/O 事件 → 触发 ChannelPipeline 处理。
3. 数据处理层(ChannelPipeline)
  • ChannelPipeline
    • 由多个 ChannelHandler 组成的双向链表。
    • 处理链方向:
      • Inbound(入站):处理读取事件(如解码、业务逻辑)。
      • Outbound(出站):处理写出事件(如编码、加密)。
  • 核心 Handler 类型:
    • Decoder:将字节数据解码为 Java 对象(如 ByteToMessageDecoder)。
    • Encoder:将 Java 对象编码为字节数据(如 MessageToByteEncoder)。
    • BusinessHandler:自定义业务逻辑(如处理用户请求)。
4. 数据存储层(ByteBuf)
  • ByteBuf
    • Netty 的字节缓冲区,支持堆内/堆外内存分配。
    • 零拷贝优化:
      • CompositeByteBuf:逻辑组合多个 Buffer,避免物理拷贝。
      • FileRegion:通过 sendfile 实现文件零拷贝传输。
  • 内存管理:
    • 使用 PooledByteBufAllocator 内存池减少 GC 压力。
5. Netty架构图
graph TB
    A[Clinet]
    B[Clinet]
    C[Clinet]

    subgraph BossGroup
        subgraph NioEventLoopGroup1
            subgraph NioEventLoop
                D[Selector]
                E[TaskQueue]
            end
        end
    end

    subgraph 2
        F(step1:selector.select)
        G(step2:processSelectorKeys)
        H(step3:runAllTasks)
    end

    A -->|request| BossGroup
    B -->|request| BossGroup
    C -->|request| BossGroup

    F --> G --> H --> F

    NioEventLoop -->|Accept| F

    subgraph WorkerGroup
        subgraph NioEventLoopGroup2
            subgraph NioEventLoop1
                I[Selector]
                J[TaskQueue]
            end

             subgraph NioEventLoop2
                K[Selector]
                L[TaskQueue]
            end
        end
    end

    G -->|注册Channel到Selector| NioEventLoop2


    M(step1:selector.select)
    N(step2:processSelectorKeys)
    O(step3:runAllTasks)

    M --> N --> O --> M

    NioEventLoop2 -->|read/write| M

    subgraph Pipeline
        P(ChannelPipeline)
        Q(ChannelPipeline)
    end

    N -->|ChannelPipeline| Pipeline

6. Netty核心组件

1. Channel(通道)
  • 功能

    • 代表一个网络连接(如 TCP Socket),是 Netty 网络操作的抽象。
    • 提供异步的 I/O 操作(如 readwritebindconnect)。
    • 绑定到 EventLoop 以处理事件。
  • 常见实现类

    • NioSocketChannel:基于 NIO 的客户端或服务器 TCP 连接。
    • NioServerSocketChannel:服务器端监听连接的通道。
  • 示例

    1
    2
    
    Channel channel = ...;
    channel.writeAndFlush("Data"); // 异步发送数据
    
2. EventLoop(事件循环)
  • 功能

    • 单线程的事件循环,负责处理注册到它的所有 Channel 的 I/O 事件。
    • 执行提交到该线程的任务(如定时任务、异步回调)。
    • 一个 EventLoop 可以绑定多个 Channel,但一个 Channel 只能注册到一个 EventLoop
  • 关联组件

    • EventLoopGroup:管理一组 EventLoop(如 NioEventLoopGroup)。
  • 示例

    1
    2
    3
    
    channel.eventLoop().execute(() -> {
        System.out.println("在 EventLoop 线程执行任务");
    });
    
3. ChannelPipeline(处理链)
  • 功能

    • 由多个 ChannelHandler 组成的双向链表,定义数据处理流程。
    • 入站(Inbound):处理数据读取(如解码、业务逻辑)。
    • 出站(Outbound):处理数据写出(如编码、加密)。
    • 支持动态增删 ChannelHandler
  • 关键方法

    • addFirstaddLast:插入处理器到链头或链尾。
  • 示例

    1
    2
    3
    
    pipeline.addLast(new StringDecoder());  // 入站解码
    pipeline.addLast(new BusinessHandler()); // 业务处理
    pipeline.addLast(new StringEncoder());  // 出站编码
    
4. ChannelHandler(处理器)
  • 功能

    • 处理 I/O 事件(如连接建立、数据读取)或拦截数据流。
    • 分为 入站处理器(InboundHandler)出站处理器(OutboundHandler)
  • 常见子类

    • SimpleChannelInboundHandler<T>:处理特定类型的消息(自动释放资源)。
    • ByteToMessageDecoder:将字节流解码为对象。
    • MessageToByteEncoder<T>:将对象编码为字节流。
  • 示例

    1
    2
    3
    4
    5
    6
    
    public class EchoHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            ctx.write(msg); // 回显数据
        }
    }
    
5. ByteBuf(字节缓冲区)
  • 功能

    • Netty 优化的数据容器,替代 Java NIO 的 ByteBuffer
    • 支持 堆内存(Heap Buffer)堆外内存(Direct Buffer)
    • 提供零拷贝操作(如 sliceduplicateCompositeByteBuf)。
  • 关键特性

    • 引用计数:通过 retain()release() 管理内存生命周期。
    • 容量动态扩展:按需自动扩容。
  • 示例

    1
    2
    
    ByteBuf buf = Unpooled.directBuffer(1024);
    buf.writeBytes("Hello".getBytes());
    
6. BootstrapServerBootstrap(启动器)
  • 功能

    • Bootstrap:配置和启动客户端。
    • ServerBootstrap:配置和启动服务器。
  • 关键方法

    • group():设置 EventLoopGroup(线程组)。
    • channel():指定 Channel 类型(如 NioSocketChannel)。
    • handler() / childHandler():设置处理器。
  • 示例(服务器启动):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    ServerBootstrap bootstrap = new ServerBootstrap();
    bootstrap.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 protected void initChannel(SocketChannel ch) {
                     ch.pipeline().addLast(new ServerHandler());
                 }
             });
    
7. ChannelHandlerContext(处理器上下文)
  • 功能

    • 关联 ChannelHandlerChannelPipeline,提供操作 Channel 和触发事件的方法。
    • 支持在处理器链中动态传递数据(如 fireChannelRead)。
  • 示例

    1
    2
    3
    
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ctx.fireChannelRead(msg); // 将数据传递到下一个处理器
    }
    
8. ChannelFuture(异步结果)
  • 功能

    • 表示异步 I/O 操作的结果(如连接、绑定、发送数据)。
    • 通过 addListener 添加回调逻辑。
  • 示例

    1
    2
    3
    4
    5
    6
    
    ChannelFuture future = channel.writeAndFlush("Data");
    future.addListener(f -> {
        if (f.isSuccess()) {
            System.out.println("数据发送成功");
        }
    });
    
组件 角色
Boss EventLoopGroup 主 Reactor,监听连接请求,将新连接注册到 Worker Group
Worker EventLoopGroup 从 Reactor,处理已建立连接的 I/O 读写事件
Channel 代表一个 Socket 连接,绑定到特定的 EventLoop
NioEventLoopGroup 可以含有多个线程,即可以含有多个 NioEventLoop
NioEventLoop 每个 NioEventLoop 都包含一个 Selector和TaskQueue,将事件放入Queue中,用于监听注册在其上的 Socket 网络连接(Channel)
runAllTasks 循环处理任务队列中的任务
sync() 阻塞当前线程,直到操作完成,并在操作失败时抛出异常

7. Netty代码示例

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// 服务端
public class NettyServer {

    private final int port;

    public NettyServer(int port) {
        this.port = port;
    }

    public void run() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 处理连接请求
        EventLoopGroup workerGroup = new NioEventLoopGroup(); // 处理 I/O 事件

        try {
            ServerBootstrap bootstrap = new ServerBootstrap(); // 配置服务器
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class) // 使用 NIO 模型
                    .childHandler(new ChannelInitializer<SocketChannel>() { // 定义处理链,包括StringDecoder、StringEncoder和自定义的ServerHandler
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new StringDecoder()); // 解码字节为字符串
                            pipeline.addLast(new StringEncoder()); // 编码字符串为字节
                            pipeline.addLast(new ServerHandler()); // 自定义业务处理器
                        }
                    });

            // 绑定端口并启动
            ChannelFuture future = bootstrap.bind(port).sync();
            System.out.println("服务器启动成功,监听端口: " + port);
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    // 自定义业务处理器
    static class ServerHandler extends SimpleChannelInboundHandler<String> {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, String msg) {
            System.out.println("收到客户端消息: " + msg);
            ctx.writeAndFlush("服务器回复: " + msg); // 回复客户端
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        }
        new NettyServer(port).run();
    }
}

// 客户端
public class NettyClient {

    private final String host;
    private final int port;

    public NettyClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void run() throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class) // 使用 NIO 模型
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new StringDecoder()); // 解码字节为字符串
                            pipeline.addLast(new StringEncoder()); // 编码字符串为字节
                            pipeline.addLast(new ClientHandler()); // 自定义业务处理器
                        }
                    });

            // 连接到服务器
            ChannelFuture future = bootstrap.connect(host, port).sync();
            System.out.println("客户端连接成功");

            // 获取 Channel 对象
            Channel channel = future.channel();

            // 从 System.in 读取用户输入
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            String line;
            while ((line = in.readLine()) != null) {
                channel.writeAndFlush(line); // 发送消息到服务器
            }

            // 等待服务器回复
            channel.closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    // 自定义业务处理器
    static class ClientHandler extends SimpleChannelInboundHandler<String> {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, String msg) {
            System.out.println("收到服务器消息: " + msg);
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }
    }

    public static void main(String[] args) throws Exception {
        String host = "localhost";
        int port = 8080;
        if (args.length > 1) {
            host = args[0];
            port = Integer.parseInt(args[1]);
        }
        new NettyClient(host, port).run();
    }
}

8. 调试与优化

1. 工具

  • Wireshark:抓包分析 TCP 握手过程。
  • JProfiler:监控线程阻塞和内存泄漏。

2. 优化点

  • 减少上下文切换:合理配置线程池大小。
  • 缓冲区调优:根据 MTU 调整 Buffer 大小(如 1500 字节)。

总结

需要掌握网络编程的基础知识,如 TCP/IP 协议、NIO、BIO、线程池、网络框架等。