1. 程式人生 > >XMPP常用的協議(一)

XMPP常用的協議(一)

最近,在用XMPP協議封裝iOS和Android的IMSDK,整理了一下常用的協議內容。
其中包括上線、離線;新增好友、刪除好友、同意好友申請、拒絕好友申請、為好友設定備註;傳送訊息(文字、圖片、定位、語音);獲取群列表、建立群、配置群資訊、設定管理員、撤銷管理員、邀請人加群、踢人、退群、解散群等等。
使用各種庫版本是Openfire 4.1 、(iOS)XMPPFramework、(Android)Smack 4.1.4

1.上線

我們在登入成功後,要傳送一個上線的presence 訊息:

<presence>
  <status>我上線咯</status
>
<show>xa</show> </presence>

iOS 中的示例程式碼是:

- (void)goOnline
{
    // 傳送一個<presence/> 預設值avaliable 線上 伺服器收到空的presence 也會認為是avaliable
    // status ---自定義的內容,可以是任何的。
    // show 是固定的,有幾種型別 dnd、xa、away、chat,在方法XMPPPresence 的intShow中可以看到
    XMPPPresence *presence = [XMPPPresence presence];
    [presence addChild:[DDXMLNode elementWithName:@"status"
stringValue:@"我上線咯"]]; [presence addChild:[DDXMLNode elementWithName:@"show" stringValue:@"xa"]]; [self.stream sendElement:presence]; }

而Android 裡(用Smack4.1) 是這樣發的:

Presence presence = new Presence(Presence.Type.available);
presence.setMode(Presence.Mode.available);
dmConnection.sendStanza
(presence);

當然,Android 這裡少了<status><show> 這兩個節點,因為Android 裡新增節點比較麻煩,我們沒有狀態顯示需求,也就沒新增。

其中show的內容是固定的,只能是dndxaawaychat,類似於QQ的狀態:我在線上、離開、隱身、離線等。(可以在Demo裡設定後,另一端用Spark檢視效果)
而status 可以是任意的內容。

2. 離線

同樣的,當我們推出登入時,最好也發出一個離線的presence訊息:

<presence type="unavailable"/>

iOS 裡的是這樣的:

- (void)offline
{
    XMPPPresence *off = [XMPPPresence presenceWithType:@"unavailable"];
    [_stream sendElement:off];
}

Android 裡是這樣的:

Presence presence = new Presence(Presence.Type.unavailable);
dmConnection.sendStanza(presence);

3.新增好友

XMPP協議新增好友,其實就是A訂閱B,同時B也訂閱A的模式。
新增好友可以擴充套件幾個節點,放備註資訊。例如下面的extra 裡的 message 節點

<presence type="subscribe" to="[email protected]" id="3C1090D3-BF41-4CF3-8034-A6DFEACC118B">
  <extra xmlns="http://www.duimy.com/presence-extra">
    <message>請求加個好友唄</message>
    <timestamp>2017-06-20 17:56:26</timestamp>
  </extra>
</presence>

iOS裡的程式碼是這樣的:

    XMPPPresence* presence = [XMPPPresence presenceWithType:@"subscribe" to:[jid bareJID]];
    [presence addAttributeWithName:@"id" stringValue:[HLCoreManager manager].stream.generateUUID];
    //extra
    NSXMLElement *extra = [NSXMLElement elementWithName:@"extra" xmlns:ADDFRIEND_XMLNS];
    [presence addChild:extra];
    //verifyContent
    if (message.length > 0) {
        NSXMLElement *verifyContent = [NSXMLElement elementWithName:@"message"];
        [verifyContent setStringValue:message];
        [extra addChild:verifyContent];
    }
    //timestamp
    NSXMLElement *timestamp = [NSXMLElement elementWithName:@"timestamp"];
    NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
    dateFormat.dateFormat = @"YYYY-MM-dd HH:mm:ss";
    NSString *dateStr = [dateFormat stringFromDate:[NSDate date]];
    [timestamp setStringValue:dateStr];
    [extra addChild:timestamp];
    [self.stream sendElement:presence];

