• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

im: netty 开发的私有协议栈 即时聊天服务器IMNow

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称:

im

开源软件地址:

https://gitee.com/130451/im

开源软件介绍:

im

QQ群 866238744
希望大家可以提出建议和贡献代码,共同进步。 :yum:

项目介绍

一款即时聊天软件的服务器,app也做了,安卓对于推送的消息,使用eventbus做事件通知。功能有:

  • 个人信息的查看,修改,包括头像
  • 添加好友申请,接受与拒绝
  • 查看好友信息,删除好友,备注好友
  • 实时未读消息数量
  • 实时的好友消息,
  • 发送文字,表情,图片
  • 进行点对点内网之间穿透的语音通信(UDP方式)
  • 后续补充

有道云数据格式协议地址:http://note.youdao.com/noteshare?id=f5a6a67680096e690f78143bfda8634a

软件架构

软件架构说明springboot + netty +mybatis +mysql

安装教程

  1. sql文件导入到mysql ,在application-dev.yml中修改你的数据库地址
  2. 启动ImApplication.java 会启动一个tomcat HTTP服务12345端口 还有一个是netty的TCP端口20000,和一个UDP端口20001用来UDP客户端通信打洞

使用说明

本项目是一款即时聊天的后台程序,经过考虑,在发送的时候使用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上面的小问题。有兴趣的朋友一起来完善下。


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap