探索Java网络编程模型:Socket、UDP、NIO和Netty的全景视角

跳哥跳哥 in Java 2023-05-15 16:24:04

想要深入了解Java网络编程模型和其核心组件?本文将详细介绍Java Socket、UDP、NIO和Netty,并提供一个简单实例来演示其应用。无论您是初学者还是有经验的开发者,通过阅读本文,您将获得构建高效、可扩展网络应用所需的知识和指导。开始探索Java网络编程的奥秘,提升您的开发技能吧!

引言

Java 网络编程是一项关键技术,为构建可靠的网络应用提供了强大的工具和框架。了解 Java 网络编程模型以及其核心组件,如 Socket、UDP、NIO 和 Netty,对于开发高效、可扩展的网络应用至关重要。本文将深入探讨 Java 网络编程模型,并通过一个简单的实例演示其应用。无论您是初学者还是有经验的开发人员,本文将帮助您全面了解 Java 网络编程,从而构建出优秀的网络应用。

Java 网络编程模型

网络编程是 Java 编程中的重要组成部分,包括服务器端和客户端两部分内容。下面是一些 Java 网络编程的基础知识:

Java 网络编程模型是基于 Socket API 实现的,其分为两种模型:

  • 客户端/服务器模型:客户端向服务器发起连接请求,服务器接受请求并返回相应结果。
  • – P2P 模型:对等节点之间直接进行通信,没有中心节点。

Java 网络编程模型基于 TCP/IP 协议,主要包括三个主要组件:Socket、ServerSocket 和 DatagramSocket。

1. Socket
Socket 是 Java 网络编程的基本组件之一,它是一种特殊的文件描述符,用于在应用程序之间提供双向通信。Socket 提供了一种标准的接口,允许应用程序通过网络发送和接收数据。在 Java 中,Socket 可以分为客户端 Socket 和服务端 Socket 两种类型。

客户端 Socket:客户端 Socket 用于与服务端 Socket 进行通信。客户端 Socket 通过指定服务端的 IP 地址和端口号,连接到服务端 Socket,然后发送数据到服务端 Socket。

服务端 Socket:服务端 Socket 用于接收来自客户端 Socket 的连接请求,并在连接成功后,与客户端 Socket 进行通信。服务端 Socket 首先需要创建一个 ServerSocket 对象,并通过 bind 方法绑定到一个本地端口,然后等待客户端 Socket 的连接请求。

2. ServerSocket
ServerSocket 是服务端 Socket 的实现,用于监听客户端 Socket 的连接请求。ServerSocket 可以通过 bind 方法绑定到一个本地端口,并通过 accept 方法等待客户端 Socket 的连接请求。当客户端 Socket 连接成功后,ServerSocket 会返回一个新的 Socket 对象,用于与客户端 Socket 进行通信。

3. DatagramSocket
DatagramSocket 是一种基于 UDP 协议的 Socket 实现,用于在网络上发送和接收数据包。与 TCP Socket 不同,UDP Socket 没有建立连接,只是将数据包发送到目标 IP 地址和端口号,并等待对方的响应。

Java 网络编程模型采用阻塞式 I/O 模型,也就是说,当一个 Socket 处于读或写操作时,当前线程会被阻塞,直到操作完成。虽然阻塞式 I/O 模型在处理并发请求时效率较低,但由于其简单易用的特点,在实际开发中仍然被广泛应用。近年来,随着 Java NIO 和 Netty 的发展,非阻塞式 I/O 模型已经成为 Java 网络编程的主流,可以更好地支持高并发请求。

案例:
这里给你提供一个简单的 Java 网络编程的 Demo,实现了一个简单的服务端和客户端之间的交互。

