Netty implements synchronous "request-response" communication mechanism

demand Implementation of Netty-based "request-response" synchronous communication mechanism. Design thinking Netty provides a unified implem...
The correct match between request and response.
Communication between request threads and response threads.
Client Code
Server-side code
demand

Implementation of Netty-based "request-response" synchronous communication mechanism.

Design thinking Netty provides a unified implementation of asynchronous IO and synchronous IO, but our requirements have nothing to do with the synchronous asynchronization of IO. The key is to realize the typical question-and-answer interaction of request-response. To meet this need, two problems need to be solved:

The correct match between request and response.

After the client sends the data, when the server returns the response result, how does it match the client's request correctly (that is, a request corresponds to its own response)?
Solution: Through the unique RequestId of the client, the response returned by the server needs to include the RequestId, so that the client can match the request response correctly through the RequestId.

Communication between request threads and response threads.

The request thread will wait for the server to return synchronously after the request is made. Therefore, it needs to be resolved how the Netty client notifies the request thread of the result after receiving the response.
Solution: After sending the request, the client thread enters to wait. After the server returns the response, it wakes up the request thread of the client according to RequestId, and returns the result to the request thread.

Solution

The CountDownLatch class in Java is used to synchronize Future.
The specific process is: after the client sends the request, the < request ID, Future > key value pair is saved in a cache, and then the request thread is hung by Future waiting for the result; when the Netty client receives the response from the server, the response thread takes Future from the cache according to the request ID, and then sets the response result to Future. At this point, the CountDownLatch notification mechanism is used to notify the requesting thread. The request thread gets the response result from Future and then does business processing.
Caching guava using google

Specific code

First introduce dependencies

<!-- guava --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>28.0-jre</version> </dependency> <!-- Netty --> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.39.Final</version> </dependency>

SyncFuture

SyncFuture: Synchronized Future. This is the core, through this tool class to achieve thread waiting.

