网络编程netty

一、I/O简单介绍

I/O 模型简单的理解:就是用什么样的通道进行数据发送接收,很大程度上决定了程序通信的性能。

Java 共支持 3 种网路编程的 I/O 模型:BIONIOAIO

Java BIO同步并阻塞传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事会造成不必要的线程开销。

Java NIO(NIO.2)同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有 I/O 请求就进行处理。

Java AIO异步非阻塞AIO 引入异步通道的概念,采用了 Proactor 模式,简化了程序编写,有效的请求才启动线程,它的特点先由系统完成才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。

二、BIO、NIO、AIO适用的场景分析

  1. BIO 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4 以前的唯一选择,单程序简单易理解。
  2. NIO 方式适用于连接数目多且连接比较短(轻操作)的结构,比如聊天服务器,弹幕系统,服务器之间通讯等。编号才能比较复杂,JDK1.4开始支持。
  3. AIO 方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用 OS 参与并发操作,编程比较复杂,JDK7 开始支持。

三、Java BIO

1、基本介绍
  • Java BIO 就是传统的 java io 编程,其相关的类和接口在 java.io 包下。
  • BIO(Blocking I/O):同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求是服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善(实现多个客户端连接服务器)。
  • BIO 方式适用于连接数目且比较小固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4 以前的唯一选择,程序简单易理解。
2、编码流程
  • 服务器端启动一个 ServiceSocket。
  • 客户端启动 Socket 对服务器进行通信,默认情况下服务器端需要对每个客户建立一个线程与之通讯。
  • 客户端发出一个请求后,先咨询服务器是否有线程响应,如果没有则会等待,或者被拒绝。
  • 如果有响应,客户端线程会等待请求结束后,才继续执行。
