如今咱們都知道,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>