Netty整合Protobuffer

如今咱們都知道,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>
相關文章
相關標籤/搜索