Java XMPP负载测试工具

在本文中,我们将开发用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库
XMPP负载测试工具

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.参考

  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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.tpcf.cn/news/339930.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

android sdcard 压缩图片,android如何往SDCard中存取图片

import java.io.File;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import android.graphics.Bitmap;import android.os.Environment;import android.util.Log;/*** 保存图片的类***/public class SaveBitmap {private f…

python get()函数_C++使用ffpython嵌入和扩展python(python2和python3)

C使用ffpython嵌入和扩展python&#xff08;python2和python3&#xff09;摘要:在服务器编程中&#xff0c;经常会用到python脚本技术。Python是最流行的脚本之一&#xff0c;并且python拥有定义良好的C API接口&#xff0c;同时又有丰富的文档&#xff0c;与C结合非常的适合。…

c# opencv 轮廓检测_C#中OpenCVSharp实现轮廓检测

OpenCv提供了函数 findContours()用于对物体轮廓进行检测&#xff0c;该函数实现算法是由S.suzuki K.Abe于1985年发表的。OpenCVSharp封装了这个函数&#xff0c;有2个参数(contours&#xff0c;hierarchy)要做特别的说明。public static void FindContours(InputOutputArray i…

鸿蒙系统的挑战,简单的讲解下何为鸿蒙系统,可能会挑战你的认知颠覆你的想象...

本帖最后由 一梦盛夏 于 2020-12-2 17:33 编辑这篇文章可能会颠覆你的想象&#xff0c;挑战你的认知&#xff0c;请耐心阅读。今天不说专业术语&#xff0c;全部用比喻方式来聊&#xff0c;这样会更明白一些&#xff0c;也欢迎大家一起来讨论。首先&#xff0c;我们先聊聊何为鸿…

linux nat冲突,Linux系统下的NAT及防火墙的混合应用

2011-11-28 13:03朋友你好&#xff01;我刚开始搞linux&#xff0c;碰到了很多问题&#xff0c;想问你下&#xff0c;希望能帮我解答下。下面是我的一段shell 程序#&#xff01;/bin/shwhile [ 0 ]doread scoreif [ $score -lt 60 ];thenecho " you must work hard!"…

外贸常用术语_13个常用的国际贸易术语详解

原标题&#xff1a;13个常用的国际贸易术语详解做外贸的我们一定要详细的了解外贸运输术语的含义&#xff0c;每一个术语买卖双方所需承担的责任与义务。这样能够更好的区分买卖双方该承担履行的责任与义务。很多时候我们只是知道我们日常经常用到的&#xff0c;其他的还是不是…

卡夫卡–一次语义学

在分布式环境中&#xff0c;故障是很常见的情况&#xff0c;可以随时发生。 在Kafka环境中&#xff0c;代理可能会崩溃&#xff0c;网络故障&#xff0c;处理故障&#xff0c;发布消息时失败或无法使用消息等。这些不同的场景引入了不同类型的数据丢失和重复。 失败场景 A&am…

android 打印机蜂鸣器,CANON喷墨打印机 蜂鸣器响5声不打印的问题解决办法

CANON喷墨打印机 蜂鸣器响5声不打印的问题解决办法通常我们如果遇到CANON打印机蜂鸣器响5声即表示废墨仓满&#xff0c;一般都可以通过按键且使用02、03、05墨盒的机型按下面方法操作&#xff1a;同时按住左、右两键&#xff0c;插入电源&#xff0c;然后松开两键&#xff1b;按…

python病毒usb文件自动安装_win7禁USB自动安装驱动功能避免木马病毒入侵

用户在限制USB设备使用方面&#xff0c;首先考虑的就是禁止其自动播放&#xff0c;以避免木马病毒入侵&#xff0c;但很多时候&#xff0c;这样的方法并不是很有效。如果你是Win7用户&#xff0c;那就可以通过它独有的“权限控制”技术&#xff0c;从源头上控制USB设备的自动运…

java错误代码典型案例_Java代码中的典型错误

java错误代码典型案例该页面包含在与我一起工作的人的Java代码中看到的最典型的错误。 静态分析&#xff08;出于明显的原因&#xff0c;我们使用查询无法捕获所有错误&#xff0c;这就是为什么我决定在此处列出所有错误的原因。 如果您要在此处添加其他内容&#xff0c;请告诉…

tinymce vue 部分工具不显示_tinymce-vue使用问题及解决记录

一、本地图片上传&#xff1a;1.转成base64&#xff0c;转换后的字符粘贴到notpadd中看了下&#xff0c;有近20万个字符&#xff0c;所以直接没考虑2.选择图片后调用后台方法传到服务器&#xff0c;保存后返回一个网络地址&#xff0c;传入success的回调方法&#xff0c;通过网…

清洁单元测试

使用JUnit和一些模拟库编写“单元测试”测试很容易。 即使测试甚至不是单元测试并提供可疑的价值&#xff0c;它们也可能产生使某些涉众满意的代码覆盖范围。 编写单元测试&#xff08;在理论上是单元测试&#xff0c;但比基础代码更复杂&#xff09;因此也很容易编写&#xff…

谷歌发布android+8.2,谷歌Pixel 2代三款新机齐曝光:骁龙835+Android 8.0

随着5月份谷歌I/O大会的临近&#xff0c;关于Android O(安卓8.0)和Pixel手机的消息逐渐多了起来。据外媒报道&#xff0c; Android Open Source Project的代码表中确认&#xff0c;三款Pixel新机搭载的都是MSM8998芯片&#xff0c;也就是骁龙835。目前&#xff0c;Pixel 2的代号…

怎么停止skywalking_Skywalking部署常见问题以及注意事项

Skywalking部署常见问题以及注意事项IntroSkyWalking 创建与2015年&#xff0c;提供分布式追踪功能。从5.x开始&#xff0c;项目进化为一个完成功能的Application Performance Management系统。他被用于追踪、监控和诊断分布式系统&#xff0c;特别是使用微服务架构&#xff0c…

akka的介绍_Akka笔记–演员介绍

akka的介绍过去做过多线程的任何人都不会否认管理多线程应用程序有多么艰辛和痛苦。 我说管理是因为它开始很简单&#xff0c;一旦您开始看到性能改进&#xff0c;它就会变得非常有趣。 但是&#xff0c;当您发现没有一种简单的方法可以从子任务中的错误或难以发现的僵尸错误中…

HTML语言name,HTML a 标签的 name 属性

延伸阅读&#xff1a;使用 name 属性还是 id 属性&#xff1f;在 HTML 4.0 之前的版本中&#xff0c;只有使用 标签的 name 属性才能创建片段标识符。随着 HTML 4.0 中 id 属性的出现&#xff0c;所有 HTML 或 XHTML 元素都可以是片段标识符。这是因为 id 标识符几乎可以用在所…

使用模拟进行测试

如果使用正确的方法&#xff0c;模拟对象将非常有用。 我在需要驱动软件开发使用的帖子中分享了一些使用Mock Objects的经验。 在这篇文章中&#xff0c;我分享了两件事 –使用模拟进行基于合同的测试。 –用于组织模拟代码的模式。 基于合同的测试 让我们以正在构建汇款服…

hazelcast_Hazelcast的MapLoader陷阱

hazelcastHazelcast提供的核心数据结构之一是IMap<K, V> &#xff0c;它扩展了java.util.concurrent.ConcurrentMap &#xff0c;它基本上是一个分布式地图&#xff0c;通常用作缓存。 您可以将此类地图配置为使用自定义MapLoader<K, V> -每次尝试从该地图&#xf…

html ul左侧浮动,UL里的LI元素左浮动层一行显示时使其居中的方法

在制作页面是&#xff0c;li浮动是很常用的&#xff0c;一般情况也不用其居中&#xff0c;但有时&#xff0c;其特殊原因需要居中&#xff0c;这是就有点犯难了&#xff0c;这里有了一个很好的解决方法&#xff0c;主要是用了相对定位的原理。在ul外报一层&#xff0c;使其相对…

aws s3 獲取所有文件_AWS SA associate 证书考试学习记录-EBS,S3,EFS比较

我们的目标&#xff0c;就是花最少的时间&#xff0c;学到最多的东西&#xff1a;-&#xff09;在AWS中&#xff0c;可以选择的存储服务很多&#xff0c;纷繁复杂&#xff0c;新手根本弄不清楚选择哪个。因为做为一个云架构师&#xff0c;你并不是让你的产品能用就够了&#xf…