Подробное объяснение и интеграция WebSocket с высокопроизводительной Netty.

Netty
Подробное объяснение и интеграция WebSocket с высокопроизводительной Netty.

Netty With WebSocket

В прошлой статье мы говорили о том, как использоватьNettyразработатьHttpфайловый сервер, который содержит информацию о том, как использоватьNettyПредоставленный класс компонента для анализаHttpПосле протокола обрабатывается запрос, а затем кодек и передача продолжаются через существующие компоненты.

В этой статье в основном рассказывается о том, как использоватьNettyИнтегрироватьWebSocketсделай одинDEMOстатья. Фактически, какHttpТакой же,NettyВерноWebSocketИнкапсуляция предоставляет несколько удобных и простых в использовании компонентов, которые вы можете использовать, пока пишете код. В этот момент я еще раз испыталNettyвысокой масштабируемости.

Затем в этой статье будет описано содержание

  1. чтоWebSocket.
  2. WebSocketа такжеHttpразница.
  3. зачем нам нужноWebSocket.
  4. Nettyа такжеWebSocketинтеграция между.

чтоWebSocket

Так большинство людей описывает это

WebSocketдаHTML5Обеспечивает связь между браузером и серверомполнодуплексная связьсетевые технологии.

Это кажется довольно непонятным.HTML5мы понимаем, но全双工通信Мы не понимаем. Итак, давайте проанализируем, что это такоеWebSocket.

Из того, что мы узнали раньше, мы знаемSocketЭто функциональный интерфейс между транспортным уровнем и приложением, через который мы можем использоватьTCP/IPСтек протоколов отправляет и получает данные на транспортном уровне. ТакWebSocketКакая связь с этим? отWebSocketБуквально, мы можем разделить называетсяWebа такжеSocket. может ты сможешьGETда, не так ли?WebSocketкак будто бежитWebвыше, ответственныйHttpВверхSocketСпецификация связи?

Конечно!WebSocketтак сказать на основеHttpпротоколSocketспецификации связи, обеспечивающие последующуюTCP SocketАналогичная функция, это может быть какTCP SocketОн также вызывает стек протоколов нижнего уровня для произвольной отправки и получения данных. Но не думайWebSocketдаHttpОбновленная версия (по причинам, которые будут описаны ниже). Фактически,WebSocketосновывается наTCPОблегченный сетевой протокол связи, статус иHttpравно.

зачем нам нужноWebSocket ?

Прежде всего, мы должны понимать, что появление новых вещей в той же области, скорее всего, не ниспровергнет прежнее, а продолжит совершенствоваться на плечах гигантов. а такжеWebSocketоказывается, на самом деле, чтобы компенсироватьHttpДефекты.

согласно сWebSocketвведение, мы знаем, что этополнодуплексная связьсетевые технологии. а такжеHttpэтополудуплексТехнология.HttpУ этой технологии есть две особенности.

  1. Между клиентом и сервером одновременно допускается только односторонний поток данных
  2. Сервер не может активно отправлять данные клиенту и может использовать толькоответ на запросспособ «пассивно» отвечать на запросы клиентов.

полудуплексКакие проблемы это принесет нам? Если вы сделали информацию в режиме реального времени, это может быть более сложным для вас. Вообще говоря, общение в реальном времени требует взаимодействия между двумя сторонами, то есть вы можете отправить его ему, а он может отправить вам. Но очевидно,полудуплексОн может быть отправлен только от клиента к серверу, и сервер не может отправить его клиенту. Может быть, вы скажете, разве мой клиент не может время от времени обращаться к серверу?

В режиме реального времени, не болееWebSocketВ случае , метод «опроса» обычно используется для достижения равномерного обмена данными, то есть для непрерывного запроса к серверу. Если частота опроса относительно высока, то ее можно аппроксимироватьобщение в реальном времениЭффект

Однако недостаток опроса также очевиден.CPUресурсов, очень неэкономично.

так,WebSocketэтополнодуплексная связь.

WebSocketспециальность

