在本文中,我们将开发用Java编写的XMPP负载测试工具。

目录

1.简介 2. XMPP负载测试工具 3.先决条件 4. LoadXmppTest Java程序
4.1。 创建一个新的Maven项目 4.2。 创建主类 4.3。 XmppManager类 4.4。 建立 4.5。 负载测试
5.总结 6.参考 7.下载Maven项目

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个用户名user001user050用户, 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);

UserChatRoom的定义如下:

/*** 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/resourceJID称为完整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.00001sasl.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在场和名册

用户登录后,可以通过创建新的ChatMultiUserChat对象开始与其他用户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.参考

  1. Aladev R.(2017a),“ XMPP负载测试–最终指南 ”。
  2. Aladev R.(2017b),“ XMPP负载测试–高级方案 ”。
  3. Gakwaya D.(2016),“ XMPP的友好介绍 ”。
  4. Marx D.(2017),“ Java命令行界面(第1部分):Apache Commons CLI ”,JavaCodeGeeks。
  5. Saint-Andre P.,Smith K.,Troncon R.(2009年), XMPP:权威指南 ,O'Reilly。
  6. Smack API
  7. 打击文件
  8. Tsagklis I.(2010a),“ Openfire服务器安装-即时消息基础结构 ”,JavaCodeGeeks。
  9. Tsagklis I.(2010b),“ Openfire服务器配置-即时消息基础结构 ”,JavaCodeGeeks。
  10. Tsagklis I.(2010c),“ 带有适用于Java应用程序的Smack的XMPP IM-即时消息基础结构 ”,JavaCodeGeeks。

7.下载Maven项目

那是一篇有关Java XMPP负载测试工具的文章。

下载
您可以在此处下载完整的源代码: Java XMPP负载测试工具

翻译自: https://www.javacodegeeks.com/java-xmpp-load-test-tool.html

Java XMPP负载测试工具相关推荐

  1. java xmpp_Java XMPP负载测试工具

    java xmpp 在本文中,我们将开发用Java编写的XMPP负载测试工具. 目录 1.简介 2. XMPP负载测试工具 3.先决条件 4. LoadXmppTest Java程序 4.1. 创建一 ...

  2. 负载测试工具Ripplet

    负载测试工具Ripplet Ripplet的最新版本:0.8b Ripplet特征: 1)基于Apache License 2.0许可证: 2)软件程序性能和负载测试工具: 3)有四个分布式的Java ...

  3. 高性能 HTTP 负载测试工具 Vegeta

    什么是 Vegeta Vegeta 是一个用 Go 语言编写的多功能的 HTTP 负载测试工具,它提供了命令行工具和一个开发库. 官方地址:https://github.com/tsenart/veg ...

  4. Vegeta(高性能 HTTP 负载测试工具)

    为什么要了解这个东西呢?主要是针对之前已经写好的程序磁盘缓存要做压力测试,我之前写的磁盘缓存采用的是类似http协议,并不是真正的http协议,所以要对这个源码进行修改,最近读了好多别人的源代码,会有 ...

  5. 如何利用负载测试工具快速进行负载测试

    自动化测试工具-黑盒测试工具-功能测试工具-AutoRunner365-Alltesting泽众云测试​www.alltesting.cn/jsp/newVersion2/bigNews/alltes ...

  6. 带您理解解负载测试怎么做及负载测试工具

    负载测试是为一个应用或系统尽可能地接近成品部署并在用户群中创建的模拟环境.一个负载测试可以测量响应时间,吞吐率和资源利用率,并确定应用程序的性能瓶颈,假设性能瓶颈的出现低于负载峰值. 在这里,&quo ...

  7. 使用 Jtest:一款优秀的 Java 代码优化和测试工具

     Jtest 简介 Jtest 是 Parasoft 公司推出的一款针对 Java 语言的自动化代码优化和测试工具,它通过自动化实现对 Java 应用程序的单元测试和编码规范校验,从而提高代码的可 ...

  8. 开源网络负载测试工具-基准测试

    译者注:在上一篇<开源网络负载测试工具测评>文章中,我以我微薄的翻译功底向大家展现了Ragnar Lönn先生对当前主流开源负载测试工具的一些看法.Ragnar Lönn先生主要以测试工具 ...

  9. VDI负载测试工具使用分享:Login VSI简介

    Login Virtual Session Indexer(Login VSI)是目前唯一一款测试集中式桌面环境(如SBC和VDI)性能和可扩展性的行业基准工具,同时,他也是一款兼容性强,能支持大多数 ...

最新文章

  1. mysql事务的优点和缺点_OLTP应用之MySQL架构选型--图文教程
  2. cf手游服务器维护19年9月19,神雕侠侣2手游9月19日停服维护公告_神雕侠侣2手游9月19日更新了什么_玩游戏网...
  3. 《Python游戏编程快速上手》第三章-猜数字游戏
  4. 不抛弃,不放弃@《士兵突击》
  5. 扫地机器人隔板_【扫地机器人使用】_摘要频道_什么值得买
  6. Python中字符串的startswith()和endswith()方法
  7. innodb启动失败无法重启的处理方法
  8. 困兽之斗!使用JavaScript执行客户端的exe文件(excute the .exe file on client-side machine by JavaScript)...
  9. 广东财经大学计算机专业学费,广东财经大学各专业一年至少需要交的学费
  10. 听说去了BAT的 Linuxers 都做过这套面试题!
  11. CUBA Platform 7.0.3 发布,企业级应用开发平台
  12. 天线多频设计方法精讲
  13. 计算机职称专业分类明细,职称分类一览表
  14. 电脑计算机快捷键切换桌面,电脑切换界面的快捷键是什么_电脑切换桌面快捷键怎么用-win7之家...
  15. unity tier setting
  16. sharepoint文档库文件下载
  17. (转)罗振宇跨年演讲:哪来直接登顶的人生,只有不断迭代的历程
  18. QQ群龙王快速获得代码以及方法
  19. Umi部署pages多页面访问配置
  20. SolidWorks焊件中将各结构构件分解成单个零件的方法

热门文章

  1. P2052-[NOI2011]道路修建【树】
  2. 欢乐纪中某B组赛【2019.1.29】
  3. jzoj1370-飞船【RMQ初见】
  4. ssl1759-求连通分量【图论,深搜,广搜】
  5. 【模拟】pjesma(jzoj 1151)
  6. SpringBoot2.1.9 多Kafka消费者配置
  7. 十面阿里,菜鸟,天猫,蚂蚁金服题目总汇
  8. JAVA多线程和并发面试问题
  9. 达到年薪 40W 必需掌握的技术
  10. 春节跳槽最新Java面试题