Directory:

  1. demand
  2. design
  3. implementation
    1. Create a Maven project and import Netty 4.1.16.
    2. Project directory structure
    3. Design of the interface
    4. Provider-specific implementation
    5. Consumer-specific implementation
    6. The test results
  4. conclusion

Github source code address

preface

As we all know, Dubbo bottom use Netty as a network communication framework, and Netty’s high performance we have analyzed the source code before, he is also calculated or more understanding. Today we will use Netty to implement a simple RPC framework.

1. The demand

Mimicking Dubbo, the consumer and provider agree on interfaces and protocols where the consumer remotely invokes the provider, the provider returns a string, and the consumer prints the data returned by the provider. Use Netty for underlying network communication.

Design of 2.

  1. Create an interface to define abstract methods. Used for contracts between consumers and providers.
  2. Create a provider that listens for consumer requests and returns data by convention.
  3. Create a consumer class that transparently calls its own non-existent methods and internally requests the provider to return data using Netty.

3. The implementation

1. Create a Maven project and import Netty 4.1.16.

  <groupId>cn.thinkinjava</groupId>
  <artifactId>rpc-demo</artifactId>
  <version>1.0 the SNAPSHOT</version>

  <dependencies>
    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-all</artifactId>
      <version>4.1.16. The Final</version>
    </dependency>
Copy the code

2. The project directory structure is as follows:

3. Design interfaces

A simple Hello World:

public interface HelloService {
  String hello(String msg);
}
Copy the code

4. Provider-specific implementation

4.1. First implement the convention interface for returning client data:

/** * implementation class */
public class HelloServiceImpl implements HelloService {
  public String hello(String msg) {
    returnmsg ! =null ? msg + " -----> I am fine." : "I am fine."; }}Copy the code

4.2. Implement Netty server and custom Handler

Netty Server:

  private static void startServer0(String hostName, int port) {
    try {
      ServerBootstrap bootstrap = new ServerBootstrap();
      NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
      bootstrap.group(eventLoopGroup)
          .channel(NioServerSocketChannel.class)
          .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
              ChannelPipeline p = ch.pipeline();
              p.addLast(new StringDecoder());
              p.addLast(new StringEncoder());
              p.addLast(newHelloServerHandler()); }}); bootstrap.bind(hostName, port).sync(); }catch(InterruptedException e) { e.printStackTrace(); }}Copy the code

The above code adds a String codec handler, adding a custom handler.

The custom handler logic is as follows:

/** * is used to process request data */
public class HelloServerHandler extends ChannelInboundHandlerAdapter {
  @Override
  public void channelRead(ChannelHandlerContext ctx, Object msg) {

    // Call a local method and return data
    if (msg.toString().startsWith(ClientBootstrap.providerName)) {
      String result = new HelloServiceImpl()
          .hello(msg.toString().substring(msg.toString().lastIndexOf("#") + 1)); ctx.writeAndFlush(result); }}}Copy the code

This display determines compliance (no complicated protocol, just a string), then creates a concrete implementation class and calls a method to write back to the client.

You also need a startup class:

public class ServerBootstrap {
  public static void main(String[] args) {
    NettyServer.startServer("localhost".8088); }}Copy the code

The main thing is to create a Netty server and implement a custom handler. The custom handler determines if it complies with the convention. If it does, it creates an implementation class for the interface and calls its methods to return a string.

5. Consumer-related implementations

One caveat for the consumer is that the invocation needs to be transparent, that is, the framework consumer doesn’t care about the underlying network implementation. Here we can use the JDK’s dynamic proxy for this purpose.

The client calls the proxy method, returns a proxy object that implements the HelloService interface, calls the proxy object’s method, and returns the result.

We need to fiddle with the proxy. When we call the proxy method, we need to initialize the Netty client, and we need to request data from the server and return it.

5.1. First create the proxy-related classes

public class RpcConsumer {

  private static ExecutorService executor = Executors
      .newFixedThreadPool(Runtime.getRuntime().availableProcessors());

  private static HelloClientHandler client;

  /** * Create a proxy object */
  public Object createProxy(finalClass<? > serviceClass,final String providerName) {
    return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
        newClass<? >[]{serviceClass}, (proxy, method, args) -> {if (client == null) {
            initClient();
          }
          // Set parameters
          client.setPara(providerName + args[0]);
          return executor.submit(client).get();
        });
  }

  /** * Initializes the client */
  private static void initClient(a) {
    client = new HelloClientHandler();
    EventLoopGroup group = new NioEventLoopGroup();
    Bootstrap b = new Bootstrap();
    b.group(group)
        .channel(NioSocketChannel.class)
        .option(ChannelOption.TCP_NODELAY, true)
        .handler(new ChannelInitializer<SocketChannel>() {
          @Override
          public void initChannel(SocketChannel ch) throws Exception {
            ChannelPipeline p = ch.pipeline();
            p.addLast(new StringDecoder());
            p.addLast(newStringEncoder()); p.addLast(client); }});try {
      b.connect("localhost".8088).sync();
    } catch(InterruptedException e) { e.printStackTrace(); }}}Copy the code

This class has two methods, creating the proxy and initializing the client.

Initialize the client logic: Create a Netty client, connect to the provider, and set up a custom handler, and some String codecs.

Create proxy logic: Using the JDK’s dynamic proxy technology, the invoke method in the proxy object is implemented as follows: If the client is not initialized, it initializes the client, which is both a handler and a Callback. Set the parameter to client, use the thread pool to call client’s call method and block for data to return.

Take a look at the HelloClientHandler implementation:

public class HelloClientHandler extends ChannelInboundHandlerAdapter implements Callable {

  private ChannelHandlerContext context;
  private String result;
  private String para;

  @Override
  public void channelActive(ChannelHandlerContext ctx) {
    context = ctx;
  }

  /** * Wake up the waiting thread */
  @Override
  public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) {
    result = msg.toString();
    notify();
  }

  /** * write out the data and start waiting for the wake up */
  @Override
  public synchronized Object call(a) throws InterruptedException {
    context.writeAndFlush(para);
    wait();
    return result;
  }

  void setPara(String para) {
    this.para = para; }}Copy the code

This class caches the ChannelHandlerContext for next use and has two properties: return result and request parameters.

When the connection is successful, cache the ChannelHandlerContext, send the request parameters to the server when the call method is called, and wait. When the server receives and returns data, it calls the channelRead method, assigns the return value to result, and wakes up the thread waiting on the call method. At this point, the proxy object returns data.

Take a look at the designed test class:

public class ClientBootstrap {

  public static final String providerName = "HelloService#hello#";

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

    RpcConsumer consumer = new RpcConsumer();
    // Create a proxy object
    HelloService service = (HelloService) consumer
        .createProxy(HelloService.class, providerName);
    for(; ;) { Thread.sleep(1000);
      System.out.println(service.hello("are you ok ?")); }}}Copy the code

The test class first creates a proxy object, then calls the proxy’s Hello method every second and prints the results returned by the server.

The test results

Successfully printed.

conclusion

Watched Netty source for so long, we finally realized an own Netty applications, while the application is very simple, even some rough code writing, but the function is achieved, the purpose of RPC is allowed as calling a local service calls to the remote service, need to be transparent to the user, so we use the dynamic proxy. Netty’s handler is used to send data and response data to complete a simple RPC call.

Of course, the same words, the code is relatively simple, mainly the idea, and understand the implementation of RPC bottom.

All right. Good luck!!!!