Spring Boot создает TCP-сервер

Java

В этом примере сначала представлен собственный API Java для реализации связи BIO, затем расширена реализация связи NIO, и, наконец, используется Netty для реализации связи NIO и введения основных компонентов модуля Netty.

Netty — это среда асинхронных сетевых приложений, управляемая событиями, для быстрой разработки поддерживаемых высокопроизводительных протокольных серверов и клиентов.

Решение BIO (блокировка ввода-вывода)

Схема модели связи BIO (один запрос и один ответ) выглядит следующим образом.

Сервер, использующий коммуникационную модель BIO, обычно имеет независимый поток Acceptor, отвечающий за мониторинг клиентского соединения. Обычно мы отслеживаем запрос, вызывая метод accept() в цикле while(true) для ожидания подключения от клиента. После получения запроса на подключение можно выполнять операции чтения и записи в коммуникационном сокете. Коммуникационная модель BIO может обрабатывать несколько клиентских запросов одновременно, необходимо использовать многопоточность (основная причина — socket.accept(), socket.read(), socket.write(). Три основные задействованные функции синхронно заблокированы)

Код

БИО-сервер

BIOServer.java

package com.easy.javaBio;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

@Slf4j
public class BIOServer {
    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(10002);
        while (true) {
            Socket client = server.accept(); //等待客户端的连接,如果没有获取连接  ,在此步一直等待
            new Thread(new ServerThread(client)).start(); //为每个客户端连接开启一个线程
        }
        //server.close();
    }
}

@Slf4j
class ServerThread extends Thread {

    private Socket client;

    public ServerThread(Socket client) {
        this.client = client;
    }

    @SneakyThrows
    @Override
    public void run() {
        log.info("客户端:" + client.getInetAddress().getLocalHost() + "已连接到服务器");
        BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
        //读取客户端发送来的消息
        String mess = br.readLine();
        log.info("客户端:" + mess);
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
        bw.write(mess + "\n");
        bw.flush();
    }
}

БИО клиент

BIOClient.java

package com.easy.javaBio;

import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.net.Socket;

@Slf4j
public class BIOClient {

    public static void main(String[] args) throws IOException {
        Socket s = new Socket("0.0.0.0", 10002);

        InputStream input = s.getInputStream();
        OutputStream output = s.getOutputStream();

        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(output));
        bw.write("客户端给服务端发消息测试\n");  //向服务器端发送一条消息
        bw.flush();

        BufferedReader br = new BufferedReader(new InputStreamReader(input));  //读取服务器返回的消息
        String mess = br.readLine();
        log.info("服务器:" + mess);
    }
}

Запустите пример

Запустите сервер BIO, затем запустите клиент BIO и наблюдайте за консолью.

Вывод консоли BIOServer:

Connected to the target VM, address: '127.0.0.1:64346', transport: 'socket'
17:29:52.519 [Thread-1] INFO com.easy.javaBio.ServerThread - 客户端:YHE6OR5UXQJ6D35/192.168.9.110已连接到服务器
17:29:52.523 [Thread-1] INFO com.easy.javaBio.ServerThread - 客户端:客户端给服务端发消息测试

Вывод консоли BIOClient:

Connected to the target VM, address: '127.0.0.1:64355', transport: 'socket'
17:29:52.527 [main] INFO com.easy.javaBio.BIOClient - 服务器:客户端给服务端发消息测试
Disconnected from the target VM, address: '127.0.0.1:64355', transport: 'socket'

Это значит, что мы реализовали простейшую БИО-коммуникацию

Этот метод открывает поток для каждого клиента, который потребляет больше ресурсов при высокой степени параллелизма, легко теряется и даже приводит к сбою сервера, что негативно сказывается на производительности.Не рекомендуется для высокой степени параллелизма.

Схема NIO (новый ввод-вывод)

Схема модели связи NIO выглядит следующим образом.

NIO — это синхронная неблокирующая модель ввода-вывода.Среда NIO была представлена ​​в Java 1.4, которая соответствует пакету java.nio и предоставляет такие абстракции, как Channel, Selector и Buffer.

