最近做了一些xmpp方面的东西,把它写下来。
最近选用了openfire做xmpp server。所以下面的内容都是基于openfire来做的。
现在openfire最新的版本是3.7.1,下载下来,用eclipse来编译运行。或者下载安装包,可以安装在windows,Solaris上。
这两种方式我都用了一下。
关于在eclipse怎么编译运行openfire,可以参考下面这个链接,写的很全。照着做就可以了。
http://wenku.baidu.com/view/8acbbb305a8102d276a22fb5.html
我的计划是搭建一个HA的openfire系统。具体的讲就是部署两个openfire server,这两个server的domain都配置成一个(这里我用的是xmppservice)。然后在部署一个F5来做负载均衡和failover。这里我没有选择openfire的cluster plugin。因为我们是商业用途,cluster plugin需要买oracle coherence license。
我的逻辑业务用external component的方式来实现,并连接到openfire server上。
在写一个客户端来测试整个系统。
下面主要把external component的实现和客户端的实现记录下来。
客户端的实现很简单,主要用到的包是smack。
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.Message;
public class XmppClient {
public static void main(String args[]) throws XMPPException{
ConnectionConfiguration config = new ConnectionConfiguration(F5_ip_address,F5_port);
XMPPConnection.DEBUG_ENABLED = true;
XMPPConnection conn2 = new XMPPConnection(config);
boolean auth = conn2.getSASLAuthentication().isAuthenticated();
try {
conn2.connect();
} catch (XMPPException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(auth);
try {
conn2.login("client_name", "client_password");
} catch (XMPPException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
String service = conn2.getServiceName();
System.out.println(service);
PacketListener listener = new SimplePacketListener();
PacketFilter filter = new SimplePacketFilter();
conn2.addPacketListener(listener, filter);
buildMsg(conn2,i);
System.out.println("finished");
}
static void buildMsg(XMPPConnection conn2){
Message req = new Message();
req.setFrom("ilxlf@mymachine_name");
req.setTo("service1@service1.xmppservice");
req.setBody("This is client message");
System.out.println("message sent: "+req.toXML());
conn2.sendPacket(req);
System.out.println("finished");
}
}
这里用到了两个listener,现在没有什么太大的用途,主要用来显示server的response。
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.Packet;
public class SimplePacketFilter implements PacketFilter {
@Override
public boolean accept(Packet arg0) {
// TODO Auto-generated method stub
return true;
}
}
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.packet.Packet;
public class SimplePacketListener implements PacketListener {
@Override
public void processPacket(Packet arg0) {
// TODO Auto-generated method stub
System.out.println("received: "+arg0.toXML());
}
}
下面来说说external component的实现。
先说说配置部署。当我们写了一个external component,我们就需要把他连接到openfire server上。打开openfire的管理页面,默认是在9090端口上。进入之后在server-->server setting -->external components里面配置,默认连接到openfire的5275端口。然后我们需要配置sub-domain和一些访问控制策略。简单点,把sub-domain配好就可以用了(这里我用service1表示sub-domain)。
为了让我们的测试客户端能连接上了,我们在openfire上创建一个测试用户。在Users/Groups-->create new user里面。主要是用户名和密码。对应到上面的代码就是"client_name","client_password"里面的值。
下面就是具体的实现代码。本质上external component可以做成一个war应用,我这里为了方案的验证就做一个普通的java应用。
这里用到的包主要是tinder和whack。 一共三个包。
com.ilxlf.component
org.dom4j.io
org.xmpp.component
后两个包看上去很奇怪,本来是不应该有的。但是在写代码的过程中发现一些问题只能先这么解决一下。
package com.ilxlf.component;
import org.jivesoftware.whack.ExternalComponentManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.component.AbstractComponent;
import org.xmpp.component.ComponentException;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
public class ExampleComponent extends AbstractComponent {
private static final JID serviceAddress = new JID("service1@service1.xmppservice");
Logger log = LoggerFactory.getLogger(getClass());
public ExampleComponent(){
super(2,1000,true);
}
@Override
public String getDomain() {
// TODO Auto-generated method stub
return "service1";
}
@Override
protected void handleMessage(Message message) {
// TODO Auto-generated method stub
//super.handleMessage(message);
System.out.println(message.toString());
Message response = new Message();
response.setFrom(serviceAddress);
response.setTo(message.getFrom());
response.setBody("give client a response.....");
send(response);
}
@Override
public String getDescription() {
// TODO Auto-generated method stub
return "This is pengfu first xmpp component.";
}
@Override
public String getName() {
// TODO Auto-generated method stub
return "service1.xmppservice";
}
public static void main(String args[]) throws ComponentException{
ExampleComponent comp = new ExampleComponent();
final ExternalComponentManager mgr = new ExternalComponentManager("xmppservice", 5275);
mgr.setSecretKey("service1", "123456");
mgr.addComponent("service1", comp);
while(true){
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package org.dom4j.io;
import org.dom4j.*;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.*;
import java.net.URL;
public class XPPPacketReader {
/**
* <code>DocumentFactory</code> used to create new document objects
*/
private DocumentFactory factory;
/**
* <code>XmlPullParser</code> used to parse XML
*/
private XmlPullParser xppParser;
/**
* <code>XmlPullParser</code> used to parse XML
*/
private XmlPullParserFactory xppFactory;
/**
* DispatchHandler to call when each <code>Element</code> is encountered
*/
private DispatchHandler dispatchHandler;
public XPPPacketReader() {
}
public XPPPacketReader(DocumentFactory factory) {
this.factory = factory;
}
/**
* <p>Reads a Document from the given <code>File</code></p>
*
* @param file is the <code>File</code> to read from.
* @return the newly created Document instance
* @throws DocumentException if an error occurs during parsing.
* @throws java.net.MalformedURLException if a URL could not be made for the given File
*/
public Document read(File file) throws DocumentException, IOException, XmlPullParserException {
String systemID = file.getAbsolutePath();
return read(new BufferedReader(new FileReader(file)), systemID);
}
/**
* <p>Reads a Document from the given <code>URL</code></p>
*
* @param url <code>URL</code> to read from.
* @return the newly created Document instance
* @throws DocumentException if an error occurs during parsing.
*/
public Document read(URL url) throws DocumentException, IOException, XmlPullParserException {
String systemID = url.toExternalForm();
return read(createReader(url.openStream()), systemID);
}
/**
* <p>Reads a Document from the given URL or filename.</p>
* <p/>
* <p/>
* If the systemID contains a <code>':'</code> character then it is
* assumed to be a URL otherwise its assumed to be a file name.
* If you want finer grained control over this mechansim then please
* explicitly pass in either a {@link URL} or a {@link File} instance
* instead of a {@link String} to denote the source of the document.
* </p>
*
* @param systemID is a URL for a document or a file name.
* @return the newly created Document instance
* @throws DocumentException if an error occurs during parsing.
* @throws java.net.MalformedURLException if a URL could not be made for the given File
*/
public Document read(String systemID) throws DocumentException, IOException, XmlPullParserException {
if (systemID.indexOf(':') >= 0) {
// lets assume its a URL
return read(new URL(systemID));
}
else {
// lets assume that we are given a file name
return read(new File(systemID));
}
}
/**
* <p>Reads a Document from the given stream</p>
*
* @param in <code>InputStream</code> to read from.
* @return the newly created Document instance
* @throws DocumentException if an error occurs during parsing.
*/
public Document read(InputStream in) throws DocumentException, IOException, XmlPullParserException {
return read(createReader(in));
}
/**
* <p>Reads a Document from the given <code>Reader</code></p>
*
* @param reader is the reader for the input
* @return the newly created Document instance
* @throws DocumentException if an error occurs during parsing.
*/
public Document read(Reader reader) throws DocumentException, IOException, XmlPullParserException {
getXPPParser().setInput(reader);
return parseDocument();
}
/**
* <p>Reads a Document from the given array of characters</p>
*
* @param text is the text to parse
* @return the newly created Document instance
* @throws DocumentException if an error occurs during parsing.
*/
public Document read(char[] text) throws DocumentException, IOException, XmlPullParserException {
getXPPParser().setInput(new CharArrayReader(text));
return parseDocument();
}
/**
* <p>Reads a Document from the given stream</p>
*
* @param in <code>InputStream</code> to read from.
* @param systemID is the URI for the input
* @return the newly created Document instance
* @throws DocumentException if an error occurs during parsing.
*/
public Document read(InputStream in, String systemID) throws DocumentException, IOException, XmlPullParserException {
return read(createReader(in), systemID);
}
/**
* <p>Reads a Document from the given <code>Reader</code></p>
*
* @param reader is the reader for the input
* @param systemID is the URI for the input
* @return the newly created Document instance
* @throws DocumentException if an error occurs during parsing.
*/
public Document read(Reader reader, String systemID) throws DocumentException, IOException, XmlPullParserException {
Document document = read(reader);
document.setName(systemID);
return document;
}
// Properties
//-------------------------------------------------------------------------
public XmlPullParser getXPPParser() throws XmlPullParserException {
if (xppParser == null) {
xppParser = getXPPFactory().newPullParser();
}
return xppParser;
}
public XmlPullParserFactory getXPPFactory() throws XmlPullParserException {
if (xppFactory == null) {
xppFactory = XmlPullParserFactory.newInstance();
}
xppFactory.setNamespaceAware(true);
return xppFactory;
}
public void setXPPFactory(XmlPullParserFactory xppFactory) {
this.xppFactory = xppFactory;
}
/**
* @return the <code>DocumentFactory</code> used to create document objects
*/
public DocumentFactory getDocumentFactory() {
if (factory == null) {
factory = DocumentFactory.getInstance();
}
return factory;
}
/**
* <p>This sets the <code>DocumentFactory</code> used to create new documents.
* This method allows the building of custom DOM4J tree objects to be implemented
* easily using a custom derivation of {@link DocumentFactory}</p>
*
* @param factory <code>DocumentFactory</code> used to create DOM4J objects
*/
public void setDocumentFactory(DocumentFactory factory) {
this.factory = factory;
}
/**
* Adds the <code>ElementHandler</code> to be called when the
* specified path is encounted.
*
* @param path is the path to be handled
* @param handler is the <code>ElementHandler</code> to be called
* by the event based processor.
*/
public void addHandler(String path, ElementHandler handler) {
getDispatchHandler().addHandler(path, handler);
}
/**
* Removes the <code>ElementHandler</code> from the event based
* processor, for the specified path.
*
* @param path is the path to remove the <code>ElementHandler</code> for.
*/
public void removeHandler(String path) {
getDispatchHandler().removeHandler(path);
}
/**
* When multiple <code>ElementHandler</code> instances have been
* registered, this will set a default <code>ElementHandler</code>
* to be called for any path which does <b>NOT</b> have a handler
* registered.
*
* @param handler is the <code>ElementHandler</code> to be called
* by the event based processor.
*/
public void setDefaultHandler(ElementHandler handler) {
getDispatchHandler().setDefaultHandler(handler);
}
// Implementation methods
//-------------------------------------------------------------------------
public Document parseDocument() throws DocumentException, IOException, XmlPullParserException {
DocumentFactory df = getDocumentFactory();
Document document = df.createDocument();
Element parent = null;
XmlPullParser pp = getXPPParser();
int count = 0;
while (true) {
int type = -1;
type = pp.nextToken();
switch (type) {
case XmlPullParser.PROCESSING_INSTRUCTION: {
String text = pp.getText();
int loc = text.indexOf(" ");
if (loc >= 0) {
document.addProcessingInstruction(text.substring(0, loc), text.substring(loc + 1));
}
else
document.addProcessingInstruction(text, "");
break;
}
case XmlPullParser.COMMENT: {
if (parent != null)
parent.addComment(pp.getText());
else
document.addComment(pp.getText());
break;
}
case XmlPullParser.CDSECT: {
String text = pp.getText();
if (parent != null) {
parent.addCDATA(text);
}
else {
if (text.trim().length() > 0) {
throw new DocumentException("Cannot have text content outside of the root document");
}
}
break;
}
case XmlPullParser.ENTITY_REF: {
String text = pp.getText();
if (parent != null) {
parent.addText(text);
}
else {
if (text.trim().length() > 0) {
throw new DocumentException("Cannot have an entityref outside of the root document");
}
}
break;
}
case XmlPullParser.END_DOCUMENT: {
return document;
}
case XmlPullParser.START_TAG: {
QName qname = (pp.getPrefix() == null) ? df.createQName(pp.getName(), pp.getNamespace()) : df.createQName(pp.getName(), pp.getPrefix(), pp.getNamespace());
Element newElement = null;
// Do not include the namespace if this is the start tag of a new packet
// This avoids including "jabber:client", "jabber:server" or
// "jabber:component:accept"
if ("jabber:client".equals(qname.getNamespaceURI()) ||
"jabber:server".equals(qname.getNamespaceURI()) ||
"jabber:component:accept".equals(qname.getNamespaceURI()) ||
"http://jabber.org/protocol/httpbind".equals(qname.getNamespaceURI())) {
newElement = df.createElement(pp.getName());
}
else {
newElement = df.createElement(qname);
}
int nsStart = pp.getNamespaceCount(pp.getDepth() - 1);
int nsEnd = pp.getNamespaceCount(pp.getDepth());
for (int i = nsStart; i < nsEnd; i++)
if (pp.getNamespacePrefix(i) != null)
newElement.addNamespace(pp.getNamespacePrefix(i), pp.getNamespaceUri(i));
for (int i = 0; i < pp.getAttributeCount(); i++) {
QName qa = (pp.getAttributePrefix(i) == null) ? df.createQName(pp.getAttributeName(i)) : df.createQName(pp.getAttributeName(i), pp.getAttributePrefix(i), pp.getAttributeNamespace(i));
newElement.addAttribute(qa, pp.getAttributeValue(i));
}
if (parent != null) {
parent.add(newElement);
}
else {
document.add(newElement);
}
parent = newElement;
count++;
break;
}
case XmlPullParser.END_TAG: {
if (parent != null) {
parent = parent.getParent();
}
count--;
if (count < 1) {
return document;
}
break;
}
case XmlPullParser.TEXT: {
String text = pp.getText();
if (parent != null) {
parent.addText(text);
}
else {
if (text.trim().length() > 0) {
throw new DocumentException("Cannot have text content outside of the root document");
}
}
break;
}
default:
{
;
}
}
}
}
protected DispatchHandler getDispatchHandler() {
if (dispatchHandler == null) {
dispatchHandler = new DispatchHandler();
}
return dispatchHandler;
}
protected void setDispatchHandler(DispatchHandler dispatchHandler) {
this.dispatchHandler = dispatchHandler;
}
/**
* Factory method to create a Reader from the given InputStream.
*/
protected Reader createReader(InputStream in) throws IOException {
return new BufferedReader(new InputStreamReader(in));
}
}
/*
* Redistribution and use of this software and associated documentation
* ("Software"), with or without modification, are permitted provided
* that the following conditions are met:
*
* 1. Redistributions of source code must retain copyright
* statements and notices. Redistributions must also contain a
* copy of this document.
*
* 2. Redistributions in binary form must reproduce the
* above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* 3. The name "DOM4J" must not be used to endorse or promote
* products derived from this Software without prior written
* permission of MetaStuff, Ltd. For written permission,
* please contact dom4j-info@metastuff.com.
*
* 4. Products derived from this Software may not be called "DOM4J"
* nor may "DOM4J" appear in their names without prior written
* permission of MetaStuff, Ltd. DOM4J is a registered
* trademark of MetaStuff, Ltd.
*
* 5. Due credit should be given to the DOM4J Project -
* http://www.dom4j.org
*
* THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Copyright 2001-2004 (C) MetaStuff, Ltd. All Rights Reserved.
*
* $Id: XPPPacketReader.java 6408 2006-12-15 23:35:30Z gato $
*/
package org.xmpp.component;
public interface Log {
}
用到的jar包是:
下一步的工作:
如果一个server连接多个相同业务的external component,是否可以在这些component之间做负载均衡和failover。
openfire的pub-sub的使用。
dd