【Tech-IM】IM类型app开发浅谈(二)---聊天中的消息协议的定义与客户端数据库设计

<strong> <span style="font-size:14px;"> </span><span style="font-size:18px;">聊天中的消息协议的定义</span></strong>

   我们这里谈及的消息,都是意在指socket推送的消息,所以消息协议的定义,更形象地说可以是socket数据的协议。类似于微信客户度那样,哪一个模块有消息更新,那个模块上面就有个小红点来提示用户:hey,guys,You have got the new messages,click me。所以在服务器推送过来的消息,必须要让客户端清晰的解说出来----这条消息是属于哪个模块下哪个点的。因此我们要为每一条推送过来的消息带上一个功能Id,称之为:functionId

  在定义一条消息主要数据协议之前,我们先来说说消息由可能出现的分包与丢包。socket的数据传输层是很脆弱,而且TCP对于传输的数据的大小是由限制的,并且默认情况下TCP会对包体过大的包自动进行分包传输,这种分包机制可以查阅TCP的相关协议。由于这样的一个机制在这里,我们客户端socket接收一个包体的时候,并不知道这个包体是否完整的,也许是分包中的一个,所以如何去面对这种机制呢?因此另一个数据协议中关键字段出现了:datalength.每条消息由服务器端推送过来,都会在二进制的消息头上先带上datalength的大小,占用二进制的数据体前四位。如下伪代码:

  

	/**
	 * 解socket数据包体
	 * @param ips
	 * @return
	 * @throws IOException 
	 */
	public static String getDataBody(InputStream ips) throws IOException {
		String dataBody = null;
		// 获取头部
		byte[] head = getData(ips, 4);
		int dataLength = ByteUtil.toInt(head);
		// 获取数据
		byte[] data = getData(ips, dataLength);
		dataBody = GZipUtil.uncompressToString(data);
		return dataBody;
	}

    由以上代码可以知道,我们的一条消息体应该是这样的:

   

    所以,在这个头信息中,我们获取了一条消息的完整长度,那么接下来就是,在流中完整地读取数据,读取的数据长度理应与前面的datalength值一致,如果流完了,而且读取的长度比datalength小,视为丢包,不需要为此消息存入本地数据库,同样地不需要对这条消息进行签收。由于这部分读流的代码涉及公司的加密内容,所以在此就不贴了,原理如上,可以自行实现了。

    消息协议为了应对丢包与分包的情况,加入了datalength,因此我们可以把消息分为两部分:1.消息头。2.消息主干。那么具体的数据内容我们怎么去定义才符合一个聊天对话消息呢?前面提及到functionID,我们希望我们服务器推送过来的消息可以告诉我们属于哪一模块的消息,所以这个functionID应该会先被解析出来,这个functionID解析出来之后,我们才根据这个functionID来把消息的内容放到不同的模块解析器里面进行解析。同样地,为了服务器端对消息更好地管理和入库及缓存,必须有一个id值来标示这条消息,所以基本一个消息主干首先包含两个值:functionId,messageId。我们采用Json数据格式进行数据交互,所以格式首先如下:

<pre name="code" class="java">{functionId:1,messageId:"256",data:{.......}}

 
 

     因此data中的内容,才是整条消息的主要内容,它包含了聊天内容,聊天对象,又或者所需更新的模块的更新信息。

     以下以聊天消息为例,我们定义一下data格式。

    【Tech-IM】IM类型app开发浅谈(二)---聊天中的消息协议的定义与客户端数据库设计_第1张图片

     上图这个聊天界面可以看出,我们只关心两个点:聊天对象,聊天内容。 何为聊天对象?这里再作一个延伸,聊天中包含双方,AToB,or BToA,要么是A发送消息给B,要么B发送消息给A。所以这个A,B对象理应在一条消息格式里面。如果我们用targetId来用作To中的右方,那么我们就用userId用作To中的左方。就是说,userId为一条消息的发起者,targetId为一条消息的接受者。因此简单的格式模型为:

<pre name="code" class="java"><pre name="code" class="java">{“targetId”:"1024","userId":"1023","content":"么么哒"}

 
 
       
 

     这条消息就是1023的用户向1024的用户发送了内容为”么么哒“的消息。

     客户端收到这条消息的时候,可以判断,如果targetId为当前用户登录的id,则这个用户是在接收消息,消息发自userId为1023的用户。可能有人会问,那么群聊的时候呢?很明显,群聊的时候targetId就为当前的群id。所以这条消息中还需要一个字段:聊天消息类型msgtype。那么聊天消息的格式模型为:

<pre name="code" class="java">{“targetId”:"1024","userId":"1023","content":"么么哒","msgtype":1}

当然由这个基础格式模型可以继续扩展为你想要的数据格式,例如聊天中可能出现图片,那么可以在设置一个pic字段,放置一个图片的url.如果是一个文件,如果是一个影片等。

 
 
 
 
    结合上文,最终的消息协议模型如下:
    
<pre name="code" class="java">{functionId:1,messageId:"256",data:{"targetid":"1024","userId":"1023","msgtype":1,"content":"么么哒",.......}}

 
 
 
     客户端数据库设计 
 

    

    

    由以上消息协议模型可知,以下为两天聊天信息,一条为他人(userid = 1023)发给我,一条为我(userid = 1024)发出。

    

{functionId:1,messageId:"278",data:{"targetid":"1024","userId":"1023","msgtype":1,"content":"吃饭了吗?",.......}}
 
 
{functionId:1,messageId:"279",data:{"targetid":"1023","userId":"1024","msgtype":1,"content":"还没有呢?在码字",.......}}


   当我们发送消息的时候,userId就为自己的当前账号的id,当我们收消息的时候,targetid为我自己当前账号id,如果此时,我们直接把这两天消息存入数据库,有问题吗?肯定有问题啦!这两条消息应该是关联在一起的,何解?看下图:

   【Tech-IM】IM类型app开发浅谈(二)---聊天中的消息协议的定义与客户端数据库设计_第2张图片

      (图片来源于网络)

      这个是会话列表,每一个对话对应一列,因此我们还需要给每个对话分配一个会话Id,用这个会话Id让消息自动分组。因为一个聊天记录表里面就应该保存全部的聊天消息,你不应该为每个一个会话创建一个数据表吧。所以我们要定义这个会话id。会话id其实就是你和谁的对话。这个谁,就可以是一个会话id了。当然如果你使用这个作为会话id,那么你必须确定这个id是唯一的,不能和之后的群聊id,公从号id或者其他服务可能出现在会话列表中的id有冲突。

      因此在数据库录入之前,需要为这条消息加入多一字段,sessionid。在会话列表这个界面,我们就可以通过分组,然后获取分组中最新的一条消息然后排列,这样的数据库操作,获取到会话列表。

     


    上图的relatedId就可以视为sessionId.上面的sql语句可以看出,先把根据会话id分组,再在分组中获取最新的会话消息,然后再把这条消息拿出来排序,就成了一个会话列表。因为会话列表中,每个会话都会消息一则最新的会话消息,和时间,所以需要经过分组获取,并且这样的排序。

     以上就是消息定义与消息数据库的其中几点,希望一些做过类似IM的同行给指点一下,也可给其他人参考一下。

      

     

你可能感兴趣的:(聊天消息协议,消息本地数据库,即时聊天App开发)