Может быть, есть еще много людей, которые сомневаются: очевидно, я вижуWebSocketосновывается наHttpвзаимодействовать, его формат протокола не совпадает сHttpЕсть ли сходство? Не совсем. мы знаемHttpПо сути, это лидер нынешних протоколов интернет-коммуникаций, и нет никого. ноWebSocketне использовал многоHttpчто-то, наоборот, имеет следующие характеристики

  1. первыйWebSocketиспользует структуру бинарного фрейма, сHttpструктура совсем другая. Однако, чтобы облегчить продвижение и применение, мы должны взять на себя «бесплатную поездку» и изо всех сил стараться использовать привычки, насколько это возможно.HttpПодойди поближе, вот как это называетсяWebимея в виду. (Я объясню, почему автостоп ниже)
  2. Второй,WebSocketНе похожSocketиспользовать такIP+端口метод, но продолжениеHttpизURIФормат. ноURLне начинается сHttp, ноwsа такжеwss, которые являются открытым текстом и зашифрованы соответственноWebSocketпротокол.
  3. Более того,WebSocketПорт по умолчанию по-прежнему используется80а также443. Поскольку текущий серверный брандмауэр в Интернете блокирует большинство портов, толькоHttpиз80а также443отпусти, такWebSocketмогукамуфляжсталиHttpсоглашениепроникатьБрандмауэр, установить соединение с сервером.

Процесс взаимодействия WebSocket

говорящийWebSocketКогда дело доходит до характеристик , возможно, вы знаете немного о внутренней истории:WebSocketна самом деле иHttpЭто не имеет большого значения, простоWebSocketЗависит отHttp«Слава» взлетела!

Не торопитесь, давайте посмотримWebSocketпоследовательность взаимодействия. Вот интерактивная диаграмма общей картины:

мы видим,WebSocketЕсть и похожиеTCPпроцесс рукопожатия. сначала он испускаетHttpизGetЗапрос, ниже приведены подробности сообщения

GET /HTTP/1.1
Upgrader: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Set-WebSocket-Key: sNNdMgdc2VGJEKS
Set-WebSocket-Version: 13

В сообщении стоит обратить внимание на несколько полей

имя поля использовать
Upgrade Установить какWebSocket, указывая на то, что вам нужно объяснить серверуHttpобновитесь доWebSocketпротокол
Sec-WebSocket-key Base64Закодированное 16-байтовое случайное число, используемое для проверки правильностиWebSocketвместоHttpпротокол
Sec-WebSocket-Version указать использованиеWebSocketНомер версии протокола

Затем, когда сервер получает сообщение от клиента, он начинает анализировать сообщение. В это время он знает из сообщения, что этоWebSocketзапрос. Итак, начните создавать специальную информацию о сообщении, содержание сообщения

HTTP/1.1 101 Switching Protocols
Upgrader: websocket
Connection: Upgrade
Set-WebSocket-Accept: fFBooB7FAkKLlXgrSz0BT3v4hq5s
Set-WebSocket-Location: ws://examples.com/

Поля сообщения все еще знакомы. но мы нашли101 Switching ProtocolsОписание, которое возвращает сервер101Код состояния, сообщающий клиенту, что он может продолжитьWebSocketПолнодуплексная двусторонняя связь. Это эквивалентно тому, что клиент и сервер согласились использоватьWebSocketПриходите пообщаться, его больше нетHttpчто случилось.

Затем выше возвращаетсяSet-WebSocket-AcceptОн используется для проверки сообщения запроса клиента, а также для предотвращения неправильного подключения. В частности, у клиентаSet-WebSocket-Keyпровести специальнуюUUID, а затем вычислитьSHA-1Резюме. Таким образом, клиент также будет использовать это вычисление для сравнения информации ответа сервера, чтобы избежать ошибки аутентификации.

После завершения рукопожатия последующая передача данных прекращается.Httpсообщение, ноWebSocketтакже отформатируйте двоичный фрейм.

NettyИнтегрироватьWebSocket

Прежде всего, мы используемNettyЧтобы реализовать класс запуска на стороне сервера

WebSocketServer.java

public class WebSocketServer {
    public void run(int port) throws Exception {
	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
			protected void initChannel(SocketChannel ch)
				throws Exception {
                     // http 的解码器
			    ChannelPipeline pipeline = ch.pipeline();
			    pipeline.addLast("http-codec",
				    new HttpServerCodec());
                    //  负责将 Http 的一些信息例如版本
                    // 和 Http 的内容继承一个 FullHttpRequesst
			    pipeline.addLast("aggregator",
				    new HttpObjectAggregator(65536));
                    // 大文件写入的类
			    ch.pipeline().addLast("http-chunked",
				    new ChunkedWriteHandler());
                    // websocket 处理类
			    pipeline.addLast("handler",
				    new WebSocketServerHandler());
			}
		    });
        // 监听端口
	    Channel ch = b.bind(port).sync().channel();
	    ch.closeFuture().sync();
	} finally {
	    bossGroup.shutdownGracefully();
	    workerGroup.shutdownGracefully();
	}
    }


    public static void main(String[] args) throws Exception {
	    new WebSocketServer().run(8080);
    }
}

Далее мы являемся процессором, реализующим логику обработкиWebSocketServerHandler.java

