夜光序言:
找伴侣,要看的永远是心,一颗善良的心,一颗上进的心,一颗充满责任感的心。远远比金钱更加重要。陪伴优质股成长,我一向认为是最完美的。
不在于未来能够获得多少,在于享受优质股前进的过程。
上图男主挺不错的,虽难人生比较坎坷,最后也收获了幸福
正文:这一篇聊一聊通讯~~,涉及客户端和服务端的编写,主要服务端是虚拟测试用,客户端则是实际用,实际与AGV对接时候,可以将服务端代码注释掉~~么么哒
package nettybak.netty.client.plc;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nettybak.entity.PlcCmd;
import nettybak.netty.AgvConnectionUtils;
import nettybak.netty.AgvConstants;
import nettybak.util.HexUtils;
import nettybak.util.PlcCmdFactory;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* @Title: PlcClient.java
* @Package
* @Description: plc通信客户端
* @author Genius Team
* @date
*/
public class PlcClient {
private Logger logger=LoggerFactory.getLogger(getClass());
private NioEventLoopGroup workGroup = new NioEventLoopGroup();
private Channel channel;
private Bootstrap bootstrap;
private final String host;
private final int port;
private boolean sync=false;//是否同步等待连接创建完成,默认异步
public PlcClient(){
this(AgvConstants.PLC_DEFAULT_HOST,AgvConstants.PLC_DEFAULT_PORT,false);
}
public PlcClient(String host, int port,boolean sync){
this.host = host;
this.port = port;
this.sync=sync;
}
public void start() {
try {
bootstrap = new Bootstrap();
bootstrap
.group(workGroup)
.channel(NioSocketChannel.class)
.handler(AgvConnectionUtils.plcClientInitializer);
if(sync) {
ChannelFuture future=bootstrap.connect(host, port).sync();
channel=future.channel();
addReconnection(future);
}else {
doConnect();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected void doConnect() {
if (channel != null && channel.isActive()) {
logger.debug("夜光:当前链接有活动的channel,不执行重连逻辑");
return ;
}
ChannelFuture future = bootstrap.connect(host, port);
addReconnection(future);
}
public void addReconnection(ChannelFuture future) {
future.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture futureListener) throws Exception {
if (futureListener.isSuccess()) {
channel = futureListener.channel();
} else {
logger.debug("链接失败"+AgvConstants.DEFAULT_REDIRECT_SECOND+"s后执行重连!");
futureListener.channel().eventLoop().schedule(new Runnable() {
@Override
public void run() {
doConnect();
}
}, AgvConstants.DEFAULT_REDIRECT_SECOND, TimeUnit.SECONDS);
}
}
});
}
/**
* 本地非集成环境测试方法
* 夜光
* @throws Exception
*/
public void test() throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String readLine = in.readLine();
PlcCmd msg=null;
switch(readLine) {
case "1" : msg = PlcCmdFactory.getUp(0); break;
case "2" : msg = PlcCmdFactory.getDown(0); break;
case "0" : msg = PlcCmdFactory.getStopJack(0); break;
case "55" : msg = PlcCmdFactory.getInquiryStatus(); break;
case "11" : msg = PlcCmdFactory.getForward(); break;
case "12" : msg = PlcCmdFactory.getBackWards(); break;
case "13" : msg = PlcCmdFactory.getToLeft(); break;
case "14" : msg = PlcCmdFactory.getToRight(); break;
case "15" : msg = PlcCmdFactory.getLevorotation(); break;
case "16" : msg = PlcCmdFactory.getDextrorotation(); break;
case "00" : msg = PlcCmdFactory.getStopRun(); break;
}
if("loop".equals(readLine)) {
while(true) {
msg=PlcCmdFactory.getUp(0);
logger.debug("我是客户端,我准备发送的消息为-升顶:" + HexUtils.toHexString(msg.toByteArray()) );
channel.writeAndFlush(msg);
Thread.sleep(1000);
}
}
if("loopd".equals(readLine)) {
msg=PlcCmdFactory.getStopJack(0);
logger.debug("我是客户端,我准备发送的消息为-停止顶升功能:" + HexUtils.toHexString(msg.toByteArray()) );
channel.writeAndFlush(msg);
while(true) {
msg=PlcCmdFactory.getDown(0);
logger.debug("我是客户端,我准备发送的消息为-落顶:" + HexUtils.toHexString(msg.toByteArray()) );
channel.writeAndFlush(msg);
Thread.sleep(1000);
}
}
if("loopr".equals(readLine)) {
while(true) {
msg=PlcCmdFactory.getInquiryStatus();
logger.debug("我是客户端,我准备发送的消息为-问询:" + HexUtils.toHexString(msg.toByteArray()) );
channel.writeAndFlush(msg);
Thread.sleep(1000);
}
}
logger.debug("我是客户端,我准备发送的消息为:" + HexUtils.toHexString(msg.toByteArray()) );
channel.writeAndFlush(msg);
}
}
public void writeAndFlush(PlcCmd msg) {
logger.debug("我是客户端,我准备发送的消息为:" + HexUtils.toHexString(msg.toByteArray()) );
channel.writeAndFlush(msg);
}
public static void main(String[] args) throws Exception {
PlcClient client = new PlcClient(AgvConstants.PLC_DEFAULT_HOST,AgvConstants.PLC_DEFAULT_PORT,true);
client.start();
client.test();
}
}
package nettybak.netty.client.plc;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nettybak.entity.PlcResult;
import nettybak.util.CRC16;
import nettybak.util.HexUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.util.ByteProcessor.IndexOfProcessor;
/**
* @Package
* @Description: plc通信解码器-解码Plc反馈的信息
* @author Genius Team
* @date
*/
public class PlcClientDecoder extends ByteToMessageDecoder {
private static final Logger logger=LoggerFactory.getLogger(PlcClientDecoder.class);
/**
* 解码PLC返回的数据.
* <p>该方法执行结束,如果没有新的可读数据,即ridx==widx,则会释放该字节缓冲区资源
* @param ctx
* @param in
* @param out
* @throws Exception
* @see io.netty.handler.codec.ByteToMessageDecoder#decode(io.netty.channel.ChannelHandlerContext, io.netty.buffer.ByteBuf, List)
*/
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
//-----TEST START
in.markReaderIndex();
int len=in.readableBytes();
byte[] read=new byte[len];
in.readBytes(read);
logger.trace("decode解码PLC反馈的数据,接受到的数据字节长度为: {} ,值为:{}",len,HexUtils.toHexString(read));
in.resetReaderIndex();
//-----TEST END
//夜光:按照 站号和功能码 组合做头部分帧
int numIdx=in.forEachByte(new IndexOfProcessor((byte) 0x01));//在当前可读字节中寻找"站号"第一次出现的位置[数据过滤,认为站号之前的未读数据为垃圾数据]
if(numIdx==-1) {
return;//没有找到则return继续等待新内容写入
}
in.readerIndex(numIdx);//将当前读索引设置为站号所在位置
if (in.readableBytes() < PlcResult.BASE_LEN) {
return;//以当前站号为起始,可读数据小于头部固定长度(站号+功能码+字节总数),则return继续等待写入
}
byte typeCode=in.getByte(in.readerIndex()+1);//以不推进readerIndex的方式获取到当前指令对应的功能码.(功能码固定位于指令开头的索引+1处)
//如果在对应位置读取不到正确的功能码,则认为当前位置不是正确的头部位置,以推进readIndex的方式,丢弃该位置和该位置以前的数据
if(typeCode!=0x03) {
in.readerIndex(in.readerIndex()+1+1);//in.readerIndex()+1只包括该位置以前的数据,所以要再加一次1,把该位置的也丢弃
return;
}
byte dataLength=in.getByte(in.readerIndex()+2);//以不推进readerIndex的方式获取到当前指令对应的字节总数.(字节总数固定位于指令开头的索引+2处)
if(in.readableBytes()<PlcResult.BASE_LEN+dataLength+PlcResult.CHECK_LEN) {
return;//以当前站号为起始,可读数据不小于头部固定长度(站号+功能码+字节总数),但可读数据小于当前指令的字节总数,则return继续等待写入
}
//所有条件都符合,尝试读取封装数据
PlcResult result= packPlcResult(in);
//如果result不为空,则调用out.add(result); 数据会进入信道的下一个处理器,
if(result!=null) {
out.add(result);
}
}
/**
* 将plc反馈的字节数据封装为对应的实体对象
* <p>如果封装完成后的对象,校验和与计算出的校验和不匹配,则认为数据在传输中出现错误,则返回null
* @param in
* @return
*/
public PlcResult packPlcResult(ByteBuf in) {
PlcResult plcResult=new PlcResult();
plcResult.setCode(in.readByte());
plcResult.setTypeCode(in.readByte());
plcResult.setDataLength(in.readByte());
byte[] datas=new byte[plcResult.getDataLength()];
in.readBytes(datas);
plcResult.setData(datas);
plcResult.setChecksum(in.readShort());
short ck=(short)CRC16.calcCrc16(plcResult.toNoCheckByteArray());
if(ck!=0&&ck==plcResult.getChecksum()) {
return plcResult;
}else {
return null;
}
}
}
package nettybak.netty.client.plc;
import nettybak.entity.PlcCmd;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
/**
* @Package
* @Description: plc通信编码器-编码发送给PLC的指令
* @author Genius Team
* @date
*/
public class PlcClientEncoder extends MessageToByteEncoder<PlcCmd> {
@Override
protected void encode(ChannelHandlerContext ctx, PlcCmd cmd, ByteBuf out) throws Exception {
byte[] bytes=cmd.toByteArray();
out.writeBytes(bytes);
}
}
package nettybak.netty.client.plc;
import nettybak.netty.AgvConstants;
import nettybak.netty.client.BaseChannelInitializer;
import nettybak.netty.client.BaseClientHandler;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
/**
* @Title: PlcClientInitializer.java
* @Package
* @Description: plc通信处理器初始化
* @author Genius Team
* @date
*/
public class PlcClientInitializer extends BaseChannelInitializer<SocketChannel> {
public PlcClientInitializer(BaseClientHandler<?> handler) {
super(handler);
}
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// pipeline.addLast(new IdleStateHandler(AgvConstants.DEFAULT_HEARTBEAT_SECOND, 0, 0));//心跳
pipeline.addLast("decoder", new PlcClientDecoder());
pipeline.addLast("encoder", new PlcClientEncoder());
// pipeline.addLast("handler", this.getBusinessHandler());
}
}
一般客户端和服务端都是差不多几个Class串在一起嗯~~
package nettybak.netty.client.plc;
import java.net.InetSocketAddress;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nettybak.entity.AgvEntity;
import nettybak.entity.AgvSession;
import nettybak.entity.PlcCmd;
import nettybak.entity.PlcCommand;
import nettybak.entity.PlcResult;
import nettybak.netty.AgvUtils;
import nettybak.util.HexUtils;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
/**
* @Title: PlcDefaultClientHandler.java
* @Package
* @Description: plc通信默认业务处理器
* @author Genius Team
* @date
*/
@Sharable
public class PlcDefaultClientHandler extends PlcBaseClientHandler {
private Logger logger=LoggerFactory.getLogger(getClass());
/**
* 处理下发plc指令
* @param ctx
* @param s
// * @see
*/
@Override
protected void businessHandler(ChannelHandlerContext ctx, PlcResult s) {
//接收到消息后触发onReadMsg事件
//// AgvEntity entity=new AgvEntity();
// entity.setPlcResult(s);
// onReadMsg(entity);
Channel channel=ctx.channel();
//目前channelKey为远程主机的ip,根据具体情况,channelKey可以在消息体中携带(除外,不支持)
InetSocketAddress insocket = (InetSocketAddress) channel.remoteAddress();
String remoteIP = insocket.getAddress().getHostAddress();
//logger.debug("我是客户端,当前链接的IP为:"+remoteIP);
//System.out.println("SYSOUT-我是客户端,我接收到的消息为:"+s);
logger.debug("我是plc客户端,当前链接的plc-IP为:{}我接收到的消息为:{}",remoteIP,HexUtils.toHexString(s.toByteArray()));
}
/**
*
*
* 测试方法-链接建立成功后发送一条指令
* @param ctx
* @throws Exception
// * @see netty.client.plc.PlcBaseClientHandler#channelActive(io.netty.channel.ChannelHandlerContext)
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// TODO Auto-generated method stub
super.channelActive(ctx);
PlcCmd cmd=new PlcCmd();
//ctx.writeAndFlush(cmd);
}
}
package nettybak.netty.client.plc;
import java.net.InetSocketAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nettybak.entity.PlcResult;
import nettybak.netty.AgvConnectionUtils;
import nettybak.netty.client.BaseClientHandler;
import nettybak.util.Q600CmdFactory;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.timeout.IdleStateEvent;
/**
* @Title: PlcBaseClientHandler.java
* @Package
* @Description: plc通信业务处理器基类
* @author Genius Team
* @date
*/
public abstract class PlcBaseClientHandler extends BaseClientHandler<PlcResult> {
private Logger logger=LoggerFactory.getLogger(getClass());
/**
* 嗯唔:需要实现的处理业务逻辑的方法
*/
protected abstract void businessHandler(ChannelHandlerContext ctx, PlcResult s);
@Override
protected void channelRead0(ChannelHandlerContext ctx, PlcResult s) throws Exception {
businessHandler(ctx, s);
}
/**
* 心跳处理
* @param ctx
* @param evt
* @throws Exception
* @see io.netty.channel.ChannelInboundHandlerAdapter#userEventTriggered(io.netty.channel.ChannelHandlerContext, Object)
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
// IdleStateHandler 所产生的 IdleStateEvent 的处理逻辑.
if (evt instanceof IdleStateEvent) {
IdleStateEvent e = (IdleStateEvent) evt;
switch (e.state()) {
case READER_IDLE:
handleReaderIdle(ctx);
break;
case WRITER_IDLE:
handleWriterIdle(ctx);
break;
case ALL_IDLE:
handleAllIdle(ctx);
break;
default:
break;
}
}
}
/**
* 信道被激活(每当成功建立链接时,信道会被激活)
* @param ctx
* @throws Exception
* @see io.netty.channel.ChannelInboundHandlerAdapter#channelActive(io.netty.channel.ChannelHandlerContext)
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
logger.debug("---" + ctx.channel().remoteAddress() + " 信道被激活---");
}
/**
* 断开自动重连
* @param ctx
* @throws Exception
* @see io.netty.channel.ChannelInboundHandlerAdapter#channelInactive(io.netty.channel.ChannelHandlerContext)
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
logger.debug("---" + ctx.channel().remoteAddress() + " is inactive---连接中断开,执行重连逻辑");
InetSocketAddress addr=(InetSocketAddress)ctx.channel().remoteAddress();
String ip=addr.getAddress().getHostAddress();
int port=addr.getPort();
AgvConnectionUtils.connectPlc(ip, port, false);
}
/**处理指定的时间间隔内没有读到数据
* @param ctx
*/
protected void handleReaderIdle(ChannelHandlerContext ctx) {
logger.debug("---READER_IDLE---指定的时间间隔内没有读到agv返回的数据,准备发送 问询坐标 指令");
//到了指定的间隔时间,就问询一次坐标
ctx.channel().writeAndFlush(Q600CmdFactory.Q600_GET_POSE);
logger.debug("---READER_IDLE--发送问询坐标指令完成");
}
/**处理指定的时间间隔内没有写入数据
* @param ctx
*/
protected void handleWriterIdle(ChannelHandlerContext ctx) {
logger.debug("---WRITER_IDLE---指定的时间间隔内没有写入数据");
}
/**处理指定的时间间隔内没有读/写入数据
* @param ctx
*/
protected void handleAllIdle(ChannelHandlerContext ctx) {
logger.debug("---ALL_IDLE---指定的时间间隔内没有读/写入数据");
}
}
总结:实际测试只要编写上面客户端就可以了嗯,主要就是与PLC之间的编解码问题