Java XMPP负载测试工具
在本文中,我們將開發用Java編寫的XMPP負載測試工具。
目錄
1.簡介 2. XMPP負載測試工具 3.先決條件 4. LoadXmppTest Java程序1.簡介
可擴展消息傳遞和狀態協議 ( XMPP )是基于XML(可擴展標記語言)的面向消息的中間件的通信協議。 它是由Internet工程任務組 (IETF) 標準化并由XMPP標準基金會 (XSF)支持和擴展的開放協議。 XMPP在開放標準中定義,并使用開放系統的開發和應用方法。 因此,許多服務器,客戶端和庫的實現都以自由和開源軟件的形式分發。 XMPP擴展協議 (XEP)中還定義了許多擴展。
IgniteRealtime發行的一種免費的開源發行版提供了以下實現:
- Openfire聊天服務器
- Spark聊天客戶端
- XMPP協議的Smack Java庫
Spark是類似于Messenger,What's app,Viber或Google Talk的聊天客戶端應用程序(實際上后者使用XMPP協議)。 一個人可以發送聊天消息,文件作為附件等。這些消息被發送到Openfire服務器,然后由其負責將它們傳遞到目的地,該服務器可以是直接與其連接的另一個Spark(或其他)聊天客戶端,也可以是另一個Openfire。實例(聯盟),直到他們到達最終目的地。
但是,服務器和客戶端在負載下的性能如何,即當它們必須處理許多聊天消息或許多文件傳輸時?
2. XMPP負載測試工具
存在許多解決方案來對XMPP服務器(例如Openfire)進行負載/壓力測試(列表并不詳盡):
- 帶有XMPP協議支持插件的Apache JMeter(請參閱[1,2])
- iksemel XMPP C庫
- Tsung ,一種開源的多協議分布式負載測試工具
在本文中,我們將使用Smack XMPP庫編寫Java XMPP負載測試工具。
3.先決條件
您需要在系統上下載并安裝Openfire 。 要嘗試我們的負載測試工具,使用嵌入式數據庫就足夠了,即使您需要記住嵌入式數據庫(HSQLDB)將在一段時間后填滿。 當然,推薦使用真實的RDBMS。
您必須創建許多用戶來模擬用戶消息交換負載。 在我們的示例中,我們將創建50個用戶名user001到user050用戶, user050所有用戶的密碼都相同,即a 。 如果您不知道該怎么做,請登錄管理控制臺(例如http:// localhost:9090或https:// localhost:9091 ),然后單擊“ 用戶/組”標簽; 在那里,您可以單擊創建新用戶來創建用戶。
由于創建大量用戶非常繁瑣,因此有幾個插件可以節省您的時間。 單擊Openfire管理控制臺的“ 插件”標簽,然后單擊“ 可用插件”并安裝“ 用戶創建”和/或“ 用戶導入/導出”插件。 如果現在單擊返回到“ 用戶/組”選項卡,您將看到已創建新鏈接; 用戶創建 (由于用戶創建插件)和導入和導出 (由于用戶導入/導出插件)。 剩下的練習是找出它們如何工作。
但是,這些并不是唯一需要做的更改。 在最新版本的Openfire中,安全機制已更改,因此,要使我們的程序正常運行,我們需要定義兩個屬性。 單擊服務器選項卡, 服務器管理器->系統屬性,然后在頁面底部輸入以下屬性名稱/值對:
| sasl.mechs.00001 | PLAIN |
| sasl.mechs.00002 | DIGEST-MD5 |
4. LoadXmppTest Java程序
我們將創建的工具是一個使用smack庫的Java程序。 它提供了命令行界面(CLI),但是如果發現有用,則可以為其編寫圖形用戶界面(GUI)。
$ java -Djava.util.logging.config.file=logging.properties -jar loadxmpptest.jar Required options: s, d, p, n usage: java -Djava.util.logging.config.file=logging.properties –jar loadxmpptest.jar -a,--attachment Test attachments -b,--big Test big attachments or messages -d,--domain Domain -n,--number Number of users -o,--observer Observer -p,--password Password -s,--server Server Usage : java -Djava.util.logging.config.file=logging.properties -jar loadxmpptest.jar -s -d -p -n [-o ] [-a] [-b]jabber id : userXXX@chatroom : roomXXX@conference.observer : userXXX@/Spark (just to test)10 users per chatroom5 chatrooms Use:-a to test small attachments (file transfers) or-a -b to test big attachments (file transfers) or:-b to test long messages此外, loadxmpptest.properties允許進一步配置測試應用程序:
SHORT_MESSAGES_DELAY_SECONDS = 100 LONG_MESSAGES_DELAY_SECONDS = 60 SMALL_ATTACHMENTS_DELAY_MINUTES = 1 BIG_ATTACHMENTS_DELAY_MINUTES = 5 DELAY_TO_SEND_MESSAGES_MILLISECONDS = 1000 BIG_FILE_NAME_PATH=blob.txt SMALL_FILE_NAME_PATH=test.txt日志存儲在log/loadxmpptest.log ,可以通過編輯logging.properties進行配置。
這是一個執行示例,其中服務器為localhost ,域為localhost (可以是其他名稱),使用相同的密碼模擬了50個用戶a :
java -Djava.util.logging.config.file=logging.properties -jar loadxmpptest.jar -s localhost -d localhost -p a -n 50另一個例子,這次發送大型附件:
java -Djava.util.logging.config.file=logging.properties -jar loadxmpptest.jar -s localhost -d localhost -p a -n 50 -ba如上所述,要在loadxmpptest.properties中配置要發送的文件。
4.1創建一個新的Maven項目
跳轉到您喜歡的IDE并創建一個新的Maven項目。 將以下依賴項添加到pom.xml :
<dependencies><dependency><groupId>org.igniterealtime.smack</groupId><artifactId>smack-core</artifactId><version>4.3.4</version></dependency><dependency><groupId>org.igniterealtime.smack</groupId><artifactId>smack-tcp</artifactId><version>4.3.4</version></dependency><dependency><groupId>org.igniterealtime.smack</groupId><artifactId>smack-im</artifactId><version>4.3.4</version></dependency> <dependency><groupId>org.igniterealtime.smack</groupId><artifactId>smack-extensions</artifactId><version>4.3.4</version></dependency><dependency><groupId>org.igniterealtime.smack</groupId><artifactId>smack-java7</artifactId><version>4.3.4</version></dependency><dependency><groupId>org.igniterealtime.smack</groupId><artifactId>smack-debug</artifactId><version>4.3.4</version></dependency><dependency><groupId>commons-cli</groupId><artifactId>commons-cli</artifactId><version>1.4</version></dependency> </dependencies>這些是撰寫本文時的最新版本,但是您可以使用在Maven Central中可能找到的最新版本。
4.2創建主類
該程序由基于[10]的兩個類組成。 XmppLoadTest包含main()方法,并委托XmppManager來完成工作(與現實相反,因為規范是經理委托而不是實際進行工作:))。
public static void main(String[] args) throws Exception {parseCLIArguments(args);final XmppLoadTest loadXmppTest = new XmppLoadTest();loadProperties(PROPERTIES_FILE);init(loadXmppTest);performLoad(loadXmppTest); }我將跳過parseCLIArguments()方法的描述。 它使用Apache Commons CLI庫來解析命令行參數(請參見[4])。 您可以根據需要選擇其他任何CLI庫或創建GUI。
我們的測試模擬了50個用戶和5個聊天室(您可以模擬自己的方案來滿足您的需求)。 這些存儲在:
private static final List<User> users = new ArrayList< >(numberOfUsers); private static final List<ChatRoom> chatRooms = new ArrayList< >(numberOfRooms);類User和ChatRoom的定義如下:
/*** User (e.g. {@code user001}). Functionality delegated to @{see* XmppManager}.*/ final class User {private final String username;private final String password;private final String domain;private final XmppManager xmppManager; // delegate to itprivate MultiUserChat joinedChatRoom;public User(String username, String password, String domain, XmppManager xmppManager) {this.username = username;this.password = password;this.domain = domain;this.xmppManager = xmppManager;}public String getUsername() { return username; }public String getPassword() { return password; }public String getJabberID() { return username + "@" + domain; }public void connect() {xmppManager.connect();}public void disconnect() {xmppManager.destroy();LOG.info("User " + username + " disconnected.");}public void login() {xmppManager.login(username, password);}public void setStatus(boolean available, String status) {xmppManager.setStatus(available, status);}public void sendMessage(String toJID, String message) {xmppManager.sendMessage(toJID, message);}public void receiveMessage() {xmppManager.receiveMessage();}public void sendAttachment(String toJID, String path) {xmppManager.sendAttachment(toJID, "Smack", path);}public void receiveAttachment() {xmppManager.receiveAttachment(username);}public void joinChatRoom(String roomName, String nickname) {joinedChatRoom = xmppManager.joinChatRoom(roomName, nickname);}public void leaveChatRoom() {try {joinedChatRoom.leave();} catch (SmackException.NotConnectedException | InterruptedException ex) {LOG.severe(ex.getLocalizedMessage());}}public void sendMessageToChatRoom(String message) {xmppManager.sendMessageToChatRoom(joinedChatRoom, message);}public String getJoinedChatRoom() {return joinedChatRoom.getRoom().toString();}public void addRosterListener() {xmppManager.rosterChanged();}}/*** Chat room, e.g. {@code room001}*/final class ChatRoom {private final String name;private final String domain;public ChatRoom(String name, String domain) {this.name = name;this.domain = domain;}public String getName() {return name + "@conference." + domain;}}ChatRoom類很簡單。 聊天室被標識為例如room001@conference.localhost ,其中conference是您在單擊Group Chat- > Group Chat Settings時在Openfire管理員控制臺中定義的子域,而localhost是我們通過命令行參數-d傳遞的域。 getName()返回的String是房間的裸JID ,我們將在后面看到。
User類更復雜。 它需要一個username ,一個password和一個domain并委托給XmppManager ,我們將很快看到。
XMPP客戶端的地址格式為user@server.com ,其中user是用戶名 , server.com是域 。 XMPP中的節點地址稱為Jabber ID,縮寫為JID 。 JID也可以具有資源 ( user@server.com/resource ),這意味著用戶可以從多個設備連接。 格式為user@server.com JID稱為裸JID ,而格式為user@server.com/resource的JID稱為完整JID 。
用戶可以setStatus() connect()到Openfire服務器,然后再login() ,然后用戶可以setStatus() , sendMessage()/receiveMesage(), sendAttachment()/receiveAttachment(), joinChatRoom()/leaveChatRoom()和sendMessageToChatRoom() 。
init()方法初始化XmppManager()并創建50個用戶,每個用戶連接,登錄并將其狀態設置為available 。 如果要測試文件傳輸,則每個用戶都開始收聽文件傳輸。 也創建了五個聊天室。 50個用戶中的每個用戶都分配到一個聊天室,因此最后,每個聊天室都包含10個用戶。
private static void init(XmppLoadTest loadXmppTest) {XmppManager xmppManager = new XmppManager(server, domain, port);for (int i = 1; i <= numberOfUsers; i++) {User user = loadXmppTest.new User("user" + String.format("%03d", i), password, domain, xmppManager);user.connect();user.login();user.setStatus(true, "Hello from " + user.getUsername());users.add(user);if (testAttachments || testBigAttachments) {user.receiveAttachment();}}for (int i = 0; i < numberOfRooms; i++) {chatRooms.add(loadXmppTest.new ChatRoom("room" + String.format("%03d", i + 1), domain));}if (!testAttachments && !testBigAttachments) {// join chatroomsfor (int i = 1; i <= numberOfUsers; i++) {ChatRoom chatRoom = chatRooms.get((i - 1) % numberOfRooms);User user = users.get(i - 1);user.joinChatRoom(chatRoom.getName(), user.getJabberID());}} }一種方案是讓每個user連接到五個聊天室之一并發送消息。 任務被創建( chatRoomMessageTask )在performLoad()和每執行every取決于消息的類型秒( 長或短 )作為配置loadxmpptest.properties 。
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); ....} else { // send messages to chat roomsfinal Runnable task = () -> {while (true) {if (Thread.currentThread().isInterrupted()) {return;}loadXmppTest.chatRoomMessageTask();}};int every = testLongMessages ? longMessagesDelayInSeconds : shortMessagesDelayInSeconds;scheduler.scheduleWithFixedDelay(task, 0, every, SECONDS); // every x seconds }另一種情況是將附件發送給另一個用戶,而不是將消息發送到聊天室:
if (testAttachments || testBigAttachments) { // send attachmentsString filePath = testBigAttachments ? bigFileNamePath : smallFileNamePath;int delay = testBigAttachments ? bigAttachmentsDelayInMinutes : smallAttachmentsDelayInMinutes;final Runnable task = () -> {while (true) {if (Thread.currentThread().isInterrupted()) {return;}loadXmppTest.fileTransferTask(filePath);}};scheduler.scheduleWithFixedDelay(task, 0, delay, MINUTES);您當然可以將兩種情況結合起來,但是您需要確保不會溢出Openfire的緩存。
/** Each user sends a message to a chat room. */ private synchronized void chatRoomMessageTask() {for (int i = 1; i <= numberOfUsers; i++) {String message = testLongMessages ? LONG_MESSAGE : MESSAGE;User user = users.get(i - 1);try {Thread.currentThread().sleep(delayToSendMessagesInMillis); // sleep 1"user.sendMessageToChatRoom(message);LOG.info(user.getJabberID() + " sent " + (testLongMessages ? "long" : "short") + " message to " + user.getJoinedChatRoom());} catch (InterruptedException ie) {Thread.currentThread().interrupt(); // reset the flag}} }在上述方法(稱為第一種情況)中,每個用戶向該用戶加入的聊天室發送一條消息(短消息或長消息)。
在fileTransferTask() ,每個用戶將附件發送給另一用戶(避免將附件發送給自己)。 請注意此方法和先前方法中的synchronized關鍵字,以避免代碼中出現死鎖。
/*** Exchange file attachments between users.** @param path path of the file to send* @see #transferFile(int, java.lang.String)*/ private void fileTransferTask(String path) {for (int i = 1; i <= numberOfUsers; i++) {transferFile(i, path);} } /*** Transfer the file to all other users.** @param i i-th user* @param path path of the file to be sent*/ private synchronized void transferFile(int i, String path) {int j;for (j = 1; j <= numberOfUsers; j++) {if (i != j) {try {int delay = testBigAttachments ? bigAttachmentsDelayInMinutes : smallAttachmentsDelayInMinutes;Thread.currentThread().sleep(delay); if (users.get(i - 1).sendAttachment(users.get(j - 1).getJabberID(), path)) {LOG.info("Attachment " + path + " sent from " + users.get(i - 1).getJabberID() + " to " + users.get(j - 1).getJabberID());} else {LOG.severe("Attachment " + path + " from " + users.get(i - 1).getJabberID() + " to " + users.get(j - 1).getJabberID() + " was not sent!");}} catch (InterruptedException ie) {Thread.currentThread().interrupt(); // reset the flag}}} }這樣就完成了XmppLoadTest類的描述。
4.3 XmppManager類
XmppManager類使用smack庫[6,7]與Openfire服務器進行通信。 Smack是用于與XMPP服務器通信以執行實時通信(包括即時消息傳遞和群聊)的庫。
XmppManager與[10]中的類似,但是直到那時一切都在發展,API也發生了變化。 如前所述, User委托給XmppManager 。
4.3.1連接到Openfire
要連接到Openfire服務器,您需要托管Openfire 的服務器名稱 , 域和端口 (已固定: 5222 )。 XMPPTCPConnection類用于創建與XMPP服務器的連接。 可以使用XMPPTCPConnectionConfiguration.Builder配置其他連接參數:
private String resource = "Smack"; ... XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder(); try {builder.setXmppDomain(JidCreate.domainBareFrom(domain)).setHost(server).setPort(port).setResource(resource).setSecurityMode(SecurityMode.disabled).setHostnameVerifier((String hostname, SSLSession session) -> true); } catch (XmppStringprepException ex) {LOG.severe(ex.getLocalizedMessage()); } try {builder = TLSUtils.acceptAllCertificates(builder); } catch (KeyManagementException | NoSuchAlgorithmException ex) {LOG.log(Level.SEVERE, null, ex); } XMPPTCPConnection.setUseStreamManagementDefault(true); XMPPTCPConnectionConfiguration config = builder.build();resource String對于文件傳輸很重要。 如果您使用的是smack,則可以是"Smack"或"Resource" 。 如果使用其他客戶端,例如Spark,則可以將其設置為"Spark ”。它可以確定要將文件發送到的資源。
//SASLMechanism mechanism = new SASLDigestMD5Mechanism(); SASLMechanism mechanism = new SASLPlainMechanism(); SASLAuthentication.registerSASLMechanism(mechanism); SASLAuthentication.unBlacklistSASLMechanism("PLAIN"); SASLAuthentication.blacklistSASLMechanism("SCRAM-SHA-1"); SASLAuthentication.unBlacklistSASLMechanism("DIGEST-MD5"); try {builder = TLSUtils.acceptAllCertificates(builder); } catch (KeyManagementException | NoSuchAlgorithmException ex) {LOG.severe(ex.getLocalizedMessage()); } XMPPTCPConnection.setUseStreamManagementDefault(true); XMPPTCPConnectionConfiguration config = builder.build();TLSUtils.acceptAllCertificates(builder); 由于安全模型在最新版本的Openfire中已更改,因此這一點非常重要。 因此,我們在Openfire的管理控制臺中添加了sasl.mechs.00001和sasl.mechs.00002 。 如果他仍然遇到連接/身份驗證問題,則此鏈接可能有幫助。
4.3.2登錄
配置與Openfire的連接后,就可以連接到它了:
private AbstractXMPPConnection connection; ... connection = new XMPPTCPConnection(config); connection.setReplyTimeout(1000L); try {connection.connect(); } catch (SmackException | IOException | XMPPException | InterruptedException ex) {LOG.severe(ex.getLocalizedMessage()); }默認情況下,如果突然斷開連接,Smack將嘗試重新連接。 重新連接管理器將嘗試立即重新連接到服務器,并增加嘗試之間的延遲,因為連續的重新連接持續失敗。 創建連接后,用戶應使用其憑據使用XMPPConnection.login()方法登錄:
public void login(String username, String password) {if (connection != null && connection.isConnected()) {try {connection.login(username, password);} catch (XMPPException | SmackException | IOException | InterruptedException ex) {LOG.severe(ex.getLocalizedMessage());}}LOG.info(username + " authenticated? " + connection.isAuthenticated()); }4.3.3在場和名冊
用戶登錄后,可以通過創建新的Chat或MultiUserChat對象開始與其他用戶Chat 。 用戶還可以將其狀態設置為可用 :
public void setStatus(boolean available, String status) {Presence.Type type = available ? Type.available : Type.unavailable;Presence presence = new Presence(type);presence.setStatus(status);try {connection.sendStanza(presence);} catch (SmackException.NotConnectedException | InterruptedException ex) {LOG.severe(ex.getLocalizedMessage());} }從客戶端到XMPP服務器的每個消息稱為數據包或節,并以XML的形式發送。 節是客戶端可以在一個程序包中發送給服務器的最小XML數據段,反之亦然。 所述org.jivesoftware.smack.packet Java包中包含封裝由XMPP( 消息 , 存在和IQ)所允許的三種不同的基本分組類型的類。 XMPP服務器和客戶端對每個節的處理方式不同。 節具有類型屬性 ,這些屬性可用于進一步區分節[3]。
消息 節旨在用于在XMPP實體之間發送數據。 實在是忘了,也就是說,接收方不承認節。 通常,當您從客戶端發送消息節并且未生成任何類型的錯誤時,您可以假定消息已成功發送。 消息節的類型可以是“聊天”,“ groupchar”,“錯誤”等。
狀態節會通告其他實體的在線狀態(網絡可用性)。 在線狀態的工作方式類似于XMPP中的訂閱。 當您對某些JID的存在感興趣時,您就訂閱它們的存在,即,您告訴XMPP服務器“每次該JID向您發送狀態更新時,我都希望得到通知”。 當然,服務器會詢問JID持有者是否接受向您透露其在線信息。 當他們接受時,服務器會記住他們的決定,并在更改在線狀態時更新訂閱該狀態的任何人。 術語存在還表示用戶是否在線。
最后, IQ (信息/查詢)節用于從服務器獲取一些信息(例如,有關服務器或其注冊客戶端的信息)或將某些設置應用于服務器。
在XMPP中,術語名冊用于指代聯系人列表。 用戶的聯系人列表通常存儲在服務器上。 該名冊使您可以跟蹤其他用戶的可用性(狀態)。 可以將用戶分為“朋友”和“同事”之類的組,然后您會發現每個用戶是在線還是離線。 Roster類允許您查找所有名單條目,它們所屬的組以及每個條目的當前狀態。
名冊中的每個用戶都由RosterEntry表示,該成員包括:
- XMPP地址(例如john@example.com )。
- 您分配給用戶的名稱(例如"John" )。
- 條目所屬的名冊中的組的列表。 如果名冊條目不屬于任何組,則稱為“未歸檔條目”。
在名單中的每個條目都有一個與之關聯的存在 。 Roster.getPresence(String user)方法將返回一個具有用戶狀態的Presence對象;如果用戶不在線或您未訂閱該用戶的狀態,則返回null 。 用戶要么在線要么離線 。 當用戶在線時,他們的存在可能包含擴展信息,例如他們當前正在做什么,是否希望受到打擾等。
public Roster createRosterFor(String user, String name) throws Exception {LOG.info(String.format("Creating roster for buddy '%1$s' with name %2$s", user, name));Roster roster = Roster.getInstanceFor(connection);roster.createEntry(JidCreate.bareFrom(user), name, null);return roster; } public void printRosters() throws Exception {Roster roster = Roster.getInstanceFor(connection);Collection entries = roster.getEntries();for (RosterEntry entry : entries) {LOG.info(String.format("Buddy: %s", entry.getName()));} } public void rosterChanged() {Roster roster = Roster.getInstanceFor(connection);roster.addRosterListener(new RosterListener() {@Overridepublic void presenceChanged(Presence presence) {LOG.info("Presence changed: " + presence.getFrom() + " " + presence);resource = presence.getFrom().getResourceOrEmpty().toString();}@Overridepublic void entriesAdded(Collection clctn) { }@Overridepublic void entriesUpdated(Collection clctn) { }@Overridepublic void entriesDeleted(Collection clctn) { }}); }在場信息可能會經常更改,并且名冊條目也可能會更改或刪除。 要偵聽變化的花名冊和狀態數據,請使用RosterListener 。 為了通知有關名冊的所有更改,應在登錄XMPP服務器之前注冊RosterListener 。 文件傳輸知道,如果收件人的資源發生了變化,所描述的是很重要的位置 。
4.3.4聊天和多聊
您可以在ChatManager的幫助下發送和接收聊天消息。 盡管可以將單個消息作為數據包發送和接收,但是使用org.jivesoftware.smack.chat2.Chat類將消息字符串視為聊天通常會更容易。 聊天會在兩個用戶之間創建新的消息線程。 Chat.send(String)方法是一種便捷方法,它創建一個Message對象,使用String參數設置正文,然后發送消息。
/*** Send message to another user.** @param buddyJID recipient* @param message to send*/ public void sendMessage(String buddyJID, String message) {LOG.info(String.format("Sending message '%1$s' to user %2$s", message, buddyJID));try {Chat chat = ChatManager.getInstanceFor(connection).chatWith(JidCreate.entityBareFrom(buddyJID));chat.send(message);} catch (XmppStringprepException | SmackException.NotConnectedException | InterruptedException ex) {LOG.severe(ex.getLocalizedMessage());} } public void receiveMessage() {ChatManager.getInstanceFor(connection).addIncomingListener((EntityBareJid from, Message message, Chat chat) -> {LOG.info("New message from " + from + ": " + message.getBody());}); }要加入聊天室( MultiUserChat )并向其中發送消息:
public MultiUserChat joinChatRoom(String roomName, String nick) {try {MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection);MultiUserChat muc = manager.getMultiUserChat(JidCreate.entityBareFrom(roomName));Resourcepart nickname = Resourcepart.from(nick);muc.join(nickname);LOG.info(muc.getNickname() + "joined chat room " + muc.getRoom());return muc;} catch (XmppStringprepException | SmackException.NotConnectedException | InterruptedException | SmackException.NoResponseException | XMPPException.XMPPErrorException | MultiUserChatException.NotAMucServiceException ex) {LOG.severe(ex.getLocalizedMessage());}return null; }public void sendMessageToChatRoom(MultiUserChat muc, String message) {try {muc.sendMessage(message);LOG.fine("Message '" + message + "' was sent to room '" + muc.getRoom() + "' by '" + muc.getNickname() + "'");} catch (InterruptedException | SmackException.NotConnectedException ex) {LOG.severe(ex.getLocalizedMessage());} }您可以在加入聊天室時定義昵稱。
4.3.5文件傳輸
要發送/接收附件,它比較復雜(請參閱此處 ):
/*** File transfer.** @param buddyJID recipient* @param res e.g. "Spark-2.8.3", default "Smack" (cannot be empty or null)* @param path path of the file attachment to send* @return {@code true} if file transfer was successful*/ public boolean sendAttachment(String buddyJID, String res, String path) {LOG.info(String.format("Sending attachment '%1$s' to user %2$s", path, buddyJID));FileTransferManager fileTransferManager = FileTransferManager.getInstanceFor(connection);FileTransferNegotiator.IBB_ONLY = true;OutgoingFileTransfer fileTransfer = null;try {fileTransfer = fileTransferManager.createOutgoingFileTransfer(JidCreate.entityFullFrom(buddyJID + "/Spark-2.8.3"));} catch (XmppStringprepException ex) {LOG.log(Level.SEVERE, null, ex);return false;}if (fileTransfer != null) {OutgoingFileTransfer.setResponseTimeout(15 * 60 * 1000);LOG.info("status is:" + fileTransfer.getStatus());File file = Paths.get(path).toFile();if (file.exists()) {try {fileTransfer.sendFile(file, "sending attachment...");} catch (SmackException ex) {LOG.severe(ex.getLocalizedMessage());return false;}LOG.info("status is:" + fileTransfer.getStatus());if (hasError(fileTransfer)) {LOG.severe(getErrorMessage(fileTransfer));return false;} else {return monitorFileTransfer(fileTransfer, buddyJID);}} else try {throw new FileNotFoundException("File " + path + " not found!");} catch (FileNotFoundException ex) {LOG.severe(ex.getLocalizedMessage());return false;}}}return true; }/*** Monitor file transfer.** @param fileTransfer* @param buddyJID* @return {@code false} if file transfer failed.*/ private boolean monitorFileTransfer(FileTransfer fileTransfer, String buddyJID) {while (!fileTransfer.isDone()) {if (isRejected(fileTransfer) || isCancelled(fileTransfer)|| negotiationFailed(fileTransfer) || hasError(fileTransfer)) {LOG.severe("Could not send/receive the file to/from " + buddyJID + "." + fileTransfer.getError());LOG.severe(getErrorMessage(fileTransfer));return false;} else if (inProgress(fileTransfer)) {LOG.info("File transfer status: " + fileTransfer.getStatus() + ", progress: " + fileTransfer.getProgress());}try {Thread.sleep(1000);} catch (InterruptedException ex) {LOG.severe(ex.getLocalizedMessage());}}if (isComplete(fileTransfer)) {LOG.info(fileTransfer.getFileName() + " has been successfully transferred.");LOG.info("The file transfer is " + (fileTransfer.isDone() ? "done." : "not done."));return true;}return true; }public void receiveAttachment(String username) {final FileTransferManager manager = FileTransferManager.getInstanceFor(connection);manager.addFileTransferListener((FileTransferRequest request) -> {// Check to see if the request should be acceptedif (request.getFileName() != null) {StringBuilder sb = new StringBuilder(BUFFER_SIZE);try {// Accept itIncomingFileTransfer transfer = request.accept();String filename = transfer.getFileName() + "_" + username;transfer.receiveFile(new File(filename));while (!transfer.isDone()) {try {Thread.sleep(1000);LOG.info("STATUS: " + transfer.getStatus()+ " SIZE: " + sb.toString().length()+ " Stream ID : " + transfer.getStreamID());} catch (Exception e) {LOG.severe(e.getMessage());}if (transfer.getStatus().equals(FileTransfer.Status.error)) {LOG.severe(transfer.getStatus().name());}if (transfer.getException() != null) {LOG.severe(transfer.getException().getLocalizedMessage());}}LOG.info("File received " + request.getFileName());} catch (SmackException | IOException ex) {LOG.severe(ex.getLocalizedMessage());}} else {try {// Reject itrequest.reject();LOG.warning("File rejected " + request.getFileName());} catch (SmackException.NotConnectedException | InterruptedException ex) {LOG.severe(ex.getLocalizedMessage());}}}); }Openfire中定義了3種類型的文件傳輸 :
- 帶內 ( FileTransferNegotiator.IBB_ONLY ),其中消息被分解為多個塊并作為編碼消息發送。 它速度較慢,但??始終有效。 此外,由于交換的消息存儲在Openfire數據庫中,因此備份起來更容易。
- 當兩個用戶都在同一網絡上時, 對等 (p2p)效果很好,但是當一個用戶位于防火墻后或使用NAT時,對等網絡將失敗。 它速度更快,除了上述問題之外,您無法控制要交換的內容。
- 代理服務器 (SOCKS5,請參閱XEP-0096或更新的XEP-0234 )使用文件傳輸代理,但需要打開端口7777。它比p2p慢,但比帶內快。
在我們的測試工具中,正在使用帶內文件傳輸。
一旦成功發送文件,就需要監視其狀態( monitorFileTransfer() )。 可能存在網絡錯誤,或者收件人可能只是拒絕文件傳輸。 實際上,其他用戶可以選擇接受,拒絕或忽略文件傳輸請求。
發送附件是OutgoingFileTransfer ,而接收是IncomingFileTransfer 。 這是通過向FileTransferManager添加偵聽器來實現的。 如前所述,接收者需要在發送者發送文件之前開始偵聽。 此外,在我們的負載測試中,正在發送和接收相同的文件。 為了避免覆蓋相同的文件,源文件以不同的名稱存儲,在文件名中添加"_"和收件人的名稱。 當然,這些文件名在負載測試工具運行時會一次又一次地寫入。
建立
為了能夠執行負載測試工具,您需要創建一個可執行文件XmppLoadTest-1.0.jar 。 一種方法是將以下內容添加到pom.xml中:
<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>3.2.0</version><configuration><archive><manifest><addClasspath>true</addClasspath><mainClass>test.xmpp.xmpploadtest.XmppLoadTest</mainClass></manifest></archive></configuration></plugin></plugins> </build>并且還需要將依賴項包括到classpath中 。 或者,您可以使用依賴項插件來創建一個單個jar ,該jar會創建此處所述的所有內容。
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.2.0</version><configuration><archive><manifest><mainClass>test.xmpp.xmpploadtest.XmppLoadTest</mainClass></manifest></archive><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs></configuration><executions><execution><id>make-assembly</id> <!-- this is used for inheritance merges --><phase>package</phase> <!-- bind to the packaging phase --><goals><goal>single</goal></goals></execution></executions> </plugin>您也可以使用maven命令代替執行它:
mvn exec:java -Dexec.mainClass=test.xmpp.xmpploadtest.XmppLoadTest "-Dexec.args=-s localhost -d localhost -p a -n 50"4.5負載測試
一旦執行了負載測試工具,您將看到許多發送到Openfire服務器的消息。 根據您選擇的場景(群聊或文件傳輸),如果您使用第50個用戶(例如Spark)之類的聊天客戶端進行連接并加入聊天室,您將看到他們被重復發送的相同消息所填充其他49個模擬用戶。
Apr 25, 2020 11:55:16 PM test.xmpp.xmpploadtest.XmppManager connect INFO: Initializing connection to server localhost port 5222 Apr 25, 2020 11:55:18 PM test.xmpp.xmpploadtest.XmppManager connect INFO: Connected: true Apr 25, 2020 11:55:18 PM test.xmpp.xmpploadtest.XmppManager login INFO: user001 authenticated? True ... Apr 25, 2020 11:55:21 PM test.xmpp.xmpploadtest.XmppManager joinChatRoom INFO: user001@localhost joined chat room room001@conference.localhost Apr 25, 2020 11:55:21 PM test.xmpp.xmpploadtest.XmppManager joinChatRoom INFO: user002@localhost joined chat room room002@conference.localhost ... Apr 25, 2020 11:55:24 PM test.xmpp.xmpploadtest.XmppLoadTest chatRoomMessageTask INFO: user001@localhost sent short message to room001@conference.localhost Apr 25, 2020 11:55:25 PM test.xmpp.xmpploadtest.XmppLoadTest chatRoomMessageTask INFO: user002@localhost sent short message to room002@conference.localhost ...當您在user050情況下運行該工具時,您沒有在Spark或以user050連接的聊天客戶端中看到任何附件。
INFO: Sending attachment 'test.txt' to user user003@localhost [Sun May 10 17:55:15 CEST 2020] INFO: status is:Initial [Sun May 10 17:55:15 CEST 2020] INFO: status is:Initial [Sun May 10 17:55:15 CEST 2020] INFO: STATUS: Complete SIZE: 0 Stream ID : jsi_2604404248040129956 [Sun May 10 17:55:15 CEST 2020] INFO: File received test.txt [Sun May 10 17:55:15 CEST 2020] INFO: STATUS: In Progress SIZE: 0 Stream ID : jsi_4005559316676416776 [Sun May 10 17:55:16 CEST 2020] WARNING: Closing input stream [Sun May 10 17:55:16 CEST 2020] INFO: STATUS: In Progress SIZE: 0 Stream ID : jsi_6098909703710301467 [Sun May 10 17:55:16 CEST 2020] INFO: STATUS: In Progress SIZE: 0 Stream ID : jsi_2348439600749627884 [Sun May 10 17:55:16 CEST 2020] INFO: STATUS: In Progress SIZE: 0 Stream ID : jsi_8708250841661514027 [Sun May 10 17:55:16 CEST 2020] INFO: STATUS: In Progress SIZE: 0 Stream ID : jsi_2119745768373873364 [Sun May 10 17:55:16 CEST 2020] INFO: STATUS: In Progress SIZE: 0 Stream ID : jsi_6583436044582265363 [Sun May 10 17:55:16 CEST 2020] INFO: STATUS: In Progress SIZE: 0 Stream ID : jsi_3738252107587424431 [Sun May 10 17:55:16 CEST 2020] INFO: STATUS: In Progress SIZE: 0 Stream ID : jsi_4941117510857455094 [Sun May 10 17:55:16 CEST 2020] INFO: test.txt has been successfully transferred. [Sun May 10 17:55:16 CEST 2020] INFO: The file transfer is done. [Sun May 10 17:55:16 CEST 2020]一旦運行了加載/壓力工具,就可以搜索XMPP服務器或客戶端的內存泄漏或CPU高使用率。 您可以使用VisualVM之類的工具來監視內存和CPU,或者甚至可以根據需要使用YourKit或Java Flight Recorder之類的工具進行概要分析。
5.總結
在本教程中,我們學習了如何編寫自己的負載測試工具來對XMPP服務器(如Openfire)進行負載/壓力測試。 負載/壓力工具還可以用于測試XMPP客戶端(例如Spark)。 如果您編寫了自己的XMPP客戶端或服務器,那么它也可以用于測試它們。 該工具使用Smack XMPP庫以Java編寫。 它可以在兩種模式或場景下運行,既可以將消息發送到聊天室,也可以在用戶之間發送文件傳輸。 XMPP服務器需要使用模擬用戶和聊天室進行預配置。
您可以根據需要自定義進一步擴展源代碼,例如用戶數量,消息或文件附件的大小,消息之間的延遲,發送消息和文件傳輸的組合或測試XMPP的其他方面協議。
6.參考
7.下載Maven項目
那是一篇有關Java XMPP負載測試工具的文章。
下載您可以在此處下載完整的源代碼: Java XMPP負載測試工具
翻譯自: https://www.javacodegeeks.com/java-xmpp-load-test-tool.html
總結
以上是生活随笔為你收集整理的Java XMPP负载测试工具的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux命令cp使用方法(linux
- 下一篇: linux端口开启命令(linux 端口