public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {
    private static final Logger logger = Logger
	    .getLogger(WebSocketServerHandler.class.getName());
        
    private WebSocketServerHandshaker handshaker;

    @Override
    public void messageReceived(ChannelHandlerContext ctx, Object msg)
	    throws Exception {
	// 传统的HTTP接入(握手流程是走这里的)
	if (msg instanceof FullHttpRequest) {
	    handleHttpRequest(ctx, (FullHttpRequest) msg);
	}
	// WebSocket接入
	else if (msg instanceof WebSocketFrame) {
	    handleWebSocketFrame(ctx, (WebSocketFrame) msg);
	}
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
	ctx.flush();
    }

    private void handleHttpRequest(ChannelHandlerContext ctx,
	    FullHttpRequest req) throws Exception {

	// 如果HTTP解码失败,返回HHTP异常
	if (!req.getDecoderResult().isSuccess()
		|| (!"websocket".equals(req.headers().get("Upgrade")))) {
	    sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1,
		    BAD_REQUEST));
	    return;
	}

	// 构造握手响应返回,目前是本机的地址
	WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
		"ws://localhost:8080/websocket", null, false);
	handshaker = wsFactory.newHandshaker(req);
	if (handshaker == null) {
	    WebSocketServerHandshakerFactory
		    .sendUnsupportedWebSocketVersionResponse(ctx.channel());
	} else {
	    handshaker.handshake(ctx.channel(), req);
	}
    }

    private void handleWebSocketFrame(ChannelHandlerContext ctx,
	    WebSocketFrame frame) {

	// 判断是否是关闭链路的指令
	if (frame instanceof CloseWebSocketFrame) {
	    handshaker.close(ctx.channel(),
		    (CloseWebSocketFrame) frame.retain());
	    return;
	}

	// 判断是否是Ping消息
	if (frame instanceof PingWebSocketFrame) {
	    ctx.channel().write(
		    new PongWebSocketFrame(frame.content().retain()));
	    return;
	}

	// 本例程仅支持文本消息,不支持二进制消息
	if (!(frame instanceof TextWebSocketFrame)) {
	    throw new UnsupportedOperationException(String.format(
		    "%s frame types not supported", frame.getClass().getName()));
	}

	// 返回应答消息
	String request = ((TextWebSocketFrame) frame).text();
	if (logger.isLoggable(Level.FINE)) {
	    logger.fine(String.format("%s received %s", ctx.channel(), request));
	}

	ctx.channel().write(
		new TextWebSocketFrame(request
			+ " , 欢迎使用Netty WebSocket服务,现在时刻:"
			+ new java.util.Date().toString()));
    }

    private static void sendHttpResponse(ChannelHandlerContext ctx,
	    FullHttpRequest req, FullHttpResponse res) {
	// 返回应答给客户端
	if (res.getStatus().code() != 200) {
	    ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(),
		    CharsetUtil.UTF_8);
	    res.content().writeBytes(buf);
	    buf.release();
	    setContentLength(res, res.content().readableBytes());
	}

	// 如果是非Keep-Alive,关闭连接
	ChannelFuture f = ctx.channel().writeAndFlush(res);
	if (!isKeepAlive(req) || res.getStatus().code() != 200) {
	    f.addListener(ChannelFutureListener.CLOSE);
	}
    }

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

На самом деле приведенный выше код относительно ясен. В основном делится на два процесса:

метод иллюстрировать
handleHttpRequest() Ответственный за ответ на запрос рукопожатия клиента
handleWebSocketFrame() ответственный за обработкуWebSocketНовости

во время обработкиhandleWebSocketFrame()В основном отвечает за несколько операций:

  1. Это команда закрыть ссылку
  2. Является ли это сообщением сердцебиения
  3. Является ли это содержанием сообщения

Эпилог

Эта статья предназначалась дляNettyа такжеWebSocketинтегрированных. Но я узнал, чтоWebSocketРост и причины его популярности гораздо менее просты, чем предполагалось. Вот и решил копнуть глубже. Обобщить содержание статьи

  1. WebSocketэквивалентноHttpПатч длительного подключения для протокола. это иHttpСуществующие общие черты (использование рукопожатия соединенияGetЗапрос), первый - это решитьHttpНе поддерживает отсутствие длинных подключений, решитьHttpХарактеристики коротких ссылок, не отвечающих спросу.
  2. Во внутренней структуре,WebSocketа такжеHttpЕсть много различий. Чтобы повысить эффективность,WebSocketИспользуя двоичные кадры, их легче понять и передать.
  3. использоватьNettyПроцессор может быть реализован быстро упакованнымWebSocketПриложения

конец!