而Android 裡因為使用Smack 就很簡單了:

Presence presence = new Presence(Presence.Type.subscribe);
presence.setTo(user);
dmConnection.sendStanza(presence);

4.刪除好友

刪除好友其實就是取消訂閱,A取消訂閱B,B取消訂閱A。

<presence type="unsubscribe" to="[email protected]" id="F7129693-910C-4C0D-93C2-33845794FEF0"/>

iOS 裡的程式碼片段:

XMPPPresence *presence = [XMPPPresence presenceWithType:@"unsubscribe" to:[jid bareJID]];
[presence addAttributeWithName:@"id" stringValue:[HLCoreManager manager].stream.generateUUID];
[self.stream sendElement:presence];

Android 中的程式碼段:

Presence presence = new Presence(Presence.Type.unsubscribe);
presence.setTo(user);
dmConnection.sendStanza(presence);

4.1 收到對方取消訂閱

收到對方取消訂閱自己,自己這邊預設也要取消對方的訂閱
為了避免收到對方取消訂閱的死迴圈,取消訂閱使用iq來發訊息

<iq type="set">
  <query xmlns="jabber:iq:roster">
    <item jid="[email protected]" subscription="remove"/>
  </query>
</iq>

iOS 中的程式碼段:

//自動也取消訂閱對方,這裡的xmppRoster 就是XMPPRoster 的例項
[[HLCoreManager manager].xmppRoster removeUser:presence.from];

Android 中的程式碼段:

RosterEntry rosterEntry = roster.getEntry(presence.getFrom());
roster.removeEntry(rosterEntry);

5.同意對方的好友申請

同意對方的好友申請分為兩步:第一步,告訴對方,你訂閱成功了,傳送如下訊息:

<presence type="subscribed" to="[email protected]" id="F7129693-910C-4C0D-93C2-33845794FEF0"/>

第二步,傳送訂閱對方的訊息(即上面小節3)。

iOS 裡的程式碼段:

// 同意加好友的申請
- (void)agreeFriendRequestFrom:(NSString *)username completion:(CompletionBlock)completion
{
    XMPPJID *jid = [[HLCoreManager manager] jidWithUsername:username];

    XMPPPresence *presence = [XMPPPresence presenceWithType:@"subscribed" to:[jid bareJID]];
    [presence addAttributeWithName:@"id" stringValue:[HLCoreManager manager].stream.generateUUID];

    // 傳送已訂閱的資訊給對方
    [self sendElement:presence completion:nil];

    //並新增對方為好友
    [self addContact:username message:nil completion:^(NSString *username, NSError *error) {
        if (completion) {
            completion(username, error);
        }
    }];
}

Android 裡的程式碼段:

Presence presence = new Presence(Presence.Type.subscribed);
presence.setTo(user);
// 告訴對方,已訂閱成功
dmConnection.sendStanza(presence);
// 傳送一條訂閱對方的訊息
Presence presence2 = new Presence(Presence.Type.subscribe);
presence2.setTo(user);
dmConnection.sendStanza(presence);

5.1 收到對方訂閱自己的訊息

收到別人訂閱自己的訊息時,分兩種情況:
如果是別人發出的申請,那麼需要將這個申請的訊息傳遞出去,讓使用者覺得是同意還是拒絕。
如果是自己發起的,別人訂閱自己其實是同意操作,則我們只需要給對方發一條訂閱成功的訊息即可。(防止無限訂閱的死迴圈)

