最近做的这个项目,需要和服务端进行tcp通信。大概的需求是这样,服务端是物理硬件,由c++代码控制,后台和机器通过tcp进行通信。当前台输入数据给后台之后,后台作为客户端将前台的数据转换成byte数组给服务端发送数据,返回给后台,后台通过websocket将数据不停的发送到前台。下面记录一下后台作为客户端发送给机器的部分。
netty的客户端
package cn.zxw.netty.work;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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.handler.timeout.IdleStateHandler;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @author zxw
* @version 1.0
* @description 连接的客户端
* @data: 2020/3/6 21:33
*/
@Component
public class SayOnClient implements InitializingBean {
private EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
@Value("${netty.host}")
private String host;
@Value("${netty.port}")
private int port;
@Value("${netty.readTime}")
private int readTime;
@Value("${netty.writeTime}")
private int writeTime;
@Value("${netty.allTime}")
private int allTime;
public static Channel channel = null;
@Override
public void afterPropertiesSet() {
createBootStrap(new Bootstrap(), eventLoopGroup);
}
void createBootStrap(Bootstrap bootstrap, EventLoopGroup eventLoopGroup) {
try {
if (bootstrap != null) {
//定义handler 因为在内部类里面无法传入this对象定义到外面传递过去
final SayOnHandler sayOnHandler = new SayOnHandler(this);
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new IdleStateHandler(readTime, writeTime, allTime))
//添加自定义handler
.addLast(sayOnHandler)
//添加解码器
.addLast("sayOnDecoder", new SayOnDecoder());
}
});
ChannelFuture channelFuture = bootstrap.connect(host, port);
channelFuture.addListener(new SayOnListener(this));
//获得channel对象赋值给静态变量
channel = channelFuture.channel();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
自定义的handler
package cn.zxw.netty.work;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import org.springframework.stereotype.Component;
import java.nio.channels.SocketChannel;
import java.util.concurrent.TimeUnit;
/**
* @author zxw
* @version 1.0
* @description 自定义handler 的区别就是SimpleChannelInboundHandler
* 在接收到数据后会自动release掉数据占用的Bytebuffer资源(自动调用Bytebuffer.release())。
* 而为何服务器端不能用呢,因为我们想让服务器把客户端请求的数据发送回去,
* 而服务器端有可能在channelRead方法返回前还没有写完数据,因此不能让它自动release
* @data: 2020/3/7 20:21
*/
@Component
@ChannelHandler.Sharable
public class SayOnHandler extends SimpleChannelInboundHandler<SocketChannel> {
private SayOnClient sayOnClient;
public SayOnHandler(SayOnClient sayOnClient) {
this.sayOnClient = sayOnClient;
}
/**
* 服务端断开连接会触发,断开后客户端会尝试重连操作
*
* @param ctx
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) {
try {
final EventLoop eventLoop = ctx.channel().eventLoop();
//获得eventLoop对象后进行任务调度,执行客户端再次连接方法
eventLoop.schedule(() -> {
sayOnClient.createBootStrap(new Bootstrap(), eventLoop);
}, 10L, TimeUnit.SECONDS);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, SocketChannel socketChannel) throws Exception {
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
switch (idleStateEvent.state()) {
case WRITER_IDLE:
System.out.println("发送心跳----写数据");
ctx.channel().writeAndFlush("111");
break;
case READER_IDLE:
System.out.println("读超时");
// ctx.channel().close();
break;
default:
ctx.channel().close();
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("发生异常:");
cause.printStackTrace();
ctx.channel().close();
}
}
自定义的listener
package cn.zxw.netty.work;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.EventLoop;
import java.util.concurrent.TimeUnit;
/**
* @author zxw
* @version 1.0
* @description 监听器
* @data: 2020/3/7 20:33
*/
public class SayOnListener implements ChannelFutureListener {
private SayOnClient sayOnClient;
public SayOnListener(SayOnClient sayOnClient){
this.sayOnClient = sayOnClient;
}
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (!channelFuture.isSuccess()){
final EventLoop eventLoop = channelFuture.channel().eventLoop();
//如果连接失败,进行重连的任务调度
eventLoop.schedule(()->{
sayOnClient.createBootStrap(new Bootstrap(),eventLoop);
},10L, TimeUnit.SECONDS);
System.out.println("开始重连!!");
} else {
System.out.println("连接成功!!");
}
}
}
Java基本类型转byte数组
package cn.zxw.netty.work;
/**
* @author zxw
* @version 1.0
* @description 数据类型转换工具类
* @data: 2020/3/26 11:17
*/
public class DataConverterUtil {
/**
* char到字节数组的转换.
*/
public static byte[] charToByte(char c) {
byte[] b = new byte[2];
b[0] = (byte) ((c & 0xFF00) >> 8);
b[1] = (byte) (c & 0xFF);
return b;
}
/**
* 字节数组到char的转换
* @param b 字节数组
* @param index 从第几位开始
*/
public static char byteToChar(byte[] b, int index) {
return (char) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF));
}
/**
* short到字节数组的转换.
*/
public static byte[] shortToByte(short number) {
int temp = number;
byte[] b = new byte[2];
for (int i = 0; i < b.length; i++) {
// 将最低位保存在最低位
b[i] = new Integer(temp & 0xff).byteValue();
// 向右移8位
temp = temp >> 8;
}
return b;
}
/**
* 字节数组到short的转换.
* @param b 字节数组
* @param index 从数组的第几位开始取
*/
public static short byteToShort(byte[] b, int index) {
short s = 0;
// 最低位
short s0 = (short) (b[index] & 0xff);
short s1 = (short) (b[index + 1] & 0xff);
s1 <<= 8;
s = (short) (s0 | s1);
return s;
}
/**
* int到字节数组的转换.
*/
public static byte[] intToByte(int number) {
int temp = number;
byte[] b = new byte[4];
for (int i = 0; i < b.length; i++) {
// 将最低位保存在最低位
b[i] = new Integer(temp & 0xff).byteValue();
// 向右移8位
temp = temp >> 8;
}
return b;
}
/**
* 字节数组到int的转换.
* @param b 字节数组
* @param index 从数组的第几位开始取
*/
public static int byteToInt(byte[] b, int index) {
int s = 0;
// 最低位
int s0 = b[index] & 0xff;
int s1 = b[index + 1] & 0xff;
int s2 = b[index + 2] & 0xff;
int s3 = b[index + 3] & 0xff;
s3 <<= 24;
s2 <<= 16;
s1 <<= 8;
s = s0 | s1 | s2 | s3;
return s;
}
/**
* 字节数组到int的转换.
*/
public static int byteToInt(byte[] b) {
int s;
// 最低位
int s0 = b[0] & 0xff;
int s1 = b[1] & 0xff;
int s2 = b[2] & 0xff;
int s3 = b[3] & 0xff;
s3 <<= 24;
s2 <<= 16;
s1 <<= 8;
s = s0 | s1 | s2 | s3;
return s;
}
/**
* long类型转成byte数组
*/
public static byte[] longToByte(long number) {
long temp = number;
byte[] b = new byte[8];
for (int i = 0; i < b.length; i++) {
// 将最低位保存在最低位 temp = temp
b[i] = new Long(temp & 0xff).byteValue();
// >> 8;// 向右移8位
temp = temp >> 8;
}
return b;
}
/**
* 字节数组到long的转换.
*/
public static long inByteToLong(byte[] b, int index) {
long s;
// 最低位
long s0 = b[index] & 0xff;
long s1 = b[index + 1] & 0xff;
long s2 = b[index + 2] & 0xff;
long s3 = b[index + 3] & 0xff;
long s4 = b[index + 4] & 0xff;
long s5 = b[index + 5] & 0xff;
long s6 = b[index + 6] & 0xff;
long s7 = b[index + 7] & 0xff;
// s0不变
s1 <<= 8;
s2 <<= 16;
s3 <<= 24;
s4 <<= 8 * 4;
s5 <<= 8 * 5;
s6 <<= 8 * 6;
s7 <<= 8 * 7;
s = s0 | s1 | s2 | s3 | s4 | s5 | s6 | s7;
return s;
}
/**
* double到字节数组的转换.
*/
public static byte[] doubleToByte(double num) {
byte[] b = new byte[8];
long l = Double.doubleToLongBits(num);
for (int i = 0; i < 8; i++) {
b[i] = new Long(l).byteValue();
l = l >> 8;
}
return b;
}
/**
* 字节数组到double的转换
* @param b 数组
* @param index 第几位开始
* @return 返回结果
*/
public static double bytesToDouble(byte[] b, int index) {
long m;
m = b[0];
m &= 0xff;
m |= ((long) b[1] << 8);
m &= 0xffff;
m |= ((long) b[2] << 16);
m &= 0xffffff;
m |= ((long) b[3] << 24);
m &= 0xffffffffL;
m |= ((long) b[4] << 32);
m &= 0xffffffffffL;
m |= ((long) b[5] << 40);
m &= 0xffffffffffffL;
m |= ((long) b[6] << 48);
m &= 0xffffffffffffffL;
m |= ((long) b[7] << 56);
return Double.longBitsToDouble(m);
}
/**
* float到字节数组的转换.
*/
public static byte[] floatToByte(float x) {
// 先用 Float.floatToIntBits(f)转换成int
byte[] b = new byte[4];
int accum = Float.floatToIntBits(x);
b = intToByte(accum);
return b;
}
/**
* 字节数组到float的转换.
*/
public static float bytesToFloat(byte[] b) {
// 4 bytes
int accum = 0;
for (int shiftBy = 0; shiftBy < 4; shiftBy++) {
accum |= (b[shiftBy] & 0xff) << shiftBy * 8;
}
return Float.intBitsToFloat(accum);
}
}
数据转换类(Java对象数据转byte数组)
package cn.zxw.netty.work;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.google.common.primitives.Bytes;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.jdbc.core.JdbcTemplate;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.LinkedList;
import java.util.List;
/**
* @author zxw
* @version 1.0
* @description 合并发送Java对象中数据转byte组
* @data: 2020/3/23 21:41
*/
public class CodeUtil {
/**
* 游标和存放数据集合
* 记录游标是为了假设需要将数组内部来进行增加的时候处理
*/
private static int cursor = 0;
private static List<Byte> list = new LinkedList<>();
public static byte[] createMsg(Class targetClass, String targetJson) {
JSONObject jsonObject = JSONUtil.parseObj(targetJson);
//循环获得每个域对象
Field[] fields = targetClass.getDeclaredFields();
for (Field field : fields) {
//暴力发射
field.setAccessible(true);
//根据业务不同来设定其它类型,比如我这会有list类型
if (List.class.isAssignableFrom(field.getType())) {
//先获得通用类型
Type type = field.getGenericType();
//获得实际类型参数
Type listInnerType = ((ParameterizedTypeImpl) type).getActualTypeArguments()[0];
//获得集合里的类型,可以进行判断是否是自己的所想想要再来赋值,如果类型比较多可以通过参数传递过来
if (listInnerType.equals(User.class)) {
Field[] innerFields = User.class.getDeclaredFields();
for (Field innerField : innerFields) {
innerField.setAccessible(true);
Object innerType = jsonObject.get(field.getName());
//先转换成array获得第一个数据再转成jsonObject
JSONObject jsonInnerObj = (JSONObject) ((JSONArray) innerType).get(0);
//将域传入对象赋值
judgeDataType(innerField, jsonInnerObj);
}
}
} else {
//如果没有集合直接是基本类型直接进行赋值
judgeDataType(field, jsonObject);
}
}
return Bytes.toArray(list);
}
/**
* 判断数据类型进行赋值
*
* @param field 每个域
* @param jsonObject json对象
*/
private static void judgeDataType(Field field, JSONObject jsonObject) {
if (field.getType().isArray()) {
switch (field.getType().getComponentType().toString()) {
case "byte":
JSONArray jsonArray = jsonObject.getJSONArray(field.getName());
for (int i = 0; i < jsonArray.size(); i++) {
Byte byteNum = jsonArray.getByte(i);
list.add(byteNum);
cursor += 1;
}
break;
case "short":
JSONArray jsonShortArray = jsonObject.getJSONArray(field.getName());
for (int i = 0; i < jsonShortArray.size(); i++) {
byte[] shortData = DataConverterUtil.shortToByte(jsonShortArray.getShort(i));
putData(shortData, 2);
}
break;
case "int":
JSONArray jsonIntArray = jsonObject.getJSONArray(field.getName());
for (int i = 0; i < jsonIntArray.size(); i++) {
byte[] intData = DataConverterUtil.intToByte(jsonIntArray.getInt(i));
putData(intData, 4);
}
break;
case "float":
JSONArray jsonFloatArray = jsonObject.getJSONArray(field.getName());
for (int i = 0; i < jsonFloatArray.size(); i++) {
byte[] floatData = DataConverterUtil.floatToByte(jsonFloatArray.getFloat(i));
putData(floatData, 8);
}
break;
case "double":
JSONArray jsonDouArray = jsonObject.getJSONArray(field.getName());
for (int i = 0; i < jsonDouArray.size(); i++) {
byte[] doubleData = DataConverterUtil.doubleToByte(jsonDouArray.getDouble(i));
putData(doubleData, 8);
}
break;
case "long":
JSONArray jsonLongArray = jsonObject.getJSONArray(field.getName());
for (int i = 0; i < jsonLongArray.size(); i++) {
byte[] longData = DataConverterUtil.longToByte(jsonLongArray.getLong(i));
putData(longData, 8);
}
break;
default:
throw new RuntimeException("无匹配数据");
}
} else {
switch (field.getType().toString()) {
case "byte":
Byte byteData = jsonObject.getByte(field.getName());
list.add(byteData);
cursor += 1;
break;
case "short":
byte[] shortData = DataConverterUtil.shortToByte(jsonObject.getShort(field.getName()));
putData(shortData, 2);
break;
case "int":
byte[] intData = DataConverterUtil.intToByte(jsonObject.getInt(field.getName()));
putData(intData, 4);
break;
case "float":
byte[] floatData = DataConverterUtil.floatToByte(jsonObject.getFloat(field.getName()));
putData(floatData, 8);
break;
case "double":
byte[] douData = DataConverterUtil.doubleToByte(jsonObject.getDouble(field.getName()));
putData(douData, 8);
break;
case "long":
byte[] longData = DataConverterUtil.longToByte(jsonObject.getLong(field.getName()));
putData(longData, 8);
break;
default:
throw new RuntimeException("无匹配数据");
}
}
}
/**
* 添加数据
*
* @param bytes 转成byte数组的数据
* @param num 加多少
*/
private static void putData(byte[] bytes, int num) {
for (byte every : bytes) {
list.add(every);
cursor += num;
}
}
}
One和User
package cn.zxw.netty.work;
import lombok.Data;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author zxw
* @version 1.0
* @description 测试编解码
* @data: 2020/3/26 9:15
*/
@Data
public class One {
private byte byteNum = 1;
private short shortNum = 2;
private int intNum = 3;
private float floatNum = 4;
private double doubleNum = 5;
private long longNum = 6;
private int[] arr = {1, 2, 3, 4};
private List<User> userList;
}
/
/**
* @author zxw
* @version 1.0
* @description bootstart
* @data: 2020/3/23 22:20
*/
@Data
public class User {
private int age = 8;
}
编写controller进行测试
/**
* @author zxw
* @version 1.0
* @description bootstart
* @data: 2020/2/28 11:10
*/
@RestController
@Api(tags = "TestController", description = "测试controller")
public class TestController {
@ApiOperation("测试netty程序")
@RequestMapping(value = "/nettyTest", method = RequestMethod.POST)
public String nettyTest() {
One one = new One();
cn.zxw.netty.work.User user = new cn.zxw.netty.work.User();
List<cn.zxw.netty.work.User> list = new LinkedList<>();
list.add(user);
one.setUserList(list);
String s = JSONUtil.toJsonStr(one);
byte[] msg = CodeUtil.createMsg(One.class, s);
SayOnClient.channel.writeAndFlush(Unpooled.copiedBuffer(msg));
return "success";
}
使用tcp服务端工具起一个服务器进行测试,发送数据后如下
此方法是自己第一次接触netty后在项目中的应用,如果有更好更规范的方式欢迎大家指点(因为客户端没有进行优雅关闭)