3、代码案例
  • 文件传输案例
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileTransferExample {
    public static void main(String[] args) {
        String sourceFile = "path/to/source/file";
        String destinationFile = "path/to/destination/file";
        
        try (FileInputStream fis = new FileInputStream(sourceFile);
             FileOutputStream fos = new FileOutputStream(destinationFile)) {
            
            byte[] buffer = new byte[1024];
            int bytesRead;
            
            while ((bytesRead = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
            }
            
            System.out.println("文件传输完成!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,我们首先定义了源文件和目标文件的路径。然后,我们使用FileInputStreamFileOutputStream创建输入流和输出流。

然后,我们创建了一个大小为1024字节的字节数组作为数据缓冲区。

接下来,通过循环从输入流中读取数据,并将读取的数据写入输出流中。读取的字节数通过read方法返回,直到返回值为-1表示没有更多的数据可读。

最后,我们输出一条消息表示文件传输完成。

需要注意的是,上述代码没有处理异常情况和资源释放。在实际应用中,应该进行异常处理,并在使用完流后关闭它们。

  • 客户端服务端案例

服务端代码案例:

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

public class Server {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(8888);
            System.out.println("Server started. Waiting for client connection...");

            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println("Client connected: " + socket.getInetAddress());

                // 处理客户端连接
                new Thread(() -> {
                    try {
                        InputStream inputStream = socket.getInputStream();
                        OutputStream outputStream = socket.getOutputStream();

                        // 读取客户端发送的数据
                        byte[] buffer = new byte[1024];
                        int length = inputStream.read(buffer);
                        if (length > 0) {
                            System.out.println("Received from client: " + new String(buffer, 0, length));
                        }

                        // 向客户端发送响应数据
                        String response = "Hello from server!";
                        outputStream.write(response.getBytes());
                        outputStream.flush();
                        System.out.println("Sent to client: " + response);

                        // 关闭连接
                        socket.close();
                        System.out.println("Client disconnected: " + socket.getInetAddress());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

客户端代码案例:

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

public class Client {
    public static void main(String[] args) {
        Socket socket = null;
        try {
            socket = new Socket("localhost", 8888);
            System.out.println("Connected to server: " + socket.getInetAddress());

            InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream();

            // 向服务器发送数据
            String message = "Hello from client!";
            outputStream.write(message.getBytes());
            outputStream.flush();
            System.out.println("Sent to server: " + message);

            // 接收服务器响应数据
            byte[] buffer = new byte[1024];
            int length = inputStream.read(buffer);
            if (length > 0) {
                System.out.println("Received from server: " + new String(buffer, 0, length));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

以上代码实现了一个简单的基于阻塞I/O的网络通信客户端和服务端,客户端向服务端发送消息,服务端接收消息并给出响应。请注意,这是一个基础示例,仅用于说明阻塞I/O的工作原理,实际应用中可能需要更复杂的处理逻辑和线程管理。

四、Java NIO

1、基本介绍

Java NIO(New I/O)是Java提供的一种基于通道(Channel)和缓冲区(Buffer)的I/O操作方式。相对于传统的阻塞I/O,Java NIO提供了更为灵活且高效的非阻塞I/O操作方式。

Java NIO主要由以下几个关组件构成:

  • 通道(Channel):通道是进行I/O操作的对象,支持读取和写入操作与传统的阻塞I/O不同,通道可以是双向的,即既可以读取也可以写入。常用的通道类型包括文件通道(FileChannel)、套接字通道(SocketChannel)、服务端通道(ServerSocketChannel)等。

  • 缓冲区(Buffer):缓冲区是Java NIO的核心概念,用来存储数据。所有的数据读取和写入操作都是通过缓冲区进行的。缓冲区提供了不同的类型(例如ByteBuffer、CharBuffer、IntBuffer等)来存储不同类型的数据。

  • 选择器(Selector):选择器是Java NIO实现非阻塞I/O的基础,用于监听多个通道的事件。通过将通道注册到选择器中,可以实现单线程同时监听多个通道的I/O事件,如接收连接、读取、写入等。当有事件发生时,选择器会通知应用程序进行处理。

2、工作流程

Java NIO的基本工作流程如下:

  • 通过打开通道,将通道进行读取模式或写入模式。

  • 创建一个缓冲区,将数据读取到缓冲区或从缓冲区写入数据。

  • 对缓冲区进行操作,包括读取和写入数据。

  • 将通道切换到写入或读取模式,将缓冲区的数据写入通道或从通道读取数据。

  • 关闭通道,释放资源。

3、对比传统BIO

相较于传统的阻塞I/O,Java NIO具有以下优势:

  • 非阻塞模式:可以同时处理多个连接或请求,提高了资源的利用率和响应性能。

  • 选择器:使用选择器可以通过一个线程处理多个通道的事件,避免了每个连接都需要一个独立的线程来处理的情况,降低了资源开销。

  • 缓冲区:读写操作通过缓冲区直接进行,减少了数据拷贝的次数,提高了效率。

  • 支持文件操作:Java NIO不仅支持传统的Socket网络通信,还可以进行文件的读写操作。

4、代码案例
  • 文件传输
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class FileTransferExample {
    public static void main(String[] args) {
        String sourceFile = "path/to/source/file";
        String destinationFile = "path/to/destination/file";

        try (FileInputStream fis = new FileInputStream(sourceFile);
             FileOutputStream fos = new FileOutputStream(destinationFile);
             FileChannel sourceChannel = fis.getChannel();
             FileChannel destinationChannel = fos.getChannel()) {
            
            // 创建缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            
            // 从源通道读取数据到缓冲区
            while (sourceChannel.read(buffer) != -1) {
                buffer.flip();  // 切换为读模式
               
                // 将数据从缓冲区写入目标通道
                while (buffer.hasRemaining()) {
                    destinationChannel.write(buffer);
                }
                
                buffer.clear(); // 清空缓冲区,切换为写模式
            }
            
            System.out.println("文件传输完成!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在上面的示例代码中,我们首先定义了源文件和目标文件的路径。然后,通过FileInputStreamFileOutputStream创建输入流和输出流。

接下来,我们分别通过输入流和输出流获取对应的FileChannelFileChannel提供了低级别的文件读写操作。

我们创建了一个ByteBuffer作为数据传输的缓冲区,通过调用allocate方法指定缓冲区的大小。

然后,我们使用源通道的read方法读取数据到缓冲区,并通过flip方法将缓冲区切换为读模式。

接着,我们使用目标通道的write方法将数据从缓冲区写入目标通道。

最后,我们清空缓冲区并切换为写模式,继续读取数据直到源通道数据读取完毕。

当文件传输完成后,我们输出一条消息。

  • 客户端服务端案例

服务端代码:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.TimeUnit;

public class NIOServerExample {
    public static void main(String[] args) {
        try (ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
            serverSocketChannel.bind(new InetSocketAddress(8888));
            serverSocketChannel.configureBlocking(false); // 设置为非阻塞模式

            while (true) {
                SocketChannel clientChannel = serverSocketChannel.accept();
                if (clientChannel != null) {
                    System.out.println("接受新的连接: " + clientChannel.getRemoteAddress());
                    clientChannel.configureBlocking(false); // 设置为非阻塞模式

                    // 在这里可以进行读写操作

                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    buffer.put("Hello, Client!".getBytes());
                    buffer.flip();

                    while (buffer.hasRemaining()) {
                        clientChannel.write(buffer);
                    }

                    TimeUnit.SECONDS.sleep(1); // 模拟耗时操作

                    clientChannel.close();
                    System.out.println("连接关闭");
                }
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

客户端代码:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class NIOClientExample {
    public static void main(String[] args) {
        try {
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false); // 设置为非阻塞模式
            socketChannel.connect(new InetSocketAddress("localhost", 8888));

            while (!socketChannel.finishConnect()) {}

            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int bytesRead = socketChannel.read(buffer);
            buffer.flip();

            if (bytesRead != -1) {
                byte[] data = new byte[bytesRead];
                buffer.get(data);
                System.out.println("接收到响应: " + new String(data));
            }

            socketChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,服务端使用ServerSocketChannel创建一个ServerSocket,并绑定到指定的端口号。然后使用accept方法接受客户端的连接请求,获取到一个SocketChannel,并设置为非阻塞模式。

服务端在接受到连接后,可以进行读写操作。在这个例子中,服务端往客户端发送了一条消息,并进行了1秒的休眠。

客户端使用SocketChannel创建一个Socket,并指定要连接的服务器地址和端口号。然后设置为非阻塞模式,并使用connect方法连接到服务器。

客户端从SocketChannel中读取服务端响应的数据并打印出来。

需要注意的是,代码中的异常处理、资源释放等细节并未展示,请在实际使用时进行完善。

五、零copy

1、介绍

Java零拷贝(Zero-copy)是一种技术,旨在减少数据在内核态和用户态之间的数据复制次数,提高数据传输的效率和性能。传统的I/O操作中,数据需要经过多次的拷贝操作,例如从磁盘读取数据到内核缓冲区,再从内核缓冲区拷贝到用户态缓冲区,最后再从用户态缓冲区拷贝到应用程序。这些拷贝操作会带来额外的性能开销。

Java通过提供NIO(New I/O)技术和相关API,支持零拷贝操作。以下是Java零拷贝的主要原理和机制:

  • 内存映射文件(Memory-mapped File):Java提供了文件与内存的映射功能,通过将文件的一部分或整个文件映射到内存中,实现文件IO操作的零拷贝。这样应用程序可以直接操作内存中的数据,而无需进行额外的数据复制操作。

  • Direct Buffer:Java NIO中的字节缓冲区(Buffer)分为Heap Buffer和Direct Buffer两种类型,其中Direct Buffer通过使用堆外内存(Off-heap memory)来存储数据。堆外内存是直接在操作系统的本地内存中分配的,可以绕过Java堆内存。Direct Buffer可以通过内存映射文件进行零拷贝操作。

  • scatter-gather I/O:传统的I/O操作中,读取或写入数据需要进行多次系统调用,每次调用只能读取或写入一定数量的字节。而Java NIO中,可以使用scatter-gather I/O(分散-聚集IO)机制,一次性进行多个缓冲区的读取或写入,减少系统调用的次数。

Java零拷贝的优势如下:

  • 减少数据复制:零拷贝技术可以减少数据在不同缓冲区之间的复制次数,提高数据传输效率和性能。

  • 减少系统调用:使用单个系统调用进行多个缓冲区的读取或写入,减少系统调用的次数,提高效率。

  • 提升应用程序性能:通过减少不必要的数据复制和系统调用,可以降低CPU的负载,提升应用程序的性能。

需要注意的是,Java的零拷贝并不代表完全没有数据复制操作,而是尽量减少数据在用户态和内核态之间的数据复制次数。在使用零拷贝时,需要注意相关的API和技术,以确保正确性和安全性。

2、代码案例
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class NIOClientExample {
    public static void main(String[] args) {
        try {
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false); // 设置为非阻塞模式
            socketChannel.connect(new InetSocketAddress("localhost", 8888));

            while (!socketChannel.finishConnect()) {}

            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int bytesRead = socketChannel.read(buffer);
            buffer.flip();

            if (bytesRead != -1) {
                byte[] data = new byte[bytesRead];
                buffer.get(data);
                System.out.println("接收到响应: " + new String(data));
            }

            socketChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,我们首先定义了源文件的路径和目标主机、端口。

然后,我们使用FileInputStream打开源文件并获取FileChannel

下来,我们使用SocketChannel连接到目标主机和口,并设置为阻塞模式。

然,我们获取源文件的大小,并使用To方法将文件从源文件的Channel传输到SocketChannel

在传输过程中,我们可以使用循环不断传输剩余的数据,直到传输完成。

最后,我们输出一条消息表示文件传输完成。

需要注意的是,上述代码没有处理异常情况和资源释放。在实际应用中,应该进行异常处理,并在使用完流和通道后关闭它们。

详细了解可参考这篇文章:一文搞懂零拷贝实现原理与使用(图解)

六、netty

1、基本介绍

Netty是一个开源的、高性能的网络应用框架,基于Java NIO(New I/O)技术实现,用于快速开发可扩展的网络服务器和客户端。

Netty具有以下特点和功能:

  • 异步、事件驱动:Netty使用基于事件驱动的模型,通过异步的方式处理网络通信,可以高效地处理成千上万的并发连接。

  • 高性能:Netty通过使用Java NIO提供的非阻塞I/O,实现了高性能的网络通信。它通过使用多线程和线程池技术,使得每个线程可以同时处理多个连接,提高了系统的吞吐量和并发性能。

  • 可扩展性:Netty提供了灵活的、可扩展的架构,可以根据具体需求方便地进行定制和扩展。它提供了丰富的API和组件,如编解码器、多种协议支持、SSL/TLS安全传输等。

  • 容易使用:Netty的API设计简洁、易于使用,提供了高层次的抽象,封装了底层复杂的网络通信细节,使开发人员能够更专注于业务逻辑的实现。

  • 跨平台:Netty可以跨多种操作系统平台使用,例如Windows、Linux、macOS等。

  • 广泛应用:Netty被广泛应用于构建各种网络服务器和客户端,如Web服务器、聊天服务器、即时通讯应用、游戏服务器、物联网设备通信等。

Netty的核心组件包括Channel、EventLoop、ChannelHandler和ChannelPipeline。Channel表示一个客户端或服务器端的连接通道,EventLoop提供了异步事件处理的机制,ChannelHandler负责处理各种事件和数据,而ChannelPipeline则将多个ChannelHandler组合起来形成处理链,对数据进行处理和传递。

总之,Netty是一个强大的网络应用框架,具备高性能、异步事件驱动和可扩展性等优势,适合于开发高性能、可靠、可扩展的网络应用程序。

2、工作流程

Netty的工作流程可以简单概括为以下几个步骤:

  • 创建引导程序:首先需要创建一个引导程序(Bootstrap),它是Netty应用程序的启动器。引导程序负责设置应用程序的各种参数,并准备启动。

  • 设置通道(Channel):在引导程序中,需要设置通道,例如使用SocketChannel作为通信通道,用于连接远程服务器或接受客户端的连接。通过Channel可以获取底层的I/O连接和相关的事件。

  • 配置处理器(Handler):每个Channel都有一个ChannelPipeline,它是一个事件处理器链。在这一步,需要配置ChannelPipeline,并添加相应的ChannelHandler,用于处理和转换各种事件和数据。

  • 启动引导程序:在设置好通道和处理器后,可以通过引导程序的bind方法绑定服务端的本地地址,或者通过connect方法连接远程服务器。启动引导程序后,Netty开始监听和处理事件和数据。

  • 处理事件和数据:引导程序会启动一个或多个EventLoop,每个EventLoop负责监听一个或多个通道的事件。当有事件发生时,EventLoop将事件分发给对应的ChannelHandler进行处理。ChannelHandler可以执行读/写操作、协议解析、业务逻辑处理等。

  • 关闭和释放资源:在完成网络通信后,需要关闭连接并释放资源。可以通过调用close方法主动关闭连接,或者在异常情况下自动触发关闭和释放资源的操作。

需要注意的是,Netty是基于事件驱动模型的,它采用了多线程和线程池技术,并且使用非阻塞I/O,可以高效地处理多个并发连接。通过ChannelPipeline和ChannelHandler的组合方式,可以灵活地处理各种事件和数据,实现复杂的业务逻辑。

总结起来,Netty的工作流程包括创建引导程序、设置通道和处理器、启动引导程序、处理事件和数据、关闭和释放资源等步骤。这个简要的流程描述了Netty应用程序在网络通信过程中的基本工作流程。

3、代码案例

服务端代码:

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 NettyServerExample {
    private final int port;

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

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

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

            ChannelFuture f = b.bind(port).sync();
            System.out.println("服务器启动,监听端口:" + port);

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

    public static void main(String[] args) throws InterruptedException {
        int port = 8888;
        NettyServerExample server = new NettyServerExample(port);
        server.start();
    }
}

客户端代码:

import io.netty.bootstrap.Bootstrap;
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.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.util.Scanner;

public class NettyClientExample {
    private final String host;
    private final int port;

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

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

        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(
                                    new StringDecoder(),
                                    new StringEncoder(),
                                    new NettyClientHandler()
                            );
                        }
                    })
                    .option(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture f = b.connect(host, port).sync();
            System.out.println("连接至服务器:" + host + ":" + port);

            Scanner scanner = new Scanner(System.in);
            while (true) {
                String message = scanner.nextLine();
                if ("exit".equals(message)) {
                    break;
                }
                f.channel().writeAndFlush(message);
            }

            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        String host = "localhost";
        int port = 8888;
        NettyClientExample client = new NettyClientExample(host, port);
        client.start();
    }
}

上述代码中,服务端启动时创建了一个NioEventLoopGroup作为主线程组,负责接受客户端的连接请求,以及一个NioEventLoopGroup作为工作线程组,负责处理客户端的请求。

服务端通过ServerBootstrap进行配置,绑定指定的端口,并设置一些选项和处理器。在这个例子中,通过ChannelInitializer来添加编解码器和自定义的服务器处理器NettyServerHandler

客户端启动时创建了一个NioEventLoopGroup作为工作线程组,通过Bootstrap进行配置,设置远程服务器地址和端口,添加编解码器和自定义的客户端处理器NettyClientHandler

在客户端启动之后,可以通过控制台输入发送给服务端的消息,当输入"exit"时,客户端将关闭。

需要注意的是,上述示例中NettyServerHandlerNettyClientHandler是自定义的处理器,根据实际需求进行编写。

  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值