使用 AsynchronousSocketChannel 响应数据

tio-boot 的 HTTP 响应是异步的。如果你想在业务处理完发送 HTTP 响应后执行一些额外的操作,可以参考本节内容。

tio-boot 异步发送 HTTP 响应

1. 获取 ChannelContext

首先,从 HttpRequest 中获取 ChannelContext,它包含了当前连接的状态和配置信息。

ChannelContext channelContext = request.getChannelContext();

2. 准备发送的数据

创建一个包含响应数据的 Map,此处只是添加一个简单的键值对。

Map<String, Object> data = new HashMap<>();
data.put("code", 1);

3. 封装为 HttpResponse

使用 TioControllerContext.getResponse() 获取 HttpResponse 对象,然后用 Resps.json 方法将数据封装成 JSON 格式的响应。

HttpResponse httpResponse = TioControllerContext.getResponse();
httpResponse = Resps.json(httpResponse, data);

4. 调用 Handler 进行编码

获取 AioHandler 处理器,该处理器用于对数据进行编码和解码。调用 encode 方法将 httpResponse 编码为可以通过网络发送的字节数据。

TioConfig tioConfig = channelContext.getTioConfig();
AioHandler aioHandler = tioConfig.getAioHandler();
ByteBuffer writeBuffer = aioHandler.encode(httpResponse, tioConfig, channelContext);

5. 异步写入数据到客户端

使用 channelContext.asynchronousSocketChannel.write 异步地将编码后的数据写入客户端。同时传入 request 作为 attachment,并定义 CompletionHandler 来处理写操作的结果。

channelContext.asynchronousSocketChannel.write(writeBuffer, request, new CompletionHandler<Integer, HttpRequest>() {
    ...
});

6. 处理写操作的结果

completed 方法:当写操作成功完成时调用此方法,记录操作结果,并关闭 AsynchronousSocketChannel

public void completed(Integer result, HttpRequest attachment) {
    log.info("result:{},attachment:{}", result, attachment);
    try {
        channelContext.asynchronousSocketChannel.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

failed 方法:当写操作失败时调用此方法,尝试关闭 AsynchronousSocketChannel

public void failed(Throwable exc, HttpRequest attachment) {
    try {
        channelContext.asynchronousSocketChannel.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

这种方法展示了如何异步发送 HTTP 响应,并通过 CompletionHandler 处理操作结果。通过这种方式,服务器可以在不阻塞当前线程的情况下处理多个连接和请求,从而提高性能。

AsynchronousSocketChannel 的 write 方法

AsynchronousSocketChannelwrite 方法用于异步写操作,它接收三个参数:

  1. ByteBuffer src:
    ByteBuffer 对象,包含要写入通道的数据。它提供了高效的数据读写接口,常用于 NIO 的数据输入输出。

  2. A attachment:
    附件对象,可以是任何类型的数据,会被传递给 CompletionHandlercompletedfailed 方法。

  3. CompletionHandler<Integer,? super A> handler:
    处理器接口。当异步操作完成时,无论成功还是失败,都会调用处理器的方法。completed 方法表示操作成功,failed 方法表示操作失败。

示例代码

下面是一个完整的示例代码,展示如何在 tio-boot 中异步发送 HTTP 响应。

package com.litongjava.tio.web.hello.controller;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.CompletionHandler;
import java.util.HashMap;
import java.util.Map;

import com.litongjava.tio.boot.http.TioControllerContext;
import com.litongjava.tio.core.ChannelContext;
import com.litongjava.tio.core.TioConfig;
import com.litongjava.tio.core.intf.AioHandler;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.server.annotation.RequestPath;
import com.litongjava.tio.http.server.util.Resps;

import lombok.extern.slf4j.Slf4j;

@RequestPath("/async")
@Slf4j
public class AsyncController {

  public Map<String, Object> index(HttpRequest request) {
    ChannelContext channelContext = request.getChannelContext();
    TioConfig tioConfig = channelContext.getTioConfig();
    AioHandler aioHandler = tioConfig.getAioHandler();
    String token = channelContext.getToken();

    Map<String, Object> data = new HashMap<>();
    data.put("channelContext", channelContext.toString());
    data.put("tioConfig", tioConfig.toString());
    data.put("userId", channelContext.userid);
    data.put("token", token);
    data.put("asynchronousSocketChannel", channelContext.asynchronousSocketChannel.toString());
    data.put("aioHandler", aioHandler.toString());
    log.info("data:{}", data);
    return data;
  }

  public String writeHttpResponse(HttpRequest request) {
    ChannelContext channelContext = request.getChannelContext();
    // 准备发送的数据
    Map<String, Object> data = new HashMap<>();
    data.put("code", 1);

    // 封装为 HttpResponse
    HttpResponse httpResponse = TioControllerContext.getResponse();
    httpResponse = Resps.json(httpResponse, data);

    // 调用 Handler 进行编码
    TioConfig tioConfig = channelContext.getTioConfig();
    AioHandler aioHandler = tioConfig.getAioHandler();
    ByteBuffer writeBuffer = aioHandler.encode(httpResponse, tioConfig, channelContext);

    // 异步写入数据到客户端
    channelContext.asynchronousSocketChannel.write(writeBuffer);

    return null;
  }

  public String writeHttpResponseWithCompletionHandler(HttpRequest request) {
    ChannelContext channelContext = request.getChannelContext();
    // 准备发送的数据
    Map<String, Object> data = new HashMap<>();
    data.put("code", 1);

    // 封装为 HttpResponse
    HttpResponse httpResponse = TioControllerContext.getResponse();
    httpResponse = Resps.json(httpResponse, data);

    // 调用 Handler 进行编码
    TioConfig tioConfig = channelContext.getTioConfig();
    AioHandler aioHandler = tioConfig.getAioHandler();
    ByteBuffer writeBuffer = aioHandler.encode(httpResponse, tioConfig, channelContext);

    // 异步写入数据到客户端
    channelContext.asynchronousSocketChannel.write(writeBuffer, request, new CompletionHandler<Integer, HttpRequest>() {

      @Override
      public void completed(Integer result, HttpRequest attachment) {
        // 进行其他操作
        log.info("result:{},attachment:{}", result, attachment);

        // 手动关闭连接
        try {
          channelContext.asynchronousSocketChannel.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }

      @Override
      public void failed(Throwable exc, HttpRequest attachment) {
        try {
          channelContext.asynchronousSocketChannel.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }

    });

    return null;
  }
}

访问测试

  1. 访问 http://localhost/async/index:
{
  "aioHandler": "com.litongjava.tio.boot.server.TioBootServerHandler@7a041eef",
  "tioConfig": "ServerTioConfig [name=tio-boot]",
  "channelContext": "server:0.0.0.0:80, client:0:0:0:0:0:0:0:1:4986",
  "asynchronousSocketChannel": "sun.nio.ch.WindowsAsynchronousSocketChannelImpl[connected local=/0:0:0:0:0:0:0:1:80 remote=/0:0:0:0:0:0:0:1:4986]"
}
  1. 访问 http://localhost/async/writeHttpResponse:
{ "code": 1 }
  1. 访问 http://localhost/async/writeHttpResponseWithCompletionHandler:
{ "code": 1 }

以上代码展示了如何使用 AsynchronousSocketChannel 进行异步 HTTP 响应,并在发送响应后执行额外的业务逻辑。这种非阻塞式编程可以显著提升服务器的性能。