服务端代码:


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 Server {
    public static void main(String[] args) {
        try {
            // 创建 ServerSocket 并绑定端口 8000
            ServerSocket serverSocket = new ServerSocket(8000);
            System.out.println("Server started, waiting for client...");

            // 等待客户端连接
            Socket socket = serverSocket.accept();
            System.out.println("Client connected.");

            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

            String message;
            // 读取客户端发送的消息
            while ((message = in.readLine()) != null) {
                System.out.println("Client: " + message);
                // 向客户端回复消息
                out.println("Server received message: " + message);
            }

            in.close();
            out.close();
            socket.close();
            serverSocket.close();
        } 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 Client {
    public static void main(String[] args) {
        try {
						// 创建 Socket 并连接到服务器
            Socket socket = new Socket("localhost", 8000);
            System.out.println("Connected to server.");

            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

            BufferedReader consoleIn = new BufferedReader(new InputStreamReader(System.in));
            String message;
            // 从控制台读取消息并发送给服务器
            while ((message = consoleIn.readLine()) != null) {
                out.println(message);
                // 接收服务器回复的消息并打印到控制台
                System.out.println("Server: " + in.readLine());
            }

            consoleIn.close();
            in.close();
            out.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这个 Demo 实现了一个简单的回显服务器,客户端输入什么内容,服务器就返回什么内容。你可以在服务端和客户端分别运行这两个程序,模拟一个简单的网络通信过程。

Java Socket 编程

Java 的 Socket API 是基于 TCP/IP 协议栈的封装,它包含了服务器端和客户端两部分。服务器端主要包括以下步骤:

– 创建 ServerSocket 对象,绑定监听端口;
– 调用 ServerSocket 的 accept()方法监听客户端连接请求;
– 当有客户端请求连接时,accept()方法会返回一个 Socket 对象,用于与客户端进行通信;
– 通过输入输出流进行数据传输。

客户端主要包括以下步骤:

– 创建 Socket 对象,指定服务器的地址和端口;
– 通过输入输出流进行数据传输。

Java Socket 编程是基于 TCP/IP 协议的网络编程,主要用于实现网络通信。Socket 编程可以用于实现客户端与服务器之间的通信,包括数据传输、命令执行和文件传输等。在 Java 中,Socket 编程可以通过 java.net 包中的 Socket 和 ServerSocket 类实现。

1. Socket 类

Socket 类表示客户端与服务器之间的一个连接。它提供了实现数据传输的各种方法,包括读取和写入数据、关闭连接等。

创建一个 Socket 对象的方式如下:


Socket socket = new Socket(host, port);

其中,host 表示服务器主机的 IP 地址或主机名,port 表示服务器开放的端口号。

2. ServerSocket 类

ServerSocket 类表示服务器端的套接字。它用于监听来自客户端的连接请求,并在连接请求到来时创建一个新的 Socket 对象来与客户端进行通信。

创建一个 ServerSocket 对象的方式如下:


ServerSocket serverSocket = new ServerSocket(port);

其中,port 表示服务器开放的端口号。

3. 实现客户端与服务器的通信

客户端与服务器之间的通信可以通过 Socket 类的 InputStream 和 OutputStream 实现。当客户端与服务器建立连接后,可以通过 Socket 对象获取相应的输入流和输出流,从而进行数据的读取和写入。

客户端的代码示例:


Socket socket = new Socket(host, port);
OutputStream outputStream = socket.getOutputStream();
outputStream.write("Hello, Server!".getBytes());

InputStream inputStream = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = inputStream.read(buffer);
String message = new String(buffer, 0, len);
System.out.println("Message from server: " + message);

socket.close();

服务器端的代码示例:


ServerSocket serverSocket = new ServerSocket(port);
Socket socket = serverSocket.accept();

InputStream inputStream = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = inputStream.read(buffer);
String message = new String(buffer, 0, len);
System.out.println("Message from client: " + message);

OutputStream outputStream = socket.getOutputStream();
outputStream.write("Hello, Client!".getBytes());

socket.close();
serverSocket.close();

其中,host 表示服务器主机的 IP 地址或主机名,port 表示服务器开放的端口号。

4. 其他常用方法

除了上述方法外,Socket 类还提供了一些常用的方法,包括:

– getInputStream():获取 Socket 的输入流。
– getOutputStream():获取 Socket 的输出流。
– setSoTimeout(int timeout):设置 Socket 的读取超时时间。
– setKeepAlive(boolean on):设置 Socket 是否开启保持活动状态。
– setTcpNoDelay(boolean on):设置 Socket 是否开启无延迟模式。

ServerSocket 类也提供了一些常用的方法,包括:

– accept():等待客户端连接并返回一个 Socket 对象。
– close():关闭 ServerSocket 对象。

总的来说,Java Socket 编程是实现网络通信的一种简单而有效的方式。它提供了方便的方法和工具,可以轻松地实现客户端与服务器之间的通信

案例:
下面是一个简单的 Java Socket 编程的 Demo,实现了客户端向服务器发送数据,服务器接收并打印数据的功能:

客户端代码:


import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class SocketClientDemo {
    public static void main(String[] args) {
        String serverHost = "localhost"; // 服务器地址
        int serverPort = 8888; // 服务器端口号
        Socket socket = null;
        OutputStream out = null;
        try {
            // 创建 Socket 对象
            socket = new Socket(serverHost, serverPort);
            // 获取输出流
            out = socket.getOutputStream();
            // 向服务器发送数据
            String message = "Hello World!";
            out.write(message.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                // 关闭输出流和 Socket
                out.close();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

服务器端代码:


import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class SocketServerDemo {
    public static void main(String[] args) {
        int serverPort = 8888; // 服务器端口号
        ServerSocket serverSocket = null;
        Socket socket = null;
        InputStream in = null;
        try {
            // 创建 ServerSocket 对象
            serverSocket = new ServerSocket(serverPort);
            // 等待客户端连接
            socket = serverSocket.accept();
            // 获取输入流
            in = socket.getInputStream();
            // 读取客户端发送的数据
            byte[] buffer = new byte[1024];
            int len = in.read(buffer);
            String message = new String(buffer, 0, len);
            System.out.println("Received message from client: " + message);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                // 关闭输入流、Socket 和 ServerSocket
                in.close();
                socket.close();
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

以上 Demo 展示了 Socket 编程的基本流程,包括客户端创建 Socket 对象并向服务器发送数据,服务器创建 ServerSocket 对象并等待客户端连接,客户端和服务器之间通过输入输出流进行数据传输。

Java UDP 编程

Java UDP 编程也是基于 Socket API 实现的。服务器端和客户端的实现与 TCP 编程基本一致,只是使用 DatagramSocket 和 DatagramPacket 对象进行数据传输。

UDP 是一种无连接、不可靠、面向数据报的传输协议,它与 TCP 不同,UDP 只提供数据报服务,不提供面向连接的服务。在 Java 中,UDP 编程可以使用 java.net 包中的 DatagramSocket 和 DatagramPacket 类。

下面是 UDP 编程的基本流程:

1. 创建 DatagramSocket 对象,指定端口号。


DatagramSocket socket = new DatagramSocket(8888);

2. 创建 DatagramPacket 对象,包含发送的数据、发送的数据长度、目的 IP 地址和端口号。


String msg = "Hello, UDP!";
byte[] buf = msg.getBytes();
InetAddress address = InetAddress.getByName("192.168.1.100");
int port = 9999;
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port);

3. 使用 DatagramSocket 的 send 方法发送数据报。

socket.send(packet);

4. 创建 DatagramPacket 对象,接收数据报。


byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);

5. 解析接收到的数据。


String msg = new String(packet.getData(), 0, packet.getLength());

6. 关闭 DatagramSocket 对象。


socket.close();

下面是一个简单的 UDP 编程示例,它实现了向指定 IP 地址和端口号发送数据报,并接收数据报的功能:


import java.net.*;

public class UDPServer {
    public static void main(String[] args) {
        try {
            // 创建 DatagramSocket 对象,指定端口号
            DatagramSocket socket = new DatagramSocket(8888);

            // 创建发送的数据报
            String msg = "Hello, UDP!";
            byte[] buf = msg.getBytes();
            InetAddress address = InetAddress.getByName("192.168.1.100");
            int port = 9999;
            DatagramPacket sendPacket = new DatagramPacket(buf, buf.length, address, port);

            // 发送数据报
            socket.send(sendPacket);

            // 创建接收的数据报
            byte[] recvBuf = new byte[1024];
            DatagramPacket recvPacket = new DatagramPacket(recvBuf, recvBuf.length);

            // 接收数据报
            socket.receive(recvPacket);

            // 解析接收到的数据
            String recvMsg = new String(recvPacket.getData(), 0, recvPacket.getLength());
            System.out.println("Received message: " + recvMsg);

            // 关闭 DatagramSocket 对象
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

上面的示例中,我们创建了一个 DatagramSocket 对象并指定了端口号,然后创建了一个发送的数据报,将它发送到指定的 IP 地址和端口号。接着,我们创建了一个接收的数据报,并使用 DatagramSocket 的 receive 方法接收数据报。最后,我们解析接收到的数据,并关闭了 DatagramSocket 对象。

案例:
以下是一个简单的 Java UDP 编程的示例代码:


import java.net.*;

public class UDPClientServerDemo {
    public static void main(String[] args) throws Exception {
        // 客户端代码
        DatagramSocket clientSocket = new DatagramSocket();
        String message = "Hello, server!";
        InetAddress serverAddress = InetAddress.getByName("localhost");
        int serverPort = 9876;
        byte[] sendData = message.getBytes();
        DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, serverAddress, serverPort);
        clientSocket.send(sendPacket);

        // 服务器代码
        DatagramSocket serverSocket = new DatagramSocket(9876);
        byte[] receiveData = new byte[1024];
        DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
        serverSocket.receive(receivePacket);
        String receivedMessage = new String(receivePacket.getData(), 0, receivePacket.getLength());
        System.out.println("Message from client: " + receivedMessage);
        serverSocket.close();
    }
}

该示例包含了客户端和服务器端两个部分。在客户端中,首先创建一个 DatagramSocket 对象,并准备发送给服务器的消息。然后,使用 InetAddress 类获取服务器的 IP 地址,并指定服务器的端口号。接下来,将消息转换为字节数组,并使用 DatagramPacket 将其打包。最后,使用 DatagramSocket 的 send 方法将消息发送给服务器。

在服务器端,首先创建一个 DatagramSocket 对象,并指定服务器要监听的端口号。然后,创建一个字节数组用于存储接收到的消息,使用 DatagramPacket 将其打包,并使用 DatagramSocket 的 receive 方法等待客户端发送的消息。一旦接收到消息,将其转换为字符串,并输出到控制台。最后,关闭 DatagramSocket 对象。

需要注意的是,UDP 是无连接协议,因此客户端和服务器之间并不需要建立连接,而是直接通过 DatagramPacket 进行消息的发送和接收。由于 UDP 不保证消息的可靠性和顺序性,因此需要开发人员自行处理丢失消息和乱序消息等问题。

Java NIO

Java NIO(New I/O)是 Java 的一种新的 IO(输入/输出)模型,它提供了与传统的 Java IO(输入/输出)不同的、更高效的 IO 操作方式。相比 Java IO,Java NIO 的主要优点是它可以支持非阻塞 IO 操作,使得单个线程可以管理多个并发连接,大大提高了系统的性能和吞吐量。

Java NIO 中的主要组件包括:缓冲区(Buffer)、通道(Channel)、选择器(Selector)和操作标记(InterestOps)。缓冲区用于在通道和应用程序之间传输数据,通道用于连接数据源和数据目标,选择器用于管理通道的 IO 操作,操作标记用于指示选择器感兴趣的 IO 事件。

使用 Java NIO 进行网络编程的基本步骤包括:

1.打开一个通道(Channel):使用`java.nio.channels.Channel`类的实现类来打开一个通道,如:`SocketChannel`或`ServerSocketChannel`。

2.绑定通道到本地地址:使用`bind()`方法将通道绑定到本地地址上。

3.连接到远程服务器:如果是客户端程序,则使用`connect()`方法连接到远程服务器。

4.创建缓冲区(Buffer):使用`java.nio.Buffer`的实现类来创建缓冲区。

5.写入数据到缓冲区:使用缓冲区的`put()`方法写入数据。

6.从缓冲区读取数据:使用缓冲区的`get()`方法从缓冲区读取数据。

7.注册一个选择器(Selector):使用`java.nio.channels.Selector`类来创建选择器。

8.将通道注册到选择器上:使用`register()`方法将通道注册到选择器上,并指定需要监听的 IO 事件。

9.处理选择器上的 IO 事件:使用选择器的`select()`方法来等待 IO 事件,当有事件发生时,使用选择器的`selectedKeys()`方法来获取选择器上的所有 IO 事件,并处理这些事件。

10.关闭通道和选择器:使用`close()`方法关闭通道和选择器。

案例:
客户端


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.time.LocalDateTime;
import java.util.Iterator;
import java.util.Set;

public class NIOClient {
    private Selector selector;
    private ByteBuffer buffer = ByteBuffer.allocate(1024);

    public static void main(String[] args) throws IOException {
        NIOClient client = new NIOClient();
        client.initClient("localhost", 8888);
        client.start();
    }

    public void initClient(String ip, int port) throws IOException {
        SocketChannel channel = SocketChannel.open();
        channel.configureBlocking(false);
        this.selector = Selector.open();
        channel.connect(new InetSocketAddress(ip, port));
        channel.register(selector, SelectionKey.OP_CONNECT);
    }

    public void start() throws IOException {
        while (true) {
            selector.select();
            Set keys = selector.selectedKeys();
            Iterator iterator = keys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                handleKey(key);
            }
        }
    }

    private void handleKey(SelectionKey key) throws IOException {
        if (key.isConnectable()) {
            SocketChannel channel = (SocketChannel) key.channel();
            if (channel.finishConnect()) {
                channel.configureBlocking(false);
                channel.register(selector, SelectionKey.OP_READ);
                channel.write(ByteBuffer.wrap("Hello Server".getBytes()));
            }
        } else if (key.isReadable()) {
            SocketChannel channel = (SocketChannel) key.channel();
            buffer.clear();
            int len = channel.read(buffer);
            if (len > 0) {
                buffer.flip();
                String message = new String(buffer.array(), 0, len);
                System.out.println("Received message: n"
                        + "ClientTime:" + LocalDateTime.now() + "n" +
                        message);
            }
        }
    }
}

服务端


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.time.LocalDateTime;
import java.util.Set;

public class NIOServer {
    private Selector selector;
    private ByteBuffer buffer = ByteBuffer.allocate(1024);

    public static void main(String[] args) throws IOException {
        NIOServer server = new NIOServer();
        server.initServer(8888);
        server.start();
    }

    public void initServer(int port) throws IOException {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        serverChannel.socket().bind(new InetSocketAddress(port));
        this.selector = Selector.open();
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("Server started on port " + port);
    }

    public void start() throws IOException {
        while (true) {
            selector.select();
            Set keys = selector.selectedKeys();
            for (SelectionKey key : keys) {
                handleKey(key);
            }
            keys.clear();
        }
    }

    private void handleKey(SelectionKey key) throws IOException {
        if (key.isAcceptable()) {
            ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
            SocketChannel clientChannel = serverChannel.accept();
            clientChannel.configureBlocking(false);
            clientChannel.register(selector, SelectionKey.OP_READ);
            System.out.println(LocalDateTime.now() + " Accepted connection from: " + clientChannel.getRemoteAddress());
        } else if (key.isReadable()) {
            SocketChannel clientChannel = (SocketChannel) key.channel();
            buffer.clear();
            int len = clientChannel.read(buffer);
            if (len > 0) {
                buffer.flip();
                String message = new String(buffer.array(), 0, len);
                System.out.println(LocalDateTime.now() + " Received message from " + clientChannel.getRemoteAddress() + ": " + message);

                // Echo the message back to the client
                clientChannel.write(ByteBuffer.wrap(("ServerTime:" + LocalDateTime.now() + "n" + message).getBytes()));
            }
        }
    }
}

Java Netty

Netty 是一个基于 Java 的开源网络编程框架,提供了高性能、异步事件驱动的网络应用程序开发工具。它简化了网络编程的复杂性,提供了易于使用的抽象和组件,使开发者可以轻松地构建可扩展、高性能的网络应用。

下面是 Netty 的一些关键特性和概念:

1. 异步和事件驱动:Netty 使用基于事件驱动的模型,所有的 I/O 操作都是非阻塞的。它使用回调机制处理事件,从而实现高效的并发处理和资源利用。

2. NIO 抽象:Netty 封装了 Java NIO(New I/O)的复杂性,提供了更简单和易用的 API。它使用了 Channel、Buffer 和 Selector 等概念,使网络编程更加直观和灵活。

3. 高性能:Netty 针对高性能网络应用进行了优化。它使用了零拷贝技术、内存池、多线程模型等,以提供卓越的性能和可伸缩性。

4. 支持多种协议:Netty 提供了对多种常用协议的支持,包括 HTTP、WebSocket、TCP、UDP 等。你可以轻松地构建各种类型的网络应用,如 Web 服务器、代理服务器、实时通信应用等。

5. 安全性:Netty 提供了 SSL/TLS 的支持,可以实现安全的网络通信。

6. 可扩展性:Netty 的组件模型和插件机制使得它非常易于扩展和定制。你可以根据自己的需求添加自定义的处理器和功能。

使用 Netty 可以实现各种复杂的网络应用,例如服务器、客户端、代理、游戏服务器等。Netty 的编程模型基于事件和回调,你可以通过注册事件处理器来处理连接、数据读写、异常等事件。通过配置适当的处理器和管道,你可以编写灵活、高效的网络应用。

总结起来,Netty 提供了一个强大而灵活的框架,使得 Java 程序员可以轻松构建高性能的网络应用。无论是开发 Web 服务器、实时通信应用还是其他网络应用,Netty 都是一个值得考虑的选择。

案例:
服务端


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class NettyServer {
    private int port;

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

    public void run() throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new StringDecoder(), new StringEncoder(), new ServerHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture future = bootstrap.bind(port).sync();
            System.out.println("Server started on port " + port);

            future.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        int port = 8888;
        NettyServer server = new NettyServer(port);
        try {
            server.run();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

服务端处理器


import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.time.Duration;
import java.time.LocalDateTime;

public class ServerHandler extends SimpleChannelInboundHandler {

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        System.out.println("ServerHandler - channelActive");
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String message) {
        LocalDateTime requestTime = LocalDateTime.now();

        // 在这里添加处理消息的逻辑
        // ...

        // 假设服务器要回复客户端的消息
        String response = "Hello Client";
        ctx.writeAndFlush(response).addListener(future -> {
            if (future.isSuccess()) {
                LocalDateTime responseTime = LocalDateTime.now();
                Duration duration = Duration.between(requestTime, responseTime);
                System.out.println("Response time: " + duration.toMillis() + "ms");
            }
        });
    }

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

客户端


import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class NettyClient {
    private String host;
    private int port;

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

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

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new StringDecoder(), new StringEncoder(), new ClientHandler());
                        }
                    });

            ChannelFuture future = bootstrap.connect(host, port).sync();
            System.out.println("Connected to server: " + host + ":" + port);

            // 发送消息给服务器
            String message = "Hello server!";
            future.channel().writeAndFlush(message);

            // 关闭连接
            future.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        String host = "localhost";
        int port = 8888;
        NettyClient client = new NettyClient(host, port);
        try {
            client.run();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

客户端处理器


import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.time.Duration;
import java.time.LocalDateTime;

public class ClientHandler extends SimpleChannelInboundHandler {
    private LocalDateTime requestTime;

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        System.out.println("ClientHandler - channelActive");
        requestTime = LocalDateTime.now();
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String message) {
        LocalDateTime responseTime = LocalDateTime.now();
        Duration duration = Duration.between(requestTime, responseTime);

        System.out.println("Received message from server: " + message);
        System.out.println("Response time: " + duration.toMillis() + "ms");
    }

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

总结

在本文中,我们深入探讨了 Java 网络编程模型及其核心组件:Socket、UDP、NIO 和 Netty。我们了解了客户端/服务器模型和 P2P 模型,并了解了它们在 Java 网络编程中的应用。通过对 Socket、ServerSocket 和 DatagramSocket 的介绍,我们理解了它们在建立连接、传输数据和处理网络通信中的作用。

我们还简要介绍了 Java 网络编程模型的阻塞式 I/O 模型和非阻塞式 I/O 模型。虽然阻塞式 I/O 模型在并发请求处理方面效率较低,但仍然广泛应用。同时,我们提到了随着 Java NIO 和 Netty 的发展,非阻塞式 I/O 模型已成为 Java 网络编程的主流,为高并发请求提供更好的支持。

通过一个简单的实例,我们演示了一个基本的服务端和客户端之间的交互。这个示例展示了如何使用 Java 网络编程模型实现一个回显服务器,从客户端发送的消息得到服务器的回复。通过这个案例,我们加深了对 Java 网络编程的理解,并为实际应用提供了参考。

通过本文,您应该对 Java 网络编程模型有了更全面的认识,并具备了基础知识来构建可靠、高效的网络应用。无论是开发服务器端应用还是客户端应用,Java 网络编程都是不可或缺的技能。继续学习和实践,您将能够运用这些知识创造出更强大的网络应用,满足不同场景下的需求。

-- End --