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个用户名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

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

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

相关文章

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

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

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

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

卡夫卡–一次语义学

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

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

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

使用模拟进行测试

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

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

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

html设置顶部对齐,HTML / CSS文本从div顶部对齐

首先&#xff0c;你需要修复你的CSS选择器 .你可以通过这种方式t write all those id .#content #main #services只需选择1个元素和他的孩子 .例如&#xff0c;如果你测试它&#xff0c;它将适用于你&#xff1a;#services .langelis .txt {width: 440px;height: auto;float: l…

hot编码 字符one_One Hot编码是什么?为什么要用它,什么时候用它?

作者&#xff1a;Rakshith Vasudev编译&#xff1a;ronghuaiyang导读当你在玩ML模型的时候&#xff0c;你会在任何地方遇到这个“One hot encoding”的术语。当你在玩ML模型的时候&#xff0c;你会在任何地方遇到这个“One hot encoding”术语。你可以看到一个one hot编码器的s…

CUBA 7.2 –有什么新功能?

CUBA平台的第七版向前迈出了一大步。 内部体系结构的改进和新的IDE为进一步改进奠定了良好的基础。 我们将继续添加新功能&#xff0c;以使开发人员的生活更轻松&#xff0c;并使他们的工作更加高效。 在7.2版中&#xff0c;我们引入了许多可能看起来像是主要更新的更改&#…

postgresql 分区视图_PostgreSQL架构集中式到分布式主流架构总结

文章目录一、PG未来主流架构为什么是分布式二、PostgreSQL集中式到分布式架构总结一、PG未来主流架构为什么是分布式如果说5年前DB的分布式还只是一种趋势&#xff0c;如今分布式数据库正逐渐从趋势变成主流。说到分布式&#xff0c;我想我们不能不提一下集中式和分库分表。01集…

html5 上传图片模板,HTML5实现图片文件异步上传

&#xff0c;过现前个能文使近记接的端问对字用近记接  利用HTML5的新特点做文件异步上传非常简单方便&#xff0c;本文主要展示JS部分&#xff0c;html结构。下面的代码并未使用第三发库&#xff0c;如果有参照&#xff0c;请注意一些未展现出来的代码片段。我这边的效果预览…

html中可以有两个h1,在一个HTML中h1标签能出现几次?h1标签和标题标签

首页 > web前端 > html教程 > 正文 在一个HTML中h1标签能出现几次&#xff1f;h1标签和标题标签的差别是什么&#xff1f; 2018-08-29 10:57:28本篇文章主要介绍了关于HTML h1标签的一些解释&#xff0c;有html h1标签和html title标签的区别&#xff0c;还有网页中h1…

Java中的记录类型

2020年3月发布的JDK 14引入了记录 &#xff08;预览语言功能&#xff09;&#xff0c;这些记录提供了一种紧凑的语法来声明主要用于保存数据的类。 在记录中 &#xff0c;所有低级&#xff0c;重复且容易出错的代码都类似于构造函数&#xff0c;访问器和通用方法&#xff0c;例…

山东省102021年普通高考成绩查询,山东高考成绩今日发布!成绩查询看这里!

原标题&#xff1a;山东高考成绩今日发布&#xff01;成绩查询看这里&#xff01;山东高考生注意啦~今天16:20举行山东2020年夏季高考第二次新闻发布会届时将会公布高考录取政策、分数线情况等今天17:00公布2020夏季高考与等级考成绩发布会怎么看&#xff1f;高考成绩怎样查&am…

使用SoapUI调用不同的安全WCF SOAP服务-基本身份验证,第二部分

在本系列的第一篇文章中&#xff0c;我们创建了一个基本的身份验证服务&#xff0c;以使用SoapUI进行调用。 因此&#xff0c;在第二篇文章中&#xff0c;我们将逐步演示如何使用此工具成功调用这种服务。 使用SoapUI的1-Basic WCF SOAP –创建新的SOAP项目 首先&#xff0c;我…

html table nei边框线,GitHub - meichuanneiku/TableCell: 在TableBank的基础上,进一步标注到单元格精度,利用目标检测/分割实现单元格定位。...

项目说明本项目是我2019年7月份的实习工作的**展示与记录**&#xff1a;把倾斜的表格旋转水平&#xff1b;制作5000张表格数据集&#xff0c;需要标注每一个单元格&#xff0c;并实现单元格检测第一项比较简单&#xff0c;仿射变换、透视变换已经很成熟了&#xff0c;关键是第二…

前缀命名

如果您是第一次查看Takes或Cactoos的源代码&#xff0c;则很可能会像其他名称一样被命名约定触发&#xff0c;这意味着大多数类名称都有两个字母的前缀&#xff1a; BkSafe &#xff0c; RqFake &#xff0c; RsWithStatus &#xff0c; TkGzip等。 老实说&#xff0c;我还没有…

再访PMML

嗨伙计&#xff01; 从今年年初开始&#xff0c;就有了重新设计Drools PMML模块的计划。 在这篇文章中&#xff0c;我将描述我们将如何处理它&#xff0c;目前的状态&#xff0c;未来发展的想法等&#xff0c;等等……敬请期待&#xff01; 背景 PMML是一个标准&#xff0c;旨…

用计算机怎么弹离人愁数字,拇指琴新手入门曲谱——离人愁

喜欢古风的朋友赶快凑过来啦&#xff0c;最近抖音上超火的离人愁拇指琴教学&#xff0c;喜欢离人愁的小姐姐小哥哥赶快学起来啦&#xff01;以下琴谱适用于Hugh Tracey G调17键。南非琴出厂调音是G调排列&#xff0c;习惯了C调音阶排列的朋友可能对G调排列不是很适应。因为两者…

jvm7 jvm8_JVM PermGen –您在哪里?

jvm7 jvm8这篇文章介绍了JVM内存结构的一些基础知识&#xff0c;并快速窥视了PermGen&#xff0c;以了解自Java SE 8出现以来它已消失的地方。 裸基础 JVM只是系统上运行的另一个进程&#xff0c;魔术始于java命令。 像任何OS进程一样&#xff0c;它需要内存才能运行。 请记住…