N в NIO можно понимать как неблокирующий, а не просто новый. Он поддерживает методы операций ввода-вывода на основе буфера и канала. NIO предоставляет две разные реализации канала сокетов, SocketChannel и ServerSocketChannel, соответствующие Socket и ServerSocket в традиционной модели BIO Оба канала поддерживают блокирующий и неблокирующий режимы. Блокирующий режим используется так же, как и традиционная поддержка, которая относительно проста, но производительность и надежность не очень хороши; неблокирующий режим как раз наоборот. Для малонагруженных приложений с низким уровнем параллелизма можно использовать синхронный блокирующий ввод-вывод для повышения скорости разработки и удобства обслуживания; для приложений с высокой нагрузкой и высоким уровнем параллелизма (сетевых) для разработки следует использовать неблокирующий режим NIO.

NIO-сервер

NIOServer.java

package com.easy.javaBio;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
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.util.*;

@Slf4j
public class NIOServer {
    private InetAddress addr;
    private int port;
    private Selector selector;

    private static int BUFF_SIZE = 1024;

    public NIOServer(InetAddress addr, int port) throws IOException {
        this.addr = addr;
        this.port = port;
        startServer();
    }

    private void startServer() throws IOException {
        // 获得selector及通道(socketChannel)
        this.selector = Selector.open();
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);

        // 绑定地址及端口
        InetSocketAddress listenAddr = new InetSocketAddress(this.addr, this.port);
        serverChannel.socket().bind(listenAddr);
        serverChannel.register(this.selector, SelectionKey.OP_ACCEPT);

        log.info("NIOServer运行中...按下Ctrl-C停止服务");

        while (true) {
            log.info("服务器等待新的连接和selector选择…");
            this.selector.select();

            // 选择key工作
            Iterator keys = this.selector.selectedKeys().iterator();
            while (keys.hasNext()) {
                SelectionKey key = (SelectionKey) keys.next();

                // 防止出现重复的key,处理完需及时移除
                keys.remove();

                //无效直接跳过
                if (!key.isValid()) {
                    continue;
                }
                if (key.isAcceptable()) {
                    this.accept(key);
                } else if (key.isReadable()) {
                    this.read(key);
                } else if (key.isWritable()) {
                    this.write(key);
                } else if (key.isConnectable()) {
                    this.connect(key);
                }
            }
        }
    }

    private void connect(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        if (channel.finishConnect()) {
            // 成功
            log.info("成功连接了");
        } else {
            // 失败
            log.info("失败连接");
        }
    }

    private void accept(SelectionKey key) throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
        SocketChannel channel = serverChannel.accept();
        channel.configureBlocking(false);
        channel.register(this.selector, SelectionKey.OP_READ);

        Socket socket = channel.socket();
        SocketAddress remoteAddr = socket.getRemoteSocketAddress();
        log.info("连接到: " + remoteAddr);
    }

    private void read(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();

        ByteBuffer buffer = ByteBuffer.allocate(BUFF_SIZE);
        int numRead = channel.read(buffer);
        if (numRead == -1) {
            log.info("关闭客户端连接: " + channel.socket().getRemoteSocketAddress());
            channel.close();
            return;
        }
        String msg = new String(buffer.array()).trim();
        log.info("得到了: " + msg);

        // 回复客户端
        String reMsg = msg + " 你好,这是BIOServer给你的回复消息:" + System.currentTimeMillis();
        channel.write(ByteBuffer.wrap(reMsg.getBytes()));
    }

    private void write(SelectionKey key) throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.allocate(BUFF_SIZE);
        byteBuffer.flip();
        SocketChannel clientChannel = (SocketChannel) key.channel();
        while (byteBuffer.hasRemaining()) {
            clientChannel.write(byteBuffer);
        }
        byteBuffer.compact();
    }

    public static void main(String[] args) throws IOException {
        new NIOServer(null, 10002);
    }
}

Используя NIO, Selector можно использовать, чтобы окончательно решить, какой набор зарегистрированных сокетов готов к выполнению ввода-вывода.

NIO-клиент

NIOClient.java

package com.easy.javaBio;

import lombok.extern.slf4j.Slf4j;

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

@Slf4j
public class NIOClient {
    private static int BUFF_SIZE = 1024;