iOS裡的程式碼段:

    if ([presence.type isEqualToString:@"subscribe"]) {
        XMPPUserMemoryStorageObject *userObject = [[HLCoreManager manager].xmppRosterMemoryStorage userForJID:presence.from];
        NSDictionary *userDict = [userObject valueForKey:@"itemAttributes"];
        if (userDict && [@"to" isEqualToString:userDict[@"subscription"]]) { //如果訂閱狀態是to,說明是我傳送給對方的好友申請,對方同意並新增我為好友
            XMPPPresence *replyPresence = [XMPPPresence presenceWithType:@"subscribed" to:presence.from];
            [replyPresence addAttributeWithName:@"id" stringValue:[HLCoreManager manager].stream.generateUUID];
            [self sendElement:replyPresence completion:nil];
            return;
        }

        NSXMLElement *extra = [presence elementForName:@"extra"];

        NSString *content = [[extra elementForName:@"message"] stringValue];
        NSString *time = [[extra elementForName:@"timestamp"] stringValue];

        NSMutableDictionary *dict = [NSMutableDictionary dictionary];
        if (content.length > 0) {
            [dict setObject:content forKey:@"message"];
        }

        if (time.length > 0) {
            [dict setObject:time forKey:@"time"];
        }

        if (presence.from.user > 0) {
            [dict setObject:presence.from.user forKey:@"jid"];
        }
        // 通過多播代理,把訊息傳出去
        [self.multicastDelegate friendRequestDidReceiveFromUser:presence.from.user message:content];
    }

Android 裡的程式碼段:

if (presence.getType() == Presence.Type.subscribe) { // 對方請求新增好友
    RosterEntry entry = roster.getEntry(presence.getFrom());
    if (entry != null && entry.getType() == RosterPacket.ItemType.to) {
        // 如果是自己新增對方為好友,收到對方的訂閱資訊
        Presence replyPresence = new Presence(Presence.Type.subscribed);
        replyPresence.setTo(presence.getFrom());
        dmCore.getConnection().sendStanza(replyPresence);
        return;
    }
    InputStream inputStream = new ByteArrayInputStream(presence.toString().getBytes());
    String message = XMLParse(inputStream, "message");
    for (DMContactListener contactListener : contactListeners) {                                  contactListener.onFriendRequestReceived(presence.getFrom(), message);
    }
}

6.拒絕好友申請

<presence type="unsubscribed" to="[email protected]" id="F7129693-910C-4C0D-93C2-33845794FEF0"/>

iOS 裡的程式碼段:

// 拒絕加好友的申請
- (void)rejectFriendRequestFrom:(NSString *)username completion:(CompletionBlock)completion
{
    XMPPJID *jid = [[HLCoreManager manager] jidWithUsername:username];
    // 先告訴對方,我不然讓你訂閱
    XMPPPresence *presence = [XMPPPresence presenceWithType:@"unsubscribed" to:[jid bareJID]];
    [presence addAttributeWithName:@"id" stringValue:[HLCoreManager manager].stream.generateUUID];

    [self sendElement:presence completion:^(XMPPElement *element, NSError *error) {
        if (completion) {
            completion(username, error);
        }
    }];
    // 從好友關係裡刪除
    [[HLCoreManager manager].xmppRoster removeUser:presence.from];
}

Android 裡的程式碼段:

Presence presence = new Presence(Presence.Type.unsubscribed);
presence.setTo(user);
dmCore.getConnection().sendStanza(presence);
RosterEntry rosterEntry = roster.getEntry(presence.getFrom());
if (rosterEntry != null) {
      roster.removeEntry(rosterEntry);
}

7.為好友設定備註

<iq type="set">
       <query xmlns="jabber:iq:roster">
         <item jid="bareJID" name="nickname"/>
       </query>
     </iq>

iOS裡的程式碼段:

// 為好友設定備註
- (void)setNickName:(NSString *)nickName forUser:(NSString *)username completion:(CompletionBlock)completion
{
    XMPPJID *jid = [[HLCoreManager manager] jidWithUsername:username];
    NSXMLElement *item = [NSXMLElement elementWithName:@"item"];
    [item addAttributeWithName:@"jid" stringValue:[jid bare]];
    [item addAttributeWithName:@"name" stringValue:nickName];

    NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:roster"];
    [query addChild:item];

    XMPPIQ *iq = [XMPPIQ iqWithType:@"set"];
    [iq addAttributeWithName:@"id" stringValue:[HLCoreManager manager].stream.generateUUID];
    [iq addChild:query];

    [self sendElement:iq completion:^(XMPPElement *element, NSError *error) {
        if (completion) {
            completion(username, error);
        }
    }];
}