如今咱们都知道,rpc的三要素:IO模型,线程模型,而后就是数据交互模型,即咱们说的序列化和反序列化,如今咱们来看一下压缩比率最大的二进制序列化方式——Protobuffer,并且该方式是能够跨语言的,几乎大部分的语言均可以互相序列化和反序列化。javascript
要使用Protobuffer,须要先进行安装。由于本人使用的是mac,因此我使用的是.tar.gz的二进制压缩文件。html
其下载地址为https://github.com/protocolbuffers/protobuf/releases/tag/v3.5.0前端
下载完成后,依次执行如下命令java
tar -xzvf protobuf-all-3.5.0.tar.gznode
cd protobuf-3.5.0/jquery
./configuregit
makegithub
make checkweb
make installajax
安装须要的时间比较长,须要耐心等待,安装完成后,执行
admindeMacBook-Pro:protobuf-3.5.0 admin$ protoc --version
libprotoc 3.5.0
有显示红色字样,表示安装成功。
如今咱们来写一个hello world的样例
新建一个player.proto的文件
touch player.proto
vim player.proto
添入以下代码
option java_package = "com.guanjian.proto"; //此处为生成java文件的包名
option java_outer_classname = "PlayerModule"; //生成Java的类名
message PBPlayer{ //Protobuffer的类声明,会生成java的内部类
required int64 playerId = 1; //required为必须字段,int64表示java的long类型,= 1这个1表示字段键名,每一个字段的键名不能重复
required int32 age = 2; //int32表示java的int类型
required string name = 3; //string表示java的String
repeated int32 skills = 4; //repeated int32表示Java的List<Integer>
}
message PBResource{
required int64 gold = 1;
required int32 energy = 2;
}
保存后,输入命令
protoc ./player.proto --java_out=./
会在当前文件夹生成com/guanjian/proto的三个文件夹
cd com/guanjian/proto后,能够看到咱们生成的java文件
admindeMacBook-Pro:proto admin$ ls
PlayerModule.java
这里java文件里面的内容很是的多,也很是复杂。
如今咱们来看一下怎么来操做这个Java类。
在java中要使用protobuffer,须要添加依赖,版本号跟咱们安装的Protobuffer保持一致。
<dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>3.5.0</version> </dependency>
public class PB2Bytes { public static void main(String[] args) { toBytes(); } /** * 序列化 */ public static byte[] toBytes() { //获取一个PBPlayer的建造器(建造者模式) PlayerModule.PBPlayer.Builder builder = PlayerModule.PBPlayer.newBuilder(); //建造一个PBPlayer对象 PlayerModule.PBPlayer peter = builder.setPlayerId(101).setAge(21).setName("Peter").addSkills(1001) .build(); //序列化成字节数组 byte[] bytes = peter.toByteArray(); System.out.println(Arrays.toString(bytes)); return bytes; } }
运行获得的结果为:
[8, 101, 16, 21, 26, 5, 80, 101, 116, 101, 114, 32, -23, 7]
以前咱们在.proto文件中设置了required标识,意思就是说在建造器中,咱们必须设置相应的属性,不然将会报错。
好比咱们把年龄取消掉,不设置年龄
public class PB2Bytes { public static void main(String[] args) { toBytes(); } /** * 序列化 */ public static byte[] toBytes() { //获取一个PBPlayer的建造器(建造者模式) PlayerModule.PBPlayer.Builder builder = PlayerModule.PBPlayer.newBuilder(); //建造一个PBPlayer对象 PlayerModule.PBPlayer peter = builder.setPlayerId(101).setName("Peter").addSkills(1001) .build(); //序列化成字节数组 byte[] bytes = peter.toByteArray(); System.out.println(Arrays.toString(bytes)); return bytes; } }
运行结果将抛出异常
如今咱们来增长反序列化
public class PB2Bytes { public static void main(String[] args) throws InvalidProtocolBufferException { byte[] bytes = toBytes(); toPlayer(bytes); } /** * 序列化 */ public static byte[] toBytes() { //获取一个PBPlayer的建造器(建造者模式) PlayerModule.PBPlayer.Builder builder = PlayerModule.PBPlayer.newBuilder(); //建造一个PBPlayer对象 PlayerModule.PBPlayer peter = builder.setPlayerId(101).setAge(21).setName("Peter").addSkills(1001) .build(); //序列化成字节数组 byte[] bytes = peter.toByteArray(); System.out.println(Arrays.toString(bytes)); return bytes; } /** * 反序列化 * @param bytes */ public static void toPlayer(byte[] bytes) throws InvalidProtocolBufferException { PlayerModule.PBPlayer peter = PlayerModule.PBPlayer.parseFrom(bytes); System.out.println("playerId:" + peter.getPlayerId() + ",age:" + peter.getAge() + ",name:" + peter.getName() + ",skills:" + peter.getSkillsList().get(0)); } }
运行结果
[8, 101, 16, 21, 26, 5, 80, 101, 116, 101, 114, 32, -23, 7]
playerId:101,age:21,name:Peter,skills:1001
咱们仍是用Java自带的序列化和反序列化方式来对比一下Protobuffer的序列化和反序列化
首先,咱们定义一个相似PBPlayer的Java类Player
@Data @Builder public class Player implements Serializable { private long playerId; private int age; private String name; private List<Integer> skills; }
这里咱们一样使用建造者模式来构建对象
public class Java2Bytes { public static void main(String[] args) throws IOException, ClassNotFoundException { byte[] bytes = toByes(); toPlayer(bytes); } /** * 序列化 * @return * @throws IOException */ public static byte[] toByes() throws IOException { List<Integer> skills = new ArrayList<>(1); skills.add(1001); Player peter = Player.builder().playerId(101).age(21).name("Peter").skills(skills) .build(); //字节数组流 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); //使用字节数组流来初始化一个对象流 ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); //将Player对象写入对象流 objectOutputStream.writeObject(peter); //获取字节数组 byte[] bytes = byteArrayOutputStream.toByteArray(); System.out.println(Arrays.toString(bytes)); return bytes; } /** * 反序列化 * @param bytes * @throws IOException * @throws ClassNotFoundException */ public static void toPlayer(byte[] bytes) throws IOException, ClassNotFoundException { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); Player peter = (Player) objectInputStream.readObject(); System.out.println("playerId:" + peter.getPlayerId() + ",age:" + peter.getAge() + ",name:" + peter.getName() + ",skills:" + peter.getSkills().get(0)); } }
运行结果
[-84, -19, 0, 5, 115, 114, 0, 38, 99, 111, 109, 46, 103, 117, 97, 110, 106, 105, 97, 110, 46, 119, 101, 98, 115, 111, 99, 107, 101, 116, 46, 110, 101, 116, 116, 121, 46, 112, 98, 46, 80, 108, 97, 121, 101, 114, 119, -42, 102, 37, -49, -99, -17, 15, 2, 0, 4, 73, 0, 3, 97, 103, 101, 74, 0, 8, 112, 108, 97, 121, 101, 114, 73, 100, 76, 0, 4, 110, 97, 109, 101, 116, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 76, 0, 6, 115, 107, 105, 108, 108, 115, 116, 0, 16, 76, 106, 97, 118, 97, 47, 117, 116, 105, 108, 47, 76, 105, 115, 116, 59, 120, 112, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 101, 116, 0, 5, 80, 101, 116, 101, 114, 115, 114, 0, 19, 106, 97, 118, 97, 46, 117, 116, 105, 108, 46, 65, 114, 114, 97, 121, 76, 105, 115, 116, 120, -127, -46, 29, -103, -57, 97, -99, 3, 0, 1, 73, 0, 4, 115, 105, 122, 101, 120, 112, 0, 0, 0, 1, 119, 4, 0, 0, 0, 1, 115, 114, 0, 17, 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 73, 110, 116, 101, 103, 101, 114, 18, -30, -96, -92, -9, -127, -121, 56, 2, 0, 1, 73, 0, 5, 118, 97, 108, 117, 101, 120, 114, 0, 16, 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 78, 117, 109, 98, 101, 114, -122, -84, -107, 29, 11, -108, -32, -117, 2, 0, 0, 120, 112, 0, 0, 3, -23, 120]
playerId:101,age:21,name:Peter,skills:1001
如此咱们能够看出ProtoBuffer的序列化结果字节数要小点多。
如今咱们来看一下Kryo的序列化出来的结果,要使用Kryo得修改一下Player类,由于Kryo反序列化必需要有无参构造器。首先放置依赖
<dependency> <groupId>com.esotericsoftware</groupId> <artifactId>kryo-shaded</artifactId> <version>4.0.2</version> </dependency>
@Data @NoArgsConstructor @AllArgsConstructor public class Player implements Serializable { private long playerId; private int age; private String name; private List<Integer> skills; }
此处咱们使用全参构造器来构造
public class Kryo2Bytes { public static void main(String[] args) { byte[] bytes = toByte(); toPlayer(bytes); } /** * 序列化 * @return */ public static byte[] toByte() { List<Integer> skills = new ArrayList<>(1); skills.add(1001); Player peter = new Player(101,21,"Peter",skills); Kryo kryo = new Kryo(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); Output output = new Output(byteArrayOutputStream); kryo.writeObject(output,peter); output.close(); byte[] bytes = byteArrayOutputStream.toByteArray(); System.out.println(Arrays.toString(bytes)); return bytes; } /** * 反序列化 * @param bytes */ public static void toPlayer(byte[] bytes) { Kryo kryo = new Kryo(); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); Input input = new Input(byteArrayInputStream); Player peter = kryo.readObject(input, Player.class); System.out.println("playerId:" + peter.getPlayerId() + ",age:" + peter.getAge() + ",name:" + peter.getName() + ",skills:" + peter.getSkills().get(0)); input.close(); } }
运行结果
[1, 42, 1, 80, 101, 116, 101, -14, -54, 1, 1, 0, 106, 97, 118, 97, 46, 117, 116, 105, 108, 46, 65, 114, 114, 97, 121, 76, 105, 115, -12, 1, 1, 2, -46, 15]
playerId:101,age:21,name:Peter,skills:1001
由以上结果可见,序列化字节数最少可能是ProtoBuffer,其次是Kryo,最长的是Java自己的序列化方式。
如今咱们来看一下JS的使用方法。
要使用JS的Protobuffer,须要先安装npm
brew install node
npm install google-protobuf
此时会在当前目录下生成一个node_modules,所有目录内容以下
而后使用protoc ./player.proto --js_out=library=myproto_libs,binary:. player.proto生成js版本的protobuffer文件
myproto_libs.js
将这些文件放入到项目的js目录下
而且注释掉google-protobuf.js掉最后一行代码
编辑咱们的index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> </head> <body> <script type="application/javascript" src="js/google-protobuf/google-protobuf.js"></script> <script type="application/javascript" src="js/myproto_libs.js"></script> <script type="application/javascript"> var player = new proto.PBPlayer(); player.setPlayerid(101); player.setAge(21); player.setName("Peter"); player.addSkills(1001); var bytes = player.serializeBinary(); var logs = ""; for(var i=0;i < bytes.length;i++) { logs = logs + bytes[i] + ","; } console.log(logs); var peter = proto.PBPlayer.deserializeBinary(bytes); console.log("playerId:" + peter.getPlayerid() + ",age:" + peter.getAge() + ",name:" + peter.getName() + ",skills:" + peter.getSkillsList()); </script> </body> </html>
运行出来的效果以下
如今咱们来将Netty整合WebSocket 中的聊天改形成Protobuffer的传输,首先咱们须要定义知足业务需求的.proto文件
option java_package = "com.cloud.notification.chat.proto"; option java_outer_classname = "ChatModule"; message ChatMsg{ optional string senderId = 1; optional string receiverId = 2; optional string msg = 3; optional string msgId = 4; optional int32 sign = 5; optional string createDate = 6; } message DataContent{ required int32 action = 1; optional ChatMsg chatMsg = 2; optional string extand = 3; }
按步骤生成咱们须要的java和js代码后,咱们来改造服务端。先将以前的ChatMsg类作一下修改。创建senderId,receiverId,msg的三参构造器就能够了。
@NoArgsConstructor @RequiredArgsConstructor @ToString @Data public class ChatMsg implements Serializable,Chat{ @NonNull @JSONField(serializeUsing = ToStringSerializer.class) private Long senderId; //发送者的用户id @NonNull @JSONField(serializeUsing = ToStringSerializer.class) private Long receiverId; //接收者的用户id @NonNull private String msg; @JSONField(serializeUsing = ToStringSerializer.class) private Long msgId; //用于消息的签收 private MsgSignFlagEnum signed; //消息签收状态 private LocalDateTime createDate; @Override @Transactional public void save(Chat chatMsg) { ChatDao chatDao = SpringBootUtil.getBean(ChatDao.class); IdService idService = SpringBootUtil.getBean(IdService.class); ((ChatMsg)chatMsg).setMsgId(idService.genId()); ((ChatMsg)chatMsg).setCreateDate(LocalDateTime.now()); chatDao.saveChat((ChatMsg) chatMsg); } @Transactional @Override public void updateMsgSigned(List<Long> msgIdList) { ChatDao chatDao = SpringBootUtil.getBean(ChatDao.class); chatDao.updateMsgSigned(msgIdList); } @Transactional @Override public List<ChatMsg> findUnReadChat(Long acceptUserId) { ChatDao chatDao = SpringBootUtil.getBean(ChatDao.class); return chatDao.findUnReadMsg(acceptUserId); } }
首先把文本传输为主的TextWebSocketFrame改成二进制传输为主的BinaryWebSocketFrame
/** * BinaryWebSocketFrame: 在netty中,用于为websocket专门处理二进制的对象,frame是消息的载体 */ @Slf4j public class WebSocketHandler extends SimpleChannelInboundHandler<BinaryWebSocketFrame> { //用于记录和管理全部客户端的channel private static ChannelGroup users = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); private Chat chatMsgService = ChatMsgFactory.createChatMsgService(); @Override protected void channelRead0(ChannelHandlerContext ctx, BinaryWebSocketFrame msg) throws Exception { //获取客户端传输过来的消息 // String content = msg.text(); ByteBuf content = msg.content(); log.info("content为:" + content.toString()); Channel currentChannel = ctx.channel(); //解析传输过来的消息转成聊天对象 content.readerIndex(0); byte[] bytes = new byte[content.readableBytes()]; content.readBytes(bytes); // DataContent dataContent = JSONObject.parseObject(content,DataContent.class); ChatModule.DataContent dataContent = ChatModule.DataContent.parseFrom(bytes); //获取聊天对象的动做 Integer action = dataContent.getAction(); if (action == MsgActionEnum.CONNECT.type) { //当websocket第一次open的时候,初始化channel,把用的channel和userId关联起来 Long senderId = Long.parseLong(dataContent.getChatMsg().getSenderId()); UserChannelRel.put(senderId,currentChannel); //测试 users.stream().forEach(channel -> log.info(channel.id().asLongText())); UserChannelRel.output(); }else if (action == MsgActionEnum.CHAT.type) { //聊天类型的消息,把聊天记录保存到数据库,同时标记消息的签收状态[未签收] ChatModule.ChatMsg chatMsg = dataContent.getChatMsg(); String msgText = chatMsg.getMsg(); Long receiverId = Long.parseLong(chatMsg.getReceiverId()); Long senderId = Long.parseLong(chatMsg.getSenderId()); //保存数据库 ChatMsg javaChatMsg = new ChatMsg(Long.parseLong(chatMsg.getSenderId()),Long.parseLong(chatMsg.getReceiverId()), chatMsg.getMsg()); chatMsgService.save(javaChatMsg); ChatModule.ChatMsg.Builder builder = ChatModule.ChatMsg.newBuilder(chatMsg); log.info(javaChatMsg.getMsgId() + ",java的"); ChatModule.ChatMsg newMsg = builder.setMsgId(String.valueOf(javaChatMsg.getMsgId())).build(); log.info(newMsg.getMsgId() + ",protobuffer的"); byte[] sendBytes = newMsg.toByteArray(); ByteBuf buf = Unpooled.copiedBuffer(sendBytes); Channel receiverChannel = UserChannelRel.get(receiverId); if (receiverChannel == null) { //接收方离线状态,此处无需处理 }else { Channel findChannel = users.find(receiverChannel.id()); if (findChannel != null) { findChannel.writeAndFlush(new BinaryWebSocketFrame(buf)); }else { //接收方离线,此处无需处理 } } }else if (action == MsgActionEnum.SIGNED.type) { //签收消息类型,针对具体的消息进行签收,修改数据库中对应消息的签收状态[已签收] //扩展字段在signed类型的消息中,表明须要去签收的消息id,逗号间隔 String msgIdsStr = dataContent.getExtand(); log.info("extand为:" + msgIdsStr); String[] msgIds = msgIdsStr.split(","); List<Long> msgIdList = new ArrayList<>(); for (String mId : msgIds) { if (!StringUtils.isEmpty(mId)) { msgIdList.add(Long.valueOf(mId)); } } log.info(msgIdList.toString()); if (!CollectionUtils.isEmpty(msgIdList)) { //批量签收 chatMsgService.updateMsgSigned(msgIdList); } }else if (action == MsgActionEnum.KEEPALIVE.type) { //心跳类型的消息 log.info("收到来自channel为[" + currentChannel + "]的心跳包"); } } /** * 当客户端链接服务端以后(打开链接) * 获取客户端的channel,而且放到ChannelGroup中去进行管理 * @param ctx * @throws Exception */ @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { users.add(ctx.channel()); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { //当触发handlerRemoved,ChannelGroup会自动移除对应的客户端的channel //因此下面这条语句可不写 // clients.remove(ctx.channel()); log.info("客户端断开,channel对应的长id为:" + ctx.channel().id().asLongText()); log.info("客户端断开,channel对应的短id为:" + ctx.channel().id().asShortText()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.channel().close(); users.remove(ctx.channel()); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { //IdleStateEvent是一个用户事件,包含读空闲/写空闲/读写空闲 if (evt instanceof IdleStateEvent) { IdleStateEvent event = (IdleStateEvent) evt; if (event.state() == IdleState.READER_IDLE) { log.info("进入读空闲"); }else if (event.state() == IdleState.WRITER_IDLE) { log.info("进入写空闲"); }else if (event.state() == IdleState.ALL_IDLE) { log.info("channel关闭前,用户数量为:" + users.size()); //关闭无用的channel,以防资源浪费 ctx.channel().close(); log.info("channel关闭后,用户数量为:" + users.size()); } } } }
而后改造咱们的前端代码,这里比较重要的是前端的websocket默认是文本方式传输改为二进制方式传输CHAT.socket.binaryType = 'arraybuffer';
用户id为1的代码以下,这里须要注意,咱们用的各类id都是字符串类型的
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> </head> <body> <div>发送消息:</div> <input type="text" id="msgContent" /> <input type="button" value="发送" onclick="CHAT.chat('1','2',msgContent.value,app.CHAT,null)" /> <input type="file" id="file" name="file"> <input type="button" id="button" value="发送图片" > <div>接受消息:</div> <div id="receiveMsg" style="background-color: gainsboro;"></div> <script type="application/javascript" src="js/app.js"></script> <script type="application/javascript" src="js/mui.min.js"></script> <script type="application/javascript" src="js/jquery-3.3.1.min.js"></script> <script type="application/javascript" src="js/google-protobuf/google-protobuf.js"></script> <script type="application/javascript" src="js/myproto_libs.js"></script> <script type="application/javascript"> window.CHAT = { socket: null, init: function() { if (window.WebSocket) { CHAT.socket = new WebSocket("ws://127.0.0.1:9999/ws"); CHAT.socket.binaryType = 'arraybuffer'; CHAT.socket.onopen = function() { console.log("链接创建成功"); CHAT.chat("1",null,null,app.CONNECT,null); //每次链接的时候获取未读消息 fetchUnReadMsg(); //定时发送心跳,30秒一次 setInterval("CHAT.keepalive()",30000); }, CHAT.socket.onclose = function() { console.log("链接关闭"); }, CHAT.socket.onerror = function() { console.log("发生错误"); }, CHAT.socket.onmessage = function(e) { var receiveMsg = document.getElementById("receiveMsg"); var html = receiveMsg.innerHTML; var chatMsg = proto.ChatMsg.deserializeBinary(e.data); console.log("senderId:" + chatMsg.getSenderid() + ",receiverId:" + chatMsg.getReceiverid() + ",msg:" + chatMsg.getMsg() + ",MsgId:" + chatMsg.getMsgid()); receiveMsg.innerHTML = html + "<br/>" + chatMsg.getMsg(); //消息签收 CHAT.chat(chatMsg.getReceiverid(),null,null,app.SIGNED,chatMsg.getMsgid()); } }else { alert("浏览器不支持WebSocket协议..."); } }, chat: function(senderId,receiverId,msg,action,extand) { // var chatMsg = new app.ChatMsg(senderId,receiverId,msg,null); var chatMsg = new proto.ChatMsg(); chatMsg.setSenderid(senderId); chatMsg.setReceiverid(receiverId); chatMsg.setMsg(msg); // var dataContent = new app.DataContent(action,chatMsg,extand); var dataContent = new proto.DataContent(); dataContent.setAction(action); dataContent.setChatmsg(chatMsg); dataContent.setExtand(extand); var bytes = dataContent.serializeBinary(); var intView = new Int8Array(bytes); CHAT.socket.send(intView); }, keepalive: function() { CHAT.chat("1",null,null,app.KEEPALIVE,null); fetchUnReadMsg(); } } CHAT.init(); function fetchUnReadMsg() { mui.ajax('http://127.0.0.1:8008/notification-anon/getunreadmeg?receiverid=1',{ data:{}, dataType:'json',//服务器返回json格式数据 type:'post',//HTTP请求类型 timeout:10000,//超时时间设置为10秒; success:function(data){ if (data.code == 200) { var contactList = data.data; var ids = ""; console.log(JSON.stringify(contactList)); var receiveMsg = document.getElementById("receiveMsg"); for (var i = 0;i < contactList.length;i++) { var msgObj = contactList[i]; var html = receiveMsg.innerHTML; receiveMsg.innerHTML = html + "<br/>" + msgObj.msg; ids = ids + msgObj.msgId + ","; } //批量签收未读消息 CHAT.chat("1",null,null,app.SIGNED,ids); } } }); } $(function () { $("#button").click(function () { var form = new FormData(); form.append("file", document.getElementById("file").files[0]); $.ajax({ url: "http://xxx.xxx.xxx.xxx:8010/files-anon/fdfsupload", //后台url data: form, cache: false, async: false, type: "POST", //类型,POST或者GET dataType: 'json', //数据返回类型,能够是xml、json等 processData: false, contentType: false, success: function (data) { //成功,回调函数 if (data.code == 200) { console.log(data.data); CHAT.chat("1","2","<img src='" + data.data + "' height='200' width='200' />",app.CHAT,null); } } }); }) }) </script> </body> </html>
用户id为2的代码以下
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> </head> <body> <div>发送消息:</div> <input type="text" id="msgContent" /> <input type="button" value="发送" onclick="CHAT.chat('2','1',msgContent.value,app.CHAT,null)" /> <input type="file" id="file" name="file"> <input type="button" id="button" value="发送图片" > <div>接受消息:</div> <div id="receiveMsg" style="background-color: gainsboro;"></div> <script type="application/javascript" src="js/app.js"></script> <script type="application/javascript" src="js/mui.min.js"></script> <script type="application/javascript" src="js/jquery-3.3.1.min.js"></script> <script type="application/javascript" src="js/google-protobuf/google-protobuf.js"></script> <script type="application/javascript" src="js/myproto_libs.js"></script> <script type="application/javascript"> window.CHAT = { socket: null, init: function() { if (window.WebSocket) { CHAT.socket = new WebSocket("ws://127.0.0.1:9999/ws"); CHAT.socket.binaryType = 'arraybuffer'; CHAT.socket.onopen = function() { console.log("链接创建成功"); CHAT.chat("2",null,null,app.CONNECT,null); //每次链接的时候获取未读消息 fetchUnReadMsg(); //定时发送心跳,30秒一次 setInterval("CHAT.keepalive()",30000); }, CHAT.socket.onclose = function() { console.log("链接关闭"); }, CHAT.socket.onerror = function() { console.log("发生错误"); }, CHAT.socket.onmessage = function(e) { var receiveMsg = document.getElementById("receiveMsg"); var html = receiveMsg.innerHTML; var chatMsg = proto.ChatMsg.deserializeBinary(e.data); console.log("senderId:" + chatMsg.getSenderid() + ",receiverId:" + chatMsg.getReceiverid() + ",msg:" + chatMsg.getMsg() + ",MsgId:" + chatMsg.getMsgid()); receiveMsg.innerHTML = html + "<br/>" + chatMsg.getMsg(); //消息签收 CHAT.chat(chatMsg.getReceiverid(),null,null,app.SIGNED,chatMsg.getMsgid()); } }else { alert("浏览器不支持WebSocket协议..."); } }, chat: function(senderId,receiverId,msg,action,extand) { // var chatMsg = new app.ChatMsg(senderId,receiverId,msg,null); var chatMsg = new proto.ChatMsg(); chatMsg.setSenderid(senderId); chatMsg.setReceiverid(receiverId); chatMsg.setMsg(msg); // var dataContent = new app.DataContent(action,chatMsg,extand); var dataContent = new proto.DataContent(); dataContent.setAction(action); dataContent.setChatmsg(chatMsg); dataContent.setExtand(extand); var bytes = dataContent.serializeBinary(); var intView = new Int8Array(bytes); CHAT.socket.send(intView); }, keepalive: function() { CHAT.chat("2",null,null,app.KEEPALIVE,null); fetchUnReadMsg(); } } CHAT.init(); function fetchUnReadMsg() { mui.ajax('http://127.0.0.1:8008/notification-anon/getunreadmeg?receiverid=2',{ data:{}, dataType:'json',//服务器返回json格式数据 type:'post',//HTTP请求类型 timeout:10000,//超时时间设置为10秒; success:function(data){ if (data.code == 200) { var contactList = data.data; var ids = ""; console.log(JSON.stringify(contactList)); var receiveMsg = document.getElementById("receiveMsg"); for (var i = 0;i < contactList.length;i++) { var msgObj = contactList[i]; var html = receiveMsg.innerHTML; receiveMsg.innerHTML = html + "<br/>" + msgObj.msg; ids = ids + msgObj.msgId + ","; } //批量签收未读消息 CHAT.chat("2",null,null,app.SIGNED,ids); } } }); } $(function () { $("#button").click(function () { var form = new FormData(); form.append("file", document.getElementById("file").files[0]); $.ajax({ url: "http://xxx.xxx.xxx.xxx:8010/files-anon/fdfsupload", //后台url data: form, cache: false, async: false, type: "POST", //类型,POST或者GET dataType: 'json', //数据返回类型,能够是xml、json等 processData: false, contentType: false, success: function (data) { //成功,回调函数 if (data.code == 200) { console.log(data.data); CHAT.chat("2","1","<img src='" + data.data + "' height='200' width='200' />",app.CHAT,null); } } }); }) }) </script> </body> </html>