    public static void main(String[] args) throws IOException, InterruptedException {

        InetSocketAddress socketAddress = new InetSocketAddress("0.0.0.0", 10002);
        SocketChannel socketChannel = SocketChannel.open(socketAddress);

        log.info("连接 BIOServer 服务,端口:10002...");

        ArrayList<String> companyDetails = new ArrayList<>();

        // 创建消息列表
        companyDetails.add("腾讯");
        companyDetails.add("阿里巴巴");
        companyDetails.add("京东");
        companyDetails.add("百度");
        companyDetails.add("google");

        for (String companyName : companyDetails) {
            socketChannel.write(ByteBuffer.wrap(companyName.getBytes()));
            log.info("发送: " + companyName);

            ByteBuffer buffer = ByteBuffer.allocate(BUFF_SIZE);
            buffer.clear();
            socketChannel.read(buffer);
            String result = new String(buffer.array()).trim();
            log.info("收到NIOServer回复的消息:" + result);

            // 等待2秒钟再发送下一条消息
            Thread.sleep(2000);
        }

        socketChannel.close();
    }
}

Запустите пример

Сначала запустите наш NIOServer, затем NIOClient и посмотрите вывод консоли.

Вывод консоли NIOServer

17:35:40.921 [main] INFO com.easy.javaBio.NIOServer - NIOServer运行中...按下Ctrl-C停止服务
17:35:40.924 [main] INFO com.easy.javaBio.NIOServer - 服务器等待新的连接和selector选择…
17:36:29.188 [main] INFO com.easy.javaBio.NIOServer - 连接到: /192.168.9.110:64443
17:36:29.188 [main] INFO com.easy.javaBio.NIOServer - 服务器等待新的连接和selector选择…
17:36:29.194 [main] INFO com.easy.javaBio.NIOServer - 得到了: 腾讯
17:36:29.194 [main] INFO com.easy.javaBio.NIOServer - 服务器等待新的连接和selector选择…
17:36:31.194 [main] INFO com.easy.javaBio.NIOServer - 得到了: 阿里巴巴
17:36:31.195 [main] INFO com.easy.javaBio.NIOServer - 服务器等待新的连接和selector选择…
17:36:33.195 [main] INFO com.easy.javaBio.NIOServer - 得到了: 京东
17:36:33.195 [main] INFO com.easy.javaBio.NIOServer - 服务器等待新的连接和selector选择…
17:36:35.196 [main] INFO com.easy.javaBio.NIOServer - 得到了: 百度
17:36:35.197 [main] INFO com.easy.javaBio.NIOServer - 服务器等待新的连接和selector选择…
17:36:37.197 [main] INFO com.easy.javaBio.NIOServer - 得到了: google
17:36:37.198 [main] INFO com.easy.javaBio.NIOServer - 服务器等待新的连接和selector选择…
17:36:39.198 [main] INFO com.easy.javaBio.NIOServer - 关闭客户端连接: /192.168.9.110:64443
17:36:39.198 [main] INFO com.easy.javaBio.NIOServer - 服务器等待新的连接和selector选择…

Вывод консоли NIOClient

17:36:29.189 [main] INFO com.easy.javaBio.NIOClient - 连接 BIOServer 服务,端口:10002...
17:36:29.194 [main] INFO com.easy.javaBio.NIOClient - 发送: 腾讯
17:36:29.194 [main] INFO com.easy.javaBio.NIOClient - 收到NIOServer回复的消息:腾讯 你好,这是BIOServer给你的回复消息:1576229789194
17:36:31.194 [main] INFO com.easy.javaBio.NIOClient - 发送: 阿里巴巴
17:36:31.195 [main] INFO com.easy.javaBio.NIOClient - 收到NIOServer回复的消息:阿里巴巴 你好,这是BIOServer给你的回复消息:1576229791194
17:36:33.195 [main] INFO com.easy.javaBio.NIOClient - 发送: 京东
17:36:33.196 [main] INFO com.easy.javaBio.NIOClient - 收到NIOServer回复的消息:京东 你好,这是BIOServer给你的回复消息:1576229793195
17:36:35.196 [main] INFO com.easy.javaBio.NIOClient - 发送: 百度
17:36:35.197 [main] INFO com.easy.javaBio.NIOClient - 收到NIOServer回复的消息:百度 你好,这是BIOServer给你的回复消息:1576229795197
17:36:37.197 [main] INFO com.easy.javaBio.NIOClient - 发送: google
17:36:37.198 [main] INFO com.easy.javaBio.NIOClient - 收到NIOServer回复的消息:google 你好,这是BIOServer给你的回复消息:1576229797198

Сервер NIO будет получать запрос клиента каждые две секунды и отвечать на сообщение клиента.

