在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称:im开源软件地址:https://gitee.com/130451/im开源软件介绍:imQQ群 866238744 项目介绍一款即时聊天软件的服务器,app也做了,安卓对于推送的消息,使用eventbus做事件通知。功能有:
有道云数据格式协议地址:http://note.youdao.com/noteshare?id=f5a6a67680096e690f78143bfda8634a 软件架构软件架构说明springboot + netty +mybatis +mysql 安装教程
使用说明本项目是一款即时聊天的后台程序,经过考虑,在发送的时候使用http协议,之前直接全部用在tcp全双工协议之上的方式,非常繁琐,因为你得要自己写个堵塞控制,才能做到一个请求对应一个返回结果,因此在数据发送的时候都用做好的http协议,而在推送方面,使用tcp方式,没有使用protobuf数据格式,而是自己定义pojo,对应数据格式的Body部分(查看上面的有道云笔记的链接),根据pojo的字段数据类型做读写协议,根据以往的经验,设计了2个接口,BodyToBytes和BytesToBody ,分别是pojo转字节数组和字节数组转pojo,最后让一个BaseBodyCodec实现这两个接口,而每个业务的类只需要继承这个类就拥有了数据解码和编码的能力。另外做了一个注解ProtocolField @Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface ProtocolField { int sort(); int len() default -1; String lenField() default "";} 这分别表示字段序号,长度,和表示由某个字段的值来指定长度,为什么这么做,因为,如果直接根据反射获取class的成员字段的话,那么其实是无序的,而且还有编译器生成的其他的字段,而我们的协议是顺序严格要一致的,比如第一个字节表示版本号,第2,3,4,5的这个四个字节表示流水号,那么肯定不能搞乱,解码和编码要严格和我们定义的协议一致。可以查看这个BaseBodyCodec实现代码: @Override public byte[] toBytes() { ByteBuf byteBuf = Unpooled.buffer(); try { Class<? extends BaseBodyCodec> clazz = this.getClass(); Field[] declaredFields = clazz.getDeclaredFields(); Arrays.sort(declaredFields, new Comparator<Field>() { @Override public int compare(Field o1, Field o2) { if (!o1.isAnnotationPresent(ProtocolField.class)) { return -9999; } if (!o2.isAnnotationPresent(ProtocolField.class)) { return 9999; } int annotation1 = o1.getAnnotation(ProtocolField.class).sort(); int annotation2 = o2.getAnnotation(ProtocolField.class).sort(); return annotation1 - annotation2; } }); for (Field field : declaredFields) { if (field.isAnnotationPresent(ProtocolField.class)) { String fieldName = field.getName(); PropertyDescriptor pd = new PropertyDescriptor(fieldName, clazz); Method readMethod = pd.getReadMethod(); Class<?> type = field.getType(); if (type == byte.class) { byte invoke = (byte) readMethod.invoke(this); byteBuf.writeByte(invoke); } if (type == short.class) { short invoke = (short) readMethod.invoke(this); byteBuf.writeShort(invoke); } if (type == int.class) { int invoke = (int) readMethod.invoke(this); byteBuf.writeInt(invoke); } if (type == long.class) { long invoke = (long) readMethod.invoke(this); byteBuf.writeLong(invoke); } if ((type.isArray() && type.getComponentType() == byte.class)) { byte[] invoke = (byte[]) readMethod.invoke(this); byteBuf.writeBytes(invoke); } if (type.isArray() && type.getComponentType().getSuperclass() == BaseBodyCodec.class) { BaseBodyCodec[] o = (BaseBodyCodec[]) readMethod.invoke(this); for (int i = 0; i < o.length; i++) { byteBuf.retain(); byte[] bytes = o[i].toBytes(); byteBuf.writeBytes(bytes); } } if (type == String.class) { String invoke = (String) readMethod.invoke(this); if(invoke!=null){ byteBuf.writeCharSequence(invoke, CharsetUtil.UTF_8); } } if (type.getSuperclass() == BaseBodyCodec.class) { BaseBodyCodec o = (BaseBodyCodec) readMethod.invoke(this); byteBuf.retain(); byte[] o_bytes = o.toBytes(); byteBuf.writeBytes(o_bytes); } } } byte[] bytes = new byte[byteBuf.readableBytes()]; byteBuf.readBytes(bytes); return bytes; } catch (Exception e) { e.printStackTrace(); } finally { byteBuf.release(); } return null; } @Override public T fillBodyPojo(ByteBuf byteBuf) { if (byteBuf != null) { try { Class<? extends BaseBodyCodec> clazz = this.getClass(); Field[] declaredFields = clazz.getDeclaredFields(); Arrays.sort(declaredFields, new Comparator<Field>() { @Override public int compare(Field o1, Field o2) { if (!o1.isAnnotationPresent(ProtocolField.class)) { return -9999; } if (!o2.isAnnotationPresent(ProtocolField.class)) { return 9999; } int annotation1 = o1.getAnnotation(ProtocolField.class).sort(); int annotation2 = o2.getAnnotation(ProtocolField.class).sort(); return annotation1 - annotation2; } }); for (int i = 0; i < declaredFields.length; i++) { Field field = declaredFields[i]; if (field.isAnnotationPresent(ProtocolField.class)) { String fieldName = field.getName(); PropertyDescriptor pd = new PropertyDescriptor(fieldName, clazz); Method writeMethod = pd.getWriteMethod(); Class<?> type = field.getType(); if (type == byte.class) { writeMethod.invoke(this, byteBuf.readByte()); } if (type == short.class) { writeMethod.invoke(this, byteBuf.readShort()); } if (type == int.class) { writeMethod.invoke(this, byteBuf.readInt()); } if (type == long.class) { writeMethod.invoke(this, byteBuf.readLong()); } if ((type.isArray() && type.getComponentType() == byte.class) || type == String.class) { if (field.isAnnotationPresent(ProtocolField.class)) { ProtocolField pf = field.getAnnotation(ProtocolField.class); int len = pf.len(); String lenField = pf.lenField(); if (len != -1) { if (type == String.class) { CharSequence charSequence = byteBuf.readCharSequence(len, CharsetUtil.UTF_8); writeMethod.invoke(this, charSequence); } else { byte[] bytes = new byte[len]; byteBuf.readBytes(bytes); writeMethod.invoke(this, bytes); } } else if (!lenField.equals("")) { PropertyDescriptor len_field_pd = new PropertyDescriptor(lenField, clazz); Method readMethod = len_field_pd.getReadMethod(); Integer integer = Integer.valueOf(readMethod.invoke(this).toString()); if (type == String.class) { CharSequence charSequence = byteBuf.readCharSequence(integer, CharsetUtil.UTF_8); writeMethod.invoke(this, charSequence); } else { byte[] bytes = new byte[integer]; byteBuf.readBytes(bytes); writeMethod.invoke(this, bytes); } } } else { throw new RuntimeException(fieldName + " 没有@ProtocolField注解!!"); } } //对于BaseBodyCodec[]类型的 如果没有指定名长度则 一定是要放在最后 因为后面只能根据判断后面是否还存在可读字节 //来处理是否继续读取 一直循环下去 if (type.isArray() && type.getComponentType().getSuperclass() == BaseBodyCodec.class) { Class componentType = type.getComponentType(); if (field.isAnnotationPresent(ProtocolField.class)) { int itemLen = 0; ProtocolField pf = field.getAnnotation(ProtocolField.class); int len = pf.len(); String lenField = pf.lenField(); if (len != -1) { itemLen = len; } else if (!lenField.equals("")) { PropertyDescriptor len_field_pd = new PropertyDescriptor(lenField, clazz); Method readMethod = len_field_pd.getReadMethod(); itemLen = Integer.valueOf(readMethod.invoke(this).toString()); } Object o = Array.newInstance(componentType, itemLen); for (int j = 0; j < itemLen; j++) { BaseBodyCodec item_o = (BaseBodyCodec) componentType.newInstance(); byteBuf.retain(); item_o.fillBodyPojo(byteBuf); Array.set(o, j, item_o); } writeMethod.invoke(this, o); } else { //判断是不是最后一项 if (i == declaredFields.length - 1) { ArrayList tmpList = new ArrayList(); while (byteBuf.isReadable()) { BaseBodyCodec item_o = (BaseBodyCodec) componentType.newInstance(); byteBuf.retain(); item_o.fillBodyPojo(byteBuf); tmpList.add(item_o); } Object[] objects = tmpList.toArray(); Object o = Array.newInstance(componentType, objects.length); for (int j = 0; j < objects.length; j++) { Array.set(o, j, objects[j]); } writeMethod.invoke(this, o); } else { throw new RuntimeException(fieldName + " 没有注解而且不是最后一项无法正常解析!!"); } } } if (type.getSuperclass() == BaseBodyCodec.class) { BaseBodyCodec o = (BaseBodyCodec) type.newInstance(); writeMethod.invoke(this, o); byteBuf.retain(); o.fillBodyPojo(byteBuf); } } } return (T) this; } catch (Exception e) { e.printStackTrace(); } finally { byteBuf.release(); } } return null; } 然后里面除了基本的外层数据的解码和编码,还需要做心跳检测,当一个文件描述符没有长时间的读写,应该关闭该连接,对于tcp这种协议需要注意的是,该协议需要做粘包处理,具体实现参考里面的ProtocolDecoder.java ,对于做过类似jt808协议的同学应该理解起来应该不难。 安卓端该项目已经做了一个可以用的app了,开源项目地址,这个安卓基本上是我同学做的,只能晚上QQ语音方式沟通来做,我就写了tcp,UDP通信方面的代码,和整个app逻辑的设计与建议。这个app可以做到文字,表情,图片通信,语音通信,添加好友,申请好友,等一些基础的业务。现在还有一点UI上面的小问题。有兴趣的朋友一起来完善下。 |
请发表评论