Netty Combat (1) — написание сервера WebSocket

Netty
Netty Combat (1) — написание сервера WebSocket

Netty очень удобна для связи между Java-программами, просто используйте клиент и сервер Netty напрямую. Но что, если клиент — это браузер (программы на Java обычно не разрабатывают веб-приложения), в этот раз клиент — это программа на JavaScript, а для взаимного общения используется протокол WebScoket.

Протокол веб-сокета

концепция

Те, кто знаком с сетевым программированием на Java, должны знать о программировании сокетов, технологии, используемой для связи между программами на Java (программирование сокетов также называется программированием сокетов, которое представляет собой абстракцию между транспортным уровнем и прикладным уровнем в TCP/IP). Протокол IP. Уровень сокетов, который предоставляет интерфейс для облегчения вызова прикладного уровня для реализации связи между сетями. Программирование сокетов далее делится на OIO и NIO, а Netty представляет собой коммуникационную структуру, инкапсулированную на основе NIO).

Тогда WebSocket легко понять: перед ним добавляется Web, чтобы указать, что этот протокол используется для связи в веб-приложениях (аналогично Socket, который также является двусторонним). Тогда в Интернете нет HTTP, так зачем вам WebSocket? Недостатком HTTP является то, что связь может быть инициирована только клиентом, а сервер не может активно инициировать связь с клиентом. Это приводит к тому, что если клиент хочет получить изменения или уведомления от сервера, он может только опрашивать или поддерживать длинное HTTP-соединение, что очень расточительно по ресурсам. WebSocket был создан, чтобы решить эту проблему.

Википедия:

WebSocket — это сетевой транспортный протокол, обеспечивающий полнодуплексную связь по одному TCP-соединению и расположенный на прикладном уровне модели OSI. WebSocket упрощает обмен данными между клиентом и сервером, позволяя серверу активно передавать данные клиенту. В API WebSocket браузеру и серверу нужно только выполнить рукопожатие, и между ними может быть создано постоянное соединение, и может быть выполнена двусторонняя передача данных.

Функции

Его основные особенности заключаются в следующем.

  1. На основе протокола TCP сервер может активно передавать информацию клиенту, а клиент также может активно отправлять информацию серверу.двусторонняя связь.
  2. Он имеет хорошую совместимость с протоколом HTTP. Порты по умолчанию также 80 и 443, иНа этапе рукопожатия используется протокол HTTP., поэтому во время рукопожатия защитить его непросто, и он может проходить через различные прокси-серверы HTTP.
  3. Формат данных относительно легкий, производительность невелика, а связь эффективна. Можно отправлять текст, а также двоичные данные.
  4. Ограничений по одному и тому же источнику нет, и клиенты могут взаимодействовать с любым сервером.
  5. Идентификатор протокола — ws (или wss, если используется шифрование SSL/TLS), а URL-адрес сервера — это URL-адрес.

Написание сервера WebSocket на основе Netty

Поскольку протокол WebSocket используется для связи веб-приложений, он по своей сути поддерживает запись серверов в Node. Таким образом, существует множество готовых серверов веб-сокетов на основе узлов. Сервер Java Websocket обычно инкапсулирует еще один уровень программирования Socket.Конечно, для его создания рекомендуется использовать Netty. Ведь Netty — это высокопроизводительный коммуникационный фреймворк, он тоже инкапсулирован на основе NIO, и работа с ним проще.

Основная программа сервера

Основная программа очень проста, как и обычная серверная часть Netty. Инкапсуляция Websocket в основном находится в процессоре.

public class WsServer {

    public static void main(String[] args) {
        // 一个主线程组(用于监听新连接并初始化通道),一个分发线程组(用于IO事件的处理)
        EventLoopGroup mainGroup = new NioEventLoopGroup(1);
        EventLoopGroup subGroup = new NioEventLoopGroup();
        ServerBootstrap sb = new ServerBootstrap();
        try {
            sb.group(mainGroup, subGroup)
                    .channel(NioServerSocketChannel.class)
                    // 这里是一个自定义的通道初始化器,用来添加编解码器和处理器
                    .childHandler(new WsChannelInitializer());
            // 绑定88端口,Websocket服务器的端口就是这个
            ChannelFuture future = sb.bind(88).sync();
            // 一直阻塞直到服务器关闭
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            mainGroup.shutdownGracefully();
            subGroup.shutdownGracefully();
        }
    }
}

инициализатор канала

Поскольку протокол Websocket основан на протоколе HTTP (протокол HTTP используется на этапе рукопожатия), необходимо добавить кодек HTTP и процессор сообщений. Также добавьте обработчик Webscoket для обработки рукопожатия Webscoket и передачи данных и, наконец, добавьте собственный обработчик для обработки событий ввода-вывода для взаимодействия с клиентом.

public class WsChannelInitializer extends ChannelInitializer {

    @Override
    protected void initChannel(Channel ch) {
        ChannelPipeline pipeline = ch.pipeline();
        // websocket是基于http协议的,所以需要使用http编解码器
        pipeline.addLast(new HttpServerCodec())
                // 对写大数据流的支持
                .addLast(new ChunkedWriteHandler())
                // 对http消息的聚合,聚合成FullHttpRequest或FullHttpResponse
                // 在Netty的编程中,几乎都会使用到这个handler
                .addLast(new HttpObjectAggregator(1024 * 64));
        // 以上三个处理器是对http协议的支持

        // websocket 服务器处理的协议,并用于指定客户端连接的路由(这里指定的是 /ws)
        // 这里的URL就是 ws://ip:port/ws
        // 该处理器为运行websocket服务器承担了所有繁重的工作
        // 它会负责websocket的握手以及处理控制帧
        // websocket的数据传输都是以frames进行的
        pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
        // 自定义的处理器
        pipeline.addLast(new WsServerHandler());
    }
}

пользовательский процессор

Этот процессор должен записывать и управлять всеми клиентскими каналами, получать сообщения от клиентов и обрабатывать соответствующие события ввода-вывода (можно привязать уникальный идентификатор пользователя клиента и канал, соответствующий клиенту, при инициализации соединения, а затем выполнить целевые push-сообщения).

// TextWebSocketFrame: 在Netty中,专门用于websocket处理文本消息的对象,frame是消息的载体
public class WsServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    /**
     * 用于记录和管理所有客户端的channel
     */
    private ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        // 获取客户端传输来的文本消息
        String text = msg.text();
        // 这个是自定义的日志工具类,可见其它文章
        LogUtil.info("收到的文本消息:[{}]", text);
        // 在这里可以判断消息类型(比如初始化连接、消息在客户端间传输等)
        // 然后可以将客户端Channel与对应的唯一标识用Map关联起来,就可以做定向推送,而不是广播

        // 写回客户端,这里是广播
        clients.writeAndFlush(new TextWebSocketFrame("服务器收到消息: " + text));
    }

    /**
     * 当客户端连接服务端(打开连接)后
     * 获取客户端的channel,并放到ChannelGroup中进行管理
     * @param ctx ChannelHandlerContext
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        // 不能在这里做关联,因为这里不能接受客户端的消息,是没法绑定的
        clients.add(ctx.channel());
    }

    /**
     * 当触发当前方法时,ChannelGroup会自动移除对应客户端的channel
     * @param ctx ChannelHandlerContext
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) {
        LogUtil.info("客户端断开连接,channel的长ID:[{}]", ctx.channel().id().asLongText());
        LogUtil.info("客户端断开连接,channel的短ID:[{}]", ctx.channel().id().asShortText());
    }
}