Можно создавать приложения напрямую с помощью Java NIO API, но сделать это правильно и безопасно непросто. Надежная и эффективная обработка и планирование операций ввода-вывода, особенно при высокой нагрузке, является утомительной и подверженной ошибкам задачей. Можно выбрать высокопроизводительные платформы сетевого программирования, такие как Netty и Apache Mina.

Netty строит схему службы связи NIO

При использовании собственного API сетевого приложения JDK возникнут проблемы

  • Библиотека классов и API NIO сложны и хлопотны в использовании.Вы должны хорошо разбираться в Selector, ServerSocketChannel, SocketChannel, ByteBuffer и т. д.
  • Вам необходимо иметь другие дополнительные навыки, чтобы проложить путь, например, быть знакомым с многопоточным программированием на Java, поскольку программирование NIO включает режим Reactor, вы должны быть хорошо знакомы с многопоточным и сетевым программированием, чтобы писать высококачественные программы NIO.
  • Заполненный потенциал надежности, рабочая нагрузка и сложность разработки очень велики. Например, переподключение лица клиента отключено, сетевые сбои, чтение половины пакета, сбой кеша, перегрузка сети, поток кода обработки исключений и т. Д. Характеризуются функцией программирования NIO, которую относительно легко разработать, но надежность объем работы и трудности очень велики

Netty инкапсулирует NIO API, который поставляется с JDK для решения вышеуказанных проблем.Основные функции:

  • Высокий параллелизм

Netty — это инфраструктура сетевого взаимодействия, разработанная на основе NIO (неблокирующий ввод-вывод, неблокирующий ввод-вывод).По сравнению с BIO (блокирующий ввод-вывод, блокирующий ввод-вывод) его производительность параллелизма значительно улучшена.

  • Быстрая передача

Быстрая передача Netty на самом деле опирается на функцию NIO — нулевое копирование.

  • в упаковке

Netty инкапсулирует многие детали операций NIO и предоставляет простой в использовании API.

Преимущества Нетти Фреймворк

  • API прост в использовании, а порог освоения низкий;
  • Мощный, предустановленный набор функций кодека, поддержка различных основных протоколов;
  • Широкие возможности настройки, коммуникационная структура может быть гибко расширена с помощью ChannelHandler;
  • Высокая производительность по сравнению с другими основными инфраструктурами NIO в отрасли, Netty имеет лучшую общую производительность;
  • Зрелая и стабильная, Netty исправила все обнаруженные ошибки JDK NIO, и бизнес-разработчикам больше не нужно беспокоиться об ошибках NIO;
  • Сообщество активное, цикл итерации версии короткий, найденные ошибки можно вовремя исправить, а новые функции будут добавляться;
  • После крупномасштабного коммерческого тестирования качество было подтверждено. Он был успешно коммерциализирован во многих отраслях, таких как Интернет, большие данные, онлайн-игры, корпоративные приложения и телекоммуникационное программное обеспечение, что доказывает, что он может полностью соответствовать коммерческим приложениям в различных отраслях.

Код

pom.xml зависимости

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.easy</groupId>
    <artifactId>netty</artifactId>
    <version>0.0.1</version>
    <name>netty</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <encoding>UTF-8</encoding>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.43.Final</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    <modules>
        <module>java-tcp</module>
        <module>netty-server</module>
        <module>netty-client</module>
    </modules>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Построить сервер Netty

NettyServer.java

package com.easy.nettyServer;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;

@Component
@Slf4j
public class NettyServer {
    /**
     * boss 线程组用于处理连接工作
     */
    private EventLoopGroup boss = new NioEventLoopGroup();
    /**
     * work 线程组用于数据处理
     */
    private EventLoopGroup work = new NioEventLoopGroup();

    @Value("${netty.port}")
    private Integer port;

    /**
     * 启动Netty Server
     *
     * @throws InterruptedException
     */
    @PostConstruct
    public void start() throws InterruptedException {
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(boss, work)
                // 指定Channel
                .channel(NioServerSocketChannel.class)
                //使用指定的端口设置套接字地址
                .localAddress(new InetSocketAddress(port))

                //服务端可连接队列数,对应TCP/IP协议listen函数中backlog参数
                .option(ChannelOption.SO_BACKLOG, 1024)

                //设置TCP长连接,一般如果两个小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文
                .childOption(ChannelOption.SO_KEEPALIVE, true)

                //将小的数据包包装成更大的帧进行传送,提高网络的负载
                .childOption(ChannelOption.TCP_NODELAY, true)

                .childHandler(new ServerChannelInitializer());
        ChannelFuture future = bootstrap.bind().sync();
        if (future.isSuccess()) {
            log.info("启动 Netty Server");
        }
    }

