文章做者:monkeyHijava
本文是 聲網 Agora 開發者的投稿。若有疑問,歡迎與做者交流。linux
社會高度發展的今天,你們都離不開社交和社交網絡。近幾年,直播行業的穩定高速發展,背後隱藏一個事實,你們須要一個實時性更高的互聯網環境,就像面對面溝通那樣的及時有效。git
此次嘗試了一下 Agora SignalingSDK。github
Agora Signaling 是Agora 全家桶一員,主要用來實現即時點對點通訊。Agora Signalling 是做爲插件的形式服務於 Agora 全家桶,也能夠單獨用於實時消息通訊的場景。算法
Agora 官網已經提供了比較完善的文檔資料。數據庫
以 Agroa Signaling 爲例,咱們能夠看到官網分別就客戶端集成和服務端集成進行了介紹,而客戶端部分又針對常見客戶端實現進行的清晰簡單的講解。windows
擁有必定開發經驗的攻城獅很快便能上手。api
固然咱們也發現一個問題,文檔上只有 quick start, 沒有進一步介紹接口使用的注意事項。帶着這個疑惑,筆者迅速瀏覽了API參考部分,全部接口都沒有提供具體的demo code 和注意事項。基本接入思路是這樣的:數組
初始化緩存
登陸
點對點消息
頻道消息
呼叫邀請
註銷
Agroa 官網提供了關於 Agora 信令的各類demo,初略瀏覽一番,比較容易看懂,沒有什麼很奇怪的寫法。
可是,這些demo都有一個問題,沒有註釋。這對未曾接觸Agora產品的新手不是特別友好,可能要花比較多精力來熟悉這些接口。
經過Agora 官網及已經公佈的API 。咱們能夠了解到,常見帶身份信息的文本聊天徹底不在話下,基於Agora Signaling的demo,咱們只要關心一下本身的業務模型,端上套個皮就能實現聊天室、留言板等互動交流場景。
直播間的彈幕聊天
直播間聊天和彈幕聊天,本質上就是一個留言板和即時通信的合體。而Agora 信令 自己就是爲實時通訊互動而生,實現這樣的功能只要加一個聊天數據庫來保留歷史記錄便可。
醫患遠程診斷
現實生活中,受距離、時間、心理等諸多因素影響,病患並不必定能及時到達醫院,醫生也未必能及時到達現場,這時候及時通信網絡能夠提供諸多方便。病患或病患家眷能夠經過一個App 將患情經過影像、聲音、文字傳遞給醫生,同時能夠隨時的溝通,就像現場問診同樣,病患可能也須要一個病友羣或頻道來分享交流。
消息通知
相信你們對手機短信、微信消息、qq消息都不陌生,咱們藉助 Agora 信令 也是能夠實現簡單版本的網絡短信功能的。
客服功能
有些產品可能須要一個客服功能,這樣遇到使用問題時,能夠隨時經過聊天窗口諮詢,並且不須要額外的添加客服人員的微信。有效溝通,同時保護彼此隱私。
實時性比較高的設備間通訊
好比我在A省有一批礦機,須要及時的瞭解機房情況,那麼我在機房能夠設置一個通訊機,將採集到的數據經過 Agora 信令 及時傳回並記錄在數據庫。雖然這個場景可能並非Agora 信令 設計初衷,但做爲一種可行的備選也是不錯的。
課堂在線互動
各類在線學堂的遠程授課方案,包括遠程考試等,課堂互動可不侷限於文字、語音、圖像,一般要結合起來。
直播導購互動
若是有這樣一種直播活動,畫面上和電視導購沒什麼區別,可是能夠經過更方便的方式下單,掃碼,溝通,填寫信息,付款,獲取訂單狀態,以及端上的現場互動等。
科研領域
須要遠程採集觀測的各類數據等。實驗展現等。實驗數據實時採集處理等。
幾乎能想到的任何須要實時通訊、點對點通訊、或者分頻道通訊的場景,都嘗試着去實現。
在實際作本身的應用以前,我先上手跑了一下官方的 demo,開啓踩坑之旅。
筆者體驗環境:
解壓 SDK,獲得以下目錄結構,咱們後續會基於其中的samples : Agora-Signaling-Turorial-Java 來學習和理解server端SDK和api。
└─Agora_Signaling_Server_SDK_Java // SDK根目錄
├─lib // 信令的jar包
├─libs-dep // 行令依賴的jar包
└─samples // 一個栗子
└─Agora-Signaling-Tutorial-Java
├─gradle // 由此能夠判斷時gradle項目
│ └─wrapper
├─lib // 這裏已經又所有須要的jar包了,須要用SDK中 lib、libs中的jar包覆蓋
└─src
└─main
└─java
├─mainclass
├─model
└─tool
複製代碼
前面咱們簡單預覽SDK目錄,一個gradle項目。很是容易導入idea。這裏就以idea搭建demo運行環境。
1.進入 Agora-Signaling-Tutorial-Java 2.右鍵--> Open Folder as InterlJ Idea project 3.等待導入完成,一般都很快
1.配置SDK
確保SDK目錄下的lib、libs-dep 中的全部jar包到項目的lib目錄下。
2.查看並修改build.gradle,要注意其中第14行
dir: 'lib', include: ['* .jar']
複製代碼
修改成:
dir: 'lib', include: ['*.jar']
複製代碼
星號*後沒有空格。修改後的build.gradle:
group 'com.agora'
version '1.0-SNAPSHOT'
apply plugin: 'java'
sourceCompatibility = 1.5
repositories {
jcenter()
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.11'
compile fileTree (dir: 'lib', include: ['*.jar'])
}
複製代碼
gradle配置發生變化時,idea提示 import Changes ,點一下 import Changes .
確保gradle成功引入了依賴jar包。
3.配置appid
tip: 這裏須要注意, agora 有兩種鑑權機制。直接用appid,或者使用token。爲方便演示,咱們直接用appid完成鑑權,可是,筆者也同時搬來了java的token算法。具體看 第 4 步介紹。
切換到 Pancages 視角,找到 tool/COnstant,注意 8 ~ 11 行 ,
static {
//app_ids.add("Your_appId");
//app_ids.add("Your_appId");
}
複製代碼
這裏咱們取消一行註釋, 替換其中的Your_appId 爲真實的appid。
static {
//app_ids.add("Your_appId");
app_ids.add("");
}
複製代碼
4.計算token
tips: 只有在開啓app認證時,纔會用到token。這裏方便演示,筆者決定暫時不開啓app認證。筆者僅僅模仿並貼出相關代碼
具體實現:
package tool;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class SignalingToken {
public static String getToken(String appId, String certificate, String account, int expiredTsInSeconds) throws NoSuchAlgorithmException {
StringBuilder digest_String = new StringBuilder().append(account).append(appId).append(certificate).append(expiredTsInSeconds);
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(digest_String.toString().getBytes());
byte[] output = md5.digest();
String token = hexlify(output);
String token_String = new StringBuilder().append("1").append(":").append(appId).append(":").append(expiredTsInSeconds).append(":").append(token).toString();
return token_String;
}
public static String hexlify(byte[] data) {
char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
char[] toDigits = DIGITS_LOWER;
int l = data.length;
char[] out = new char[l << 1];
// two characters form the hex value.
for (int i = 0, j = 0; i < l; i++) {
out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
out[j++] = toDigits[0x0F & data[i]];
}
return String.valueOf(out);
}
}
複製代碼
更具體的能夠參考 java版token算法實現
關於鑑權機制及算法 詳情見
1.在啓動前,有必要來一塊兒看看 mainclass目錄。
啓動類有兩個, 一個是啓動點對點通訊server的, 另外一個是頻道消息。
怎麼理解呢,其實很簡單,點對點通訊,你能夠理解爲倆人竊竊私語。頻道通訊則是羣聊(像微信羣)。
└─src
└─main
└─java
├─mainclass
│ MulteSignalObjectMain2.java // 頻道消息 啓動類
│ SingleSignalObjectMain.java // 點對點通訊 啓動類
│ WorkerThread.java // 核心業務流程
複製代碼
2.嘗試通訊
a.啓動
選中 SingleSignalObjectMain.java --> ctrl + shift + f10
b.輸入本身的accoutrun 選項卡中已經提示你輸入 account ,咱們隨便輸入一個 Roman
後續能夠嘗試本身實現用戶中心
c.選擇模式併發送消息
而後, 會看到提示 successd
這裏,先一塊兒試試 點對點通訊 ,輸入 2 ,回車
咱們輸入聊天的對象,hello
順便開個linux虛擬機運行linux客戶端demo互相發消息
這裏比較奇怪,demo可能有些功能業務省略掉了,java端能夠發點對點消息,卻收不到。
嘗試發頻道消息,發現羣聊頻道模式徹底沒問題。
3.小結
啓動demo沒有什麼難度,不過demo裏的業務怎麼樣,須要你們花些心思來學習。
demo跑起來了,可是咱們並非很明白這個程序具體業務。換本身來寫,可能仍是一臉懵。因此,筆者決定review code,學習一下SDK用法。
文件src\main\java\tool\Constant.java中大部分寫死的和預約義的參數值都在這裏
package tool;
import java.util.ArrayList;
public class Constant {
public static int CURRENT_APPID = 0;
public static ArrayList<String> app_ids = new ArrayList();
// 申明一些 命令,這些命令一般都是些常量
public static String COMMAND_LOGOUT;
public static String COMMAND_LEAVE_CHART;
public static String COMMAND_TYPE_SINGLE_POINT;
public static String COMMAND_TYPE_CHANNEL;
public static String RECORD_FILE_P2P;
public static String RECORD_FILE_CHANEEL;
public static int TIMEOUT;
public static String COMMAND_CREATE_SIGNAL;
public static String COMMAND_CREATE_ACCOUNT;
public static String COMMAND_SINGLE_SIGNAL_OBJECT;
public static String COMMAND_MULTI_SIGNAL_OBJECT;
public Constant() {
}
static {
// 前面聲明的變量名,這裏複製
// app_ids 是數組格式的,意味你能夠添加多個appid
app_ids.add("073e6cb4f3404d4ba9ad454c6760ec0b");
// 一些命令 定義
// 退出登錄
COMMAND_LOGOUT = "logout";
// 離開當前聊天繪畫
COMMAND_LEAVE_CHART = "leave";
// 私聊模式輸入2
COMMAND_TYPE_SINGLE_POINT = "2";
// 羣聊模式輸入3
COMMAND_TYPE_CHANNEL = "3";
// 緩存文件定義
RECORD_FILE_P2P = "test_p2p.tmp";
RECORD_FILE_CHANEEL = "test_channel.tmp";
// 超時
TIMEOUT = 20000;
// 新建 一個signal
COMMAND_CREATE_SIGNAL = "0";
// 新建一個用戶
COMMAND_CREATE_ACCOUNT = "1";
// 進入點對點模式
COMMAND_SINGLE_SIGNAL_OBJECT = "0";
// 進入頻道羣聊模式
COMMAND_MULTI_SIGNAL_OBJECT = "1";
}
}
複製代碼
以 點對點 爲例:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package mainclass;
import tool.Constant;
// 一個點對點啓動類
public class SingleSignalObjectMain {
// 構造方法
public SingleSignalObjectMain() {
}
// main 方法接受 字符串數組做爲參數
public static void main(String[] args) {
// new 一個workerThread ,核心業務都在workerThread 類中
WorkerThread workerThread = new WorkerThread(Constant.COMMAND_SINGLE_SIGNAL_OBJECT);
// 啓動這個workerThread 線程。
(new Thread(workerThread)).start();
}
}
複製代碼
model目錄中定義了一些數據類和類方法,比較容易理解。
main/java/mainclass/WorkerThread.java文件裏定義了一個線程類,繼承Runable。
限於篇幅,這裏摘部分代碼出來解讀一下。
首先, WorkerThread類中定義:
private boolean mainThreadStatus = false; // 主線程狀態 默認false
private String token = "_no_need_token"; // 默認未開啓token認證,而是直接使用appid
private String currentUser; // 當前會話用戶
private boolean timeOutFlag; // 超時標記,是否超時
private DialogueStatus currentStatus; // 當前消息狀態
private HashMap<String, User> users; // 用戶表
private HashMap<String, List<DialogueRecord>> accountDialogueRecords = null; // 帳號會話記錄
private HashMap<String, List<DialogueRecord>> channelDialogueRecords = null; // 頻道會話記錄
List<DialogueRecord> currentAccountDialogueRecords = null; // 當前帳號會話記錄
List<DialogueRecord> currentChannelDialogueRecords = null; // 當前頻道會話記錄
複製代碼
重點看一下構造方法
public WorkerThread(String mode) {
currentMode = mode; //傳入mode
init(); // 初始化
String appid = Constant.app_ids.get(0); // 獲取配置文件的裏的app_id
// 若是傳入mode值等於COMMAND_SINGLE_SIGNAL_OBJECT的值(點對點),用appid new 一個信令,更新會話狀態爲爲登錄狀態
// 不然判斷是否爲頻道模式,更新狀態。 這裏,你們能夠根據本身狀況修改邏輯。
// 這裏有個疑問,兩個分支裏,爲啥一個須要 new Signal 一個不須要呢?
if (currentMode.equals(Constant.COMMAND_SINGLE_SIGNAL_OBJECT)) {
sig = new Signal(appid);
currentStatus = DialogueStatus.UNLOGIN;
} else {
if (currentMode.equals(Constant.COMMAND_MULTI_SIGNAL_OBJECT)) {
currentStatus = DialogueStatus.SIGNALINSTANCE;
}
}
}
複製代碼
init() function
則初始化一個必要的須要交互輸入來初始化的數據
run() function
會根據currentStatus的值來調用不一樣的業務函數
makeSignal()
中很是關鍵的一步
Signal signal = new Signal(appId); //用id實例化信令
複製代碼
joinChannel(String channelName)中用到LoginSession類和Channel類
public void joinChannel(String channelName) {
final CountDownLatch channelJoindLatch = new CountDownLatch(1);
// 實例化Channel 類 ,裏面override幾個事件監聽
Channel channel = users.get(currentUser).getSession().channelJoin(channelName, new Signal.ChannelCallback() {
// 當加入頻道時
@Override
public void onChannelJoined(Signal.LoginSession session, Signal.LoginSession.Channel channel) {
channelJoindLatch.countDown();
}
// 頻道用戶列表發生變化時
@Override
public void onChannelUserList(Signal.LoginSession session, Signal.LoginSession.Channel channel, List<String> users, List<Integer> uids) {
}
// 收到頻道消息時
@Override
public void onMessageChannelReceive(Signal.LoginSession session, Signal.LoginSession.Channel channel, String account, int uid, String msg) {
if (currentChannelDialogueRecords != null && currentStatus == DialogueStatus.CHANNEL) {
PrintToScreen.printToScreenLine(account + ":" + msg);
DialogueRecord dialogueRecord = new DialogueRecord(account, msg, new Date());
currentChannelDialogueRecords.add(dialogueRecord);
}
}
// 當頻道用戶加入會話時
@Override
public void onChannelUserJoined(Signal.LoginSession session, Signal.LoginSession.Channel channel, String account, int uid) {
if (currentStatus == DialogueStatus.CHANNEL) {
PrintToScreen.printToScreenLine("..." + account + " joined channel... ");
}
}
@Override
public void onChannelUserLeaved(Signal.LoginSession session, Signal.LoginSession.Channel channel, String account, int uid) {
if (currentStatus == DialogueStatus.CHANNEL) {
PrintToScreen.printToScreenLine("..." + account + " leave channel... ");
}
}
@Override
public void onChannelLeaved(Signal.LoginSession session, Signal.LoginSession.Channel channel, int ecode) {
if (currentStatus == DialogueStatus.CHANNEL) {
currentStatus = DialogueStatus.LOGINED;
}
}
});
timeOutFlag = false;
wait_time(channelJoindLatch, Constant.TIMEOUT, channelName);
if (timeOutFlag == false) {
// 未超時,加入頻道
users.get(currentUser).setChannel(channel);
}
}
複製代碼
這裏篇幅有限,不能貼出所有代碼。你們能夠對着api文檔來 着重看一下如何認證,如何登錄,如何收發消息。
後續,筆者會上傳註釋過的到github。
1.demo的build.gradle 中多了一個空格,致使提示找不到lib
解決方法: * .jar --> *.jar
2.實例化signal時失敗
解決方法: 檢查appid是否正確,檢查是否開啓了token認證
若是開啓了token認證,須要增長token計算算法,能夠參考這個文檔。
3.筆者發現兩個啓動類雖然默認啓動命令值不同,可是其實啓動效果同樣,均可以選擇切換p2p或者channel模式。