1. Selection of overall architecture
First of all, according to the requirements, I use the springboot+netty architecture, using a serial port to network port conversion module. Why use it like this? The Linux system is used during deployment. It is troublesome to install the driver under Linux, so the network port can save a lot of server configuration time. Why use netty? Many people who have used netty know that netty is an asynchronous non blocking framework. You can check its specific advantages. It is a very powerful framework. The conversion module uses someone's module. It's OK to buy it on Taobao. It has also used modules from other manufacturers, such as yibaite. It's not easy to use. Someone's module is quite mature.
2. What is modbus
When it comes to 485 communication, we will think of Modbus protocol. Modbus protocol is a general language applied to electronic controller. There is no more explanation here. Here we use the Modbus RTU protocol. A separate article will be written later.
3. Code parsing
After we have a certain understanding of this architecture, we can start coding. First, we need to define the server and client. If our program is set as the server, the module should be set as the client. The final effect is the same. I prefer to set the program as the server.
3.1 server monitoring
The listener listens for client connections
import com.nari.sea.serialport.config.ServerChannelInitializer; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; import java.net.InetSocketAddress; @Component public class NettyServer { private static final Logger logger = Logger.getLogger(NettyServer.class); public void start(InetSocketAddress address) { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(4); try { ServerBootstrap bootstrap = new ServerBootstrap() .group(bossGroup,workerGroup) .channel(NioServerSocketChannel.class) .localAddress(address) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ServerChannelInitializer()) .option(ChannelOption.SO_BACKLOG, 128) //Open long connection .childOption(ChannelOption.SO_KEEPALIVE, true); // Bind the port and start receiving incoming connections ChannelFuture future = bootstrap.bind(address).sync(); logger.info("Server start listen at " + address.getPort()); future.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
3.2 netty heartbeat mechanism
All of them can be configured according to their needs. netty determines by default that the client is offline for two hours. Through the following code, we can set it to seconds and minutes. The following four parameters are read, write, full and time types respectively.
channel.pipeline().addLast(new IdleStateHandler(1, 0, 0, TimeUnit.MINUTES));
import io.netty.channel.ChannelInitializer; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.timeout.IdleStateHandler; import io.netty.util.CharsetUtil; import java.util.concurrent.TimeUnit; public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel channel) throws Exception { channel.pipeline().addLast("decoder",new StringDecoder(CharsetUtil.ISO_8859_1)); channel.pipeline().addLast("encoder",new StringEncoder(CharsetUtil.ISO_8859_1)); //Set n minutes to judge offline channel.pipeline().addLast(new IdleStateHandler(1, 0, 0, TimeUnit.MINUTES)); //Sticking length control channel.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4)); channel.pipeline().addLast(new ServerHandler()); } }
3.3 specific implementation
import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.DefaultEventLoopGroup; import io.netty.handler.timeout.IdleState; import io.netty.handler.timeout.IdleStateEvent; import io.netty.util.CharsetUtil; import java.util.List; public class ServerHandler extends ChannelInboundHandlerAdapter { //Timeout connection private int lossConnectCount = 0; @Override public void channelActive(ChannelHandlerContext ctx) { System.out.println("channelActive----->"); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof IdleStateEvent){ IdleStateEvent event = (IdleStateEvent)evt; //Judge only read if (event.state()== IdleState.READER_IDLE){ lossConnectCount++; if (lossConnectCount>5){ System.out.println("Turn off inactive channels!"); ctx.channel().close(); } System.out.println("already"+lossConnectCount+"I haven't received a message from the client for minutes!"); } }else { super.userEventTriggered(ctx,evt); } } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception{ //Client exit connection } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { lossConnectCount = 0; System.out.println("server channelRead......"); String m= (String) msg; //Judgment communication ByteBuf buf = Unpooled.copiedBuffer(m, CharsetUtil.ISO_8859_1); byte [] bytes = new byte[buf.readableBytes()]; buf.readBytes(bytes);//Copy contents to byte array bytes String returnData = bytes2HexString(bytes); //Convert byte array to hexadecimal string System.out.println(ctx.channel().remoteAddress()+"----->Server :"+ returnData); if(returnData != null && !"".equals(returnData)){ if(HexUtil.checkData(returnData)){ //Specific logic code } } //The information of the client is directly returned and written to ctx //Refresh cache } private String bytes2HexString(byte[] b) { StringBuffer result = new StringBuffer(); String hex; for (int i = 0; i < b.length; i++) { hex = Integer.toHexString(b[i] & 0xFF); if (hex.length() == 1) { hex = '0' + hex; } result.append(hex.toUpperCase()); } return result.toString(); } private String hexString2String(String hex, String charset) { byte[] bs = new byte[hex.length()/2]; for(int i=0; i<bs.length; i++) { bs[i] = (byte)(0xff&Integer.parseInt(hex.substring(i*2,i*2+2),16)); } try{ hex = new String(bs, charset); }catch(Exception e) { e.printStackTrace(); } return hex; } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
3.4 how to introduce service into netty
In the logic implementation code, we inevitably need to introduce some service s. At this time, we need to do so in the implementation code
@Component public class SerialPortDealData { @Autowired private ModeManService modeManService; //Declaration object private static SerialPortDealData serialPortDealData; // initialization @PostConstruct public void init() { serialPortDealData = this; serialPortDealData.modeManService = this.modeManService; } //Logic code }
3.5 how to start netty
There are many ways to start netty in spring boot, one of which is introduced here
import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import java.net.InetSocketAddress; @EnableScheduling @SpringBootApplication @ServletComponentScan//Prevent @ WebListener from becoming invalid @MapperScan(basePackages ={""}) @ComponentScan(basePackages = {""}) @EnableAsync//Note here that this annotation enables thread pooling public class SeaApplication extends SpringBootServletInitializer implements CommandLineRunner{ @Value("${netty.port}") private int port; @Value("${netty.url}") private String url; @Autowired private NettyServer server; public static void main(String[] args) { SpringApplication.run(SeaApplication.class, args); } @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(SeaApplication.class); } @Override public void run(String... args) throws Exception { InetSocketAddress address = new InetSocketAddress(url,port); System.out.println("run .... . ... "+url); server.start(address); } }
Application.yml configuration file
netty: port: 5001 url: 127.0.0.1
4. Thanks
So far, a method of realizing 485 communication has been introduced. Thank you for reading. Please guide me if there are any deficiencies. Finally, let's talk about some simulation tools, modbus poll - corresponding modbus slave, modbus scan, network debugging tool NetAssist.exe, serial port debugging tool, etc