    @PreDestroy
    public void destory() throws InterruptedException {
        boss.shutdownGracefully().sync();
        work.shutdownGracefully().sync();
        log.info("关闭Netty");
    }
}

NettyServerHandler.java

package com.easy.nettyServer;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    /**
     * 客户端连接会触发
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("Channel active......");
    }

    /**
     * 客户端发消息会触发
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("服务器收到消息: {}", msg.toString());
        ctx.write("我是服务端,我收到你的消息了!");
        ctx.flush();
    }

    /**
     * 发生异常触发
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

ServerChannelInitializer.java

package com.easy.nettyServer;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;

public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        //添加编解码
        socketChannel.pipeline().addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
        socketChannel.pipeline().addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
        socketChannel.pipeline().addLast(new NettyServerHandler());
    }
}

Создайте клиент Netty

NettyClient.java

package com.easy.nettyClient;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
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 lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class NettyClient {

    private EventLoopGroup group = new NioEventLoopGroup();

    @Value("${netty.port}")
    private Integer port;

    @Value("${netty.host}")
    private String host;

    private SocketChannel socketChannel;

    /**
     * 发送消息
     */
    public void sendMsg(String msg) {
        socketChannel.writeAndFlush(msg);
    }

    @PostConstruct
    public void start() {
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .remoteAddress(host, port)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .option(ChannelOption.TCP_NODELAY, true)
                .handler(new NettyClientInitializer());
        ChannelFuture future = bootstrap.connect();
        //客户端断线重连逻辑
        future.addListener((ChannelFutureListener) future1 -> {
            if (future1.isSuccess()) {
                log.info("连接Netty服务端成功");
            } else {
                log.info("连接失败,进行断线重连");
                future1.channel().eventLoop().schedule(() -> start(), 20, TimeUnit.SECONDS);
            }
        });
        socketChannel = (SocketChannel) future.channel();
    }
}

NettyClientHandler.java

package com.easy.nettyClient;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("客户端Active .....");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("客户端收到消息: {}", msg.toString());
    }

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

NettyClientInitializer.java

package com.easy.nettyClient;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class NettyClientInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        socketChannel.pipeline().addLast("decoder", new StringDecoder());
        socketChannel.pipeline().addLast("encoder", new StringEncoder());
        socketChannel.pipeline().addLast(new NettyClientHandler());
    }
}

Запустите пример

Откройте браузер, введите в адресную строку: http://localhost:8091/send?msg=%E4%BD%A0%E5%A5%BD, наблюдайте за консолью сервера и клиента

Вывод консоли сервера

2019-12-13 18:01:37.901  INFO 11288 --- [           main] com.easy.nettyServer.NettyServer         : 启动 Netty Server
2019-12-13 18:01:45.834  INFO 11288 --- [ntLoopGroup-3-1] com.easy.nettyServer.NettyServerHandler  : Channel active......
2019-12-13 18:02:07.858  INFO 11288 --- [ntLoopGroup-3-1] com.easy.nettyServer.NettyServerHandler  : 服务器收到消息: 你好

Вывод клиентской консоли

2019-12-13 18:01:45.822  INFO 11908 --- [ntLoopGroup-2-1] com.easy.nettyClient.NettyClient         : 连接Netty服务端成功
2019-12-13 18:01:45.822  INFO 11908 --- [ntLoopGroup-2-1] com.easy.nettyClient.NettyClientHandler  : 客户端Active .....
2019-12-13 18:02:08.005  INFO 11908 --- [ntLoopGroup-2-1] com.easy.nettyClient.NettyClientHandler  : 客户端收到消息: 我是服务端,我收到你的消息了!

Указывает, что наша связь NIO реализована с использованием Netty.

Компоненты модуля Netty

Bootstrap, СерверBootstrap

Приложение Netty обычно начинается с Bootstrap. Основная функция — настроить всю программу Netty и подключить различные компоненты. Класс Bootstrap в Netty — это класс начальной загрузки клиентской программы, а ServerBootstrap — класс начальной загрузки сервера.