package com.topinfo.ci.netty.client; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; public class SyncFuture<T> implements Future<T> { // Because the request and response are one-to-one, the CountDownLatch value is initialized to 1. private CountDownLatch latch = new CountDownLatch(1); // Response results that need to respond to thread settings private T response; // Futrue request time, used to calculate whether Future timeout private long beginTime = System.currentTimeMillis(); public SyncFuture() { } @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; } @Override public boolean isCancelled() { return false; } @Override public boolean isDone() { if (response != null) { return true; } return false; } // Get the response result, and do not return until there is a result. @Override public T get() throws InterruptedException { latch.await(); return this.response; } // Get the response result, and return it until the result is available or beyond the specified time. @Override public T get(long timeout, TimeUnit unit) throws InterruptedException { if (latch.await(timeout, unit)) { return this.response; } return null; } // Used to set the response result and do countDown to notify the requesting thread public void setResponse(T response) { this.response = response; latch.countDown(); } public long getBeginTime() { return beginTime; } }

Client Code

NettyClient

NettyClient: Synchronized and asynchronous methods of messages, as follows:

package com.topinfo.ci.netty.client; import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoop; 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 io.netty.util.CharsetUtil; /** *@Description: Netty Client *@Author:Yang pan *@Since:2019 September 26, 8:54:59 p.m. */ @Component public class NettyClient { private static final Logger LOGGER = LoggerFactory.getLogger(NettyClient.class); private EventLoopGroup group = new NioEventLoopGroup(); /** *@Fields DELIMITER : Custom separators, server and client alignment */ public static final String DELIMITER = "@@"; /** * @Fields hostIp : Server ip */ private String hostIp = "192.168.90.96"; /** * @Fields port : Server Port */ private int port= 8888; /** * @Fields socketChannel : passageway */ private SocketChannel socketChannel; /** *@Fields clientHandlerInitilizer : Initialization */ @Autowired private NettyClientHandlerInitilizer clientHandlerInitilizer; /** * @Description: Start the client * @Author:Yang pan * @Since: 2019 September 12, 4:43:21 p.m. */ @SuppressWarnings("unchecked") @PostConstruct public void start() { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) // Specify Channel .channel(NioSocketChannel.class) // Server address .remoteAddress(hostIp, port) // Packing small data packets into larger frames for transmission increases the network load, i.e. TCP delay transmission. .option(ChannelOption.SO_KEEPALIVE, true) // Packing small data packets into larger frames for transmission increases the network load, i.e. TCP delay transmission. .option(ChannelOption.TCP_NODELAY, true) .handler(clientHandlerInitilizer); // Connect ChannelFuture channelFuture = bootstrap.connect(); //Client disconnection reconnection logic channelFuture.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if(future.isSuccess()) { LOGGER.info("Connect Netty Server Success..."); }else { LOGGER.info("Connect Netty Server failed, disconnected and reconnected..."); final EventLoop loop =future.channel().eventLoop(); loop.schedule(new Runnable() { @Override public void run() { LOGGER.info("Connection is being retried..."); start(); } }, 20, TimeUnit.SECONDS); } } }); socketChannel = (SocketChannel) channelFuture.channel(); } /** *@Description: message sending *@Author:Yang pan *@Since: 2019 September 12, 5:08:47 p.m. *@param message */ public void sendMsg(String message) { String msg = message.concat(NettyClient.DELIMITER); ByteBuf byteBuf = Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8); ChannelFuture future = socketChannel.writeAndFlush(byteBuf); future.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if(future.isSuccess()) { System.out.println("===========Send successfully"); }else { System.out.println("------------------fail in send"); } } }); } /** *@Description: Send Synchronized Messages *@Author:Yang pan *@Since: 2019 September 12, 5:08:47 p.m. *@param message */ public String sendSyncMsg(String message, SyncFuture<String> syncFuture) { String result = ""; String msg = message.concat(NettyClient.DELIMITER); ByteBuf byteBuf = Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8); try { ChannelFuture future = socketChannel.writeAndFlush(byteBuf); future.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if(future.isSuccess()) { System.out.println("===========Send successfully"); }else { System.out.println("------------------fail in send"); } } }); // Wait for 8 seconds. result = syncFuture.get(8, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } return result; } public String getHostIp() { return hostIp; } public void setHostIp(String hostIp) { this.hostIp = hostIp; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } }

NettyClientHandlerInitilizer

NettyClientHandler Initilizer: Initialization

package com.topinfo.ci.netty.client; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.Delimiters; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.util.CharsetUtil; @Component public class NettyClientHandlerInitilizer extends ChannelInitializer<Channel> { /** *@Fields clientHandler : Client Processing */ @Autowired private NettyClientHandler clientHandler; @Override protected void initChannel(Channel ch) throws Exception { // Get the corresponding pipeline through socket Channel ChannelPipeline channelPipeline = ch.pipeline(); /* * channelPipeline There will be many handler classes (also known as interceptor classes) * Once you get pipeline, you can add handler directly to. addLast */ ByteBuf buf = Unpooled.copiedBuffer(NettyClient.DELIMITER.getBytes()); channelPipeline.addLast("framer", new DelimiterBasedFrameDecoder(1024*1024*2, buf)); //channelPipeline.addLast("decoder",new StringDecoder(CharsetUtil.UTF_8)); //channelPipeline.addLast("encoder",new StringEncoder(CharsetUtil.UTF_8)); channelPipeline.addLast(clientHandler); } }

NettyClientHandler

Netty ClientHandler: Client Processing Class for Receiving

package com.topinfo.ci.netty.client; import java.util.concurrent.TimeUnit; import io.netty.buffer.ByteBuf; import io.netty.util.CharsetUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.topinfo.ci.netty.service.NettyClientService; import com.topinfo.ci.netty.utils.ExceptionUtil; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.EventLoop; import io.netty.channel.SimpleChannelInboundHandler; @Component @ChannelHandler.Sharable // Annotating a channel handler can be safely shared by multiple channels public class NettyClientHandler extends SimpleChannelInboundHandler<ByteBuf> { private static final Logger LOGGER = LoggerFactory.getLogger(NettyClientHandler.class); @Autowired private NettyClientService service; @Autowired private NettyClient nettyClient; /** * @Description: When the server sends a message to the client, it triggers the method to receive the message. * @Author:Yang pan * @Since: 2019 September 12, 5:03:31 p.m. * @param ctx * @param byteBuf * @throws Exception */ @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception { String msg = byteBuf.toString(CharsetUtil.UTF_8); LOGGER.info("The client receives a message:{}", msg); //service.ackMsg(msg); service.ackSyncMsg(msg); // Synchronized message return } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { LOGGER.info("Successful request connection..."); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { LOGGER.info("Connection disconnected..."); // Disconnecting and reconnecting during use final EventLoop eventLoop = ctx.channel().eventLoop(); eventLoop.schedule(new Runnable() { @Override public void run() { // Reconnect nettyClient.start(); } }, 20, TimeUnit.SECONDS); super.channelInactive(ctx); } /** * Handling exceptions, typically putting Andler, which implements exception handling logic, at the end of Channel Pipeline * This ensures that all inbound messages are always processed, regardless of where they occur. Here's just a simple way to close Channel and print exception messages * * @param ctx * @param cause * @throws Exception */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); // Output to log ExceptionUtil.getStackTrace(cause); Channel channel = ctx.channel(); if (channel.isActive()) { ctx.close(); } } }

NettyClientServiceImpl

NettyClientService Impl: The client encapsulates the implementation class and its interface is not pasted out.

package com.topinfo.ci.netty.service.impl; import com.topinfo.ci.netty.bean.RealDataInfo; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.stereotype.Service; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.cache.RemovalListener; import com.google.common.cache.RemovalNotification; import com.topinfo.ci.netty.bean.Message; import com.topinfo.ci.netty.client.NettyClient; import com.topinfo.ci.netty.client.SyncFuture; import com.topinfo.ci.netty.service.NettyClientService; import com.topinfo.ci.netty.utils.AESUtil; @Service public class NettyClientServiceImpl implements NettyClientService { private static final Logger LOGGER = LoggerFactory.getLogger(NettyClientServiceImpl.class); //The cache interface here is LoadingCache, which automatically loads the cache when the cache item does not exist. private static LoadingCache<String, SyncFuture> futureCache = CacheBuilder.newBuilder() //Setting the initial capacity of the cache container to 10 .initialCapacity(100) // maximumSize Sets Cache Size .maximumSize(10000) //Set the concurrency level to 20, which refers to the number of threads that can write caches at the same time. .concurrencyLevel(20) // expireAfterWrite expires 8 seconds after the write cache is set .expireAfterWrite(8, TimeUnit.SECONDS) //Setting Cache Removal Notification .removalListener(new RemovalListener<Object, Object>() { @Override public void onRemoval(RemovalNotification<Object, Object> notification) { LOGGER.debug("LoadingCache: {} was removed, cause is {}",notification.getKey(), notification.getCause()); } }) //CacheLoader can be specified in the build method, and caching can be automatically loaded through CacheLoader when caching does not exist. .build(new CacheLoader<String, SyncFuture>() { @Override public SyncFuture load(String key) throws Exception { // When the cache to get the key does not exist, there is no need to add it automatically. return null; } }); @Autowired private NettyClient nettyClient; @Autowired private CacheManager cacheManager; @Override public boolean sendMsg(String text, String dataId, String serviceId) { LOGGER.info("Contents sent:{}", text); //TODO //nettyClient.sendMsg(json); return true; } @Override public String sendSyncMsg(String text, String dataId, String serviceId) { SyncFuture<String> syncFuture = new SyncFuture<String>(); // Put it in the cache futureCache.put(dataId, syncFuture); // Encapsulated data JSONObject object = new JSONObject(); object.put("dataId", dataId); object.put("text", text); // Send Synchronized Messages String result = nettyClient.sendSyncMsg(object.toJSONString(), syncFuture); return result; } @Override public void ackSyncMsg(String msg) { LOGGER.info("ACK Confirmation information: {}",msg); JSONObject object =JSON.parseObject(msg); String dataId = object.getString("dataId"); // Getting data from the cache SyncFuture<String> syncFuture = futureCache.getIfPresent(dataId); // If it is not null, the notification returns if(syncFuture != null) { syncFuture.setResponse(msg); } } }

TestController

TestController: Test TestController.

package com.topinfo.ci.netty.controller; import com.alibaba.fastjson.JSON; import com.topinfo.ci.netty.bean.CmwSensoralert; import com.topinfo.ci.netty.bean.Equip; import com.topinfo.ci.netty.bean.JsonResult; import com.topinfo.ci.netty.bean.RealDataInfo; import com.topinfo.ci.netty.mapper.SensorAlertMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.topinfo.ci.netty.service.NettyClientService; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @RestController @RequestMapping("/test") public class TestController { @Autowired private NettyClientService clientService; @Autowired private SensorAlertMapper sensorAlertMapper; @RequestMapping("/sendSyncMsg") public String sendSyncMsg(String dataId, String text) { String serviceId = "mmmm"; String result = clientService.sendSyncMsg(text, dataId, serviceId); return "result:"+result ; } }

The test perfectly achieves the effect of "request-response".

Server-side code

NettyServer

package com.topinfo.ju.ccon.netty.server; import java.net.InetSocketAddress; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; 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; @Component public class NettyServer { private static final Logger LOGGER = LoggerFactory.getLogger(NettyServer.class); /** *@Fields DELIMITER : Custom separators, server and client alignment */ public static final String DELIMITER = "@@"; /** * @Fields boss : boss Thread groups are used to handle connection work. By default, they are twice the number of CPU s in the system. They can also be specified according to the actual situation. */ private EventLoopGroup boss = new NioEventLoopGroup(); /** * @Fields work : work Thread groups are used for data processing. By default, they are twice the number of CPU s in the system. They can also be specified according to the actual situation. */ private EventLoopGroup work = new NioEventLoopGroup(); /** * @Fields port : Monitor port */ private Integer port = 8888; @Autowired private NettyServerHandlerInitializer handlerInitializer; /** * @throws InterruptedException * @Description: Start Netty Server * @Author:Yang pan * @Since: 2019 September 12, 4:21:35 p.m. */ @PostConstruct public void start() throws InterruptedException { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(boss, work) // Specify Channel .channel(NioServerSocketChannel.class) // Set the socket address using the specified port .localAddress(new InetSocketAddress(port)) // The number of queues that the server can connect corresponds to the backlog parameter in the listen function of TCP/IP protocol .option(ChannelOption.SO_BACKLOG, 1024) // Setting up a long TCP connection, TCP will automatically send an active probe data message if there is no data communication within two hours. .childOption(ChannelOption.SO_KEEPALIVE, true) // Packing small data packets into larger frames for transmission increases the network load, i.e. TCP delay transmission. .childOption(ChannelOption.TCP_NODELAY, true) .childHandler(handlerInitializer); ChannelFuture future = bootstrap.bind().sync(); if (future.isSuccess()) { LOGGER.info("start-up Netty Server..."); } } @PreDestroy public void destory() throws InterruptedException { boss.shutdownGracefully().sync(); work.shutdownGracefully().sync(); LOGGER.info("Close Netty..."); } }

NettyServerHandlerInitializer

package com.topinfo.ju.ccon.netty.server; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.util.CharsetUtil; @Component public class NettyServerHandlerInitializer extends ChannelInitializer<Channel> { /** *@Fields serverHandler : Service processing */ @Autowired private NettyServerHandler serverHandler; @Override protected void initChannel(Channel ch) throws Exception { // Get the corresponding pipeline through socket Channel ChannelPipeline channelPipeline = ch.pipeline(); /* * channelPipeline There will be many handler classes (also known as interceptor classes) * Once you get pipeline, you can add handler directly to. addLast */ ByteBuf buf = Unpooled.copiedBuffer(NettyServer.DELIMITER.getBytes()); channelPipeline.addLast("framer", new DelimiterBasedFrameDecoder(1024*1024*2, buf)); //channelPipeline.addLast("decoder",new StringDecoder(CharsetUtil.UTF_8)); //channelPipeline.addLast("encoder",new StringEncoder(CharsetUtil.UTF_8)); // Custom decoder, paste/unpack/unpack channelPipeline.addLast(serverHandler); } }

NettyServerHandler

package com.topinfo.ju.ccon.netty.server; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.alibaba.fastjson.JSONObject; import com.topinfo.ju.ccon.netty.bean.Message; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.util.CharsetUtil; @Component @ChannelHandler.Sharable //Annotating a channel handler can be safely shared by multiple channels public class NettyServerHandler extends SimpleChannelInboundHandler<ByteBuf> { private static final Logger LOGGER = LoggerFactory.getLogger(NettyServerHandler.class); public static AtomicInteger nConnection = new AtomicInteger(0); @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { String txt = msg.toString(CharsetUtil.UTF_8); LOGGER.info("Receive a message from the client:{}", txt); ackMessage(ctx, txt); } /** *@Description: confirmation message *@Author:Yang pan *@Since: 2019 September 17, 11:22:27 a.m. *@param ctx *@param message */ public void ackMessage(ChannelHandlerContext ctx, String message) { //custom delimiter String msg = message+NettyServer.DELIMITER; ByteBuf byteBuf = Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8); //Response Client ctx.writeAndFlush(byteBuf); } /** *@Description: Each time a new connection comes, add one to the number of connections *@Author:Yang pan *@Since: 2019 September 16, 3:04:42 p.m. *@param ctx *@throws Exception */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { nConnection.incrementAndGet(); LOGGER.info("Request connection...{},Current number of connections: : {}", ctx.channel().id(),nConnection.get()); } /** *@Description: Each time you disconnect from the server, reduce the number of connections by one *@Author:Yang pan *@Since: 2019 September 16, 3:06:10 p.m. *@param ctx *@throws Exception */ @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { nConnection.decrementAndGet(); LOGGER.info("Disconnect...Current number of connections: : {}", nConnection.get()); } /** *@Description: Callback when connection is abnormal *@Author:Yang pan *@Since: 2019 September 16, 3:06:55 p.m. *@param ctx *@param cause *@throws Exception */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { super.exceptionCaught(ctx, cause); // Print error log cause.printStackTrace(); Channel channel = ctx.channel(); if(channel.isActive()){ ctx.close(); } } }

This is the core code, I hope to help you.

26 September 2019, 10:15 | Views: 7722

Add new comment

For adding a comment, please log in
or create account

0 comments