Будущее, КаналБудущее

Все операции ввода-вывода в Netty являются асинхронными, и вы не можете сразу узнать, правильно ли обработано сообщение, но вы можете дождаться его завершения через некоторое время или зарегистрировать прослушиватель напрямую.Конкретная реализация — через Future и ChannelFuture, они могут регистрировать Монитор, когда операция завершается успешно или неудачно, монитор автоматически инициирует зарегистрированное событие монитора.

Channel

Компонент сетевой связи Netty, который можно использовать для выполнения сетевых операций ввода-вывода. Канал предоставляет пользователям:

  • Статус канала для текущего сетевого подключения (например, открыт? Подключен?)
  • Параметры конфигурации для сетевых подключений (например, размер приемного буфера)
  • Обеспечивает асинхронные сетевые операции ввода-вывода (такие как установление соединений, чтение и запись, привязка портов). Асинхронный вызов означает, что любой вызов ввода-вывода будет немедленно возвращен, и нет гарантии, что запрошенная операция ввода-вывода была завершена. в конце разговора Готово. Вызов немедленно возвращает экземпляр ChannelFuture.Зарегистрировав прослушиватель в ChannelFuture, вызывающая сторона может быть уведомлена обратным вызовом, когда операция ввода-вывода успешна, неудачна или отменена.
  • Поддержка связывания операций ввода-вывода с соответствующими обработчиками

Соединения по разным протоколам и разным типам блокировки имеют разные типы каналов, соответствующие им Ниже приведены некоторые часто используемые типы каналов.

  • NioSocketChannel, асинхронное клиентское подключение через сокет TCP
  • NioServerSocketChannel, асинхронное соединение TCP Socket на стороне сервера
  • NioDatagramChannel, асинхронное соединение UDP
  • NioSctpChannel, асинхронное клиентское соединение Sctp
  • NioSctpServerChannel, асинхронное соединение Sctp на стороне сервера

Selector

Netty реализует мультиплексирование ввода-вывода на основе объекта Selector. Через Selector поток может отслеживать события канала нескольких подключений. Когда канал зарегистрирован в Selector, внутренний механизм Selector может автоматически и непрерывно запрашивать (выбирать) эти Есть ли у зарегистрированного канала готовые события ввода-вывода (такие как чтение, запись, завершение сетевого подключения и т. д.), чтобы программа могла просто использовать один поток для эффективного управления несколькими каналами.

NioEventLoop

Поток и очередь задач поддерживается в NioEventLoop, который поддерживает асинхронную отправку и выполнение задач.Когда поток запускается, он вызывает метод запуска NioEventLoop для выполнения задач ввода-вывода и задач, не связанных с вводом-выводом:

  • Задача ввода-вывода — это событие готовности в selectionKey, такое как принятие, подключение, чтение, запись и т. д., которое запускается методом processSelectedKeys.
  • Задачи, не связанные с вводом-выводом Задачи, добавленные в taskQueue, такие как register0, bind0 и другие задачи, запускаются методом runAllTasks.

Соотношение времени выполнения двух задач контролируется переменной ioRatio, которая по умолчанию равна 50, что означает, что время выполнения задачи без ввода-вывода равно времени выполнения задачи ввода-вывода.

NioEventLoopGroup

NioEventLoopGroup, который в основном управляет жизненным циклом eventLoop, можно понимать как пул потоков, внутри которого поддерживается набор потоков.Каждый поток (NioEventLoop) отвечает за обработку событий на нескольких каналах, а один канал соответствует только одному потоку.

ChannelHandler

ChannelHandler — это интерфейс, который обрабатывает события ввода-вывода или перехватывает операции ввода-вывода и перенаправляет их следующему обработчику в ChannelPipeline (цепочке бизнес-обработки).

ChannelHandlerContext

Сохраните всю контекстную информацию, связанную с каналом, и одновременно свяжите объект ChannelHandler.

ChannelPipline

Содержит список обработчиков каналов для обработки или перехвата входящих событий канала и исходящих операций. ChannelPipeline реализует расширенную форму шаблона фильтра перехвата, который дает пользователю полный контроль над тем, как обрабатываются события и как отдельные ChannelHandlers в канале взаимодействуют друг с другом.

материал

Spring Boot, проект облачного обучения