关于将XMPP server部署到Tomcat上的一些问题
- 在XMPP消息推送这个问题上,网上已经有很多资料了,本人觉得很好的一篇资料是:http://www.iteye.com/topic/1117043
- 提供了一个连接下载源码:http://115.com/file/bhkfse3i#%20Androidpn.rar
- 很感谢前辈们的研究结果。
- 在源码的使用过程中要注意的地方有两点,网上的那篇资料好像忽略了一个重要的地方,就是要改resources文件夹下面的jdbc.properties,将里面关于数据库的配置改为自己的,另一个需要注意的地方就是改android端的ip了。
在项目部署到tomcat下之后,发现了不少的bug,其中一个就是当tomcat重新启动,客户端的连接将断开,不能进行自动重连。
对于这个BUG,我们可以在Androidpn-clieng下的XmppManager这个类中做简要的处理即可修改。源码如下:
1 /* 2 * Copyright (C) 2010 Moduad Co., Ltd. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package org.androidpn.client; 17 18 import java.util.ArrayList; 19 import java.util.List; 20 import java.util.UUID; 21 import java.util.concurrent.Future; 22 23 import org.jivesoftware.smack.ConnectionConfiguration; 24 import org.jivesoftware.smack.ConnectionListener; 25 import org.jivesoftware.smack.PacketListener; 26 import org.jivesoftware.smack.XMPPConnection; 27 import org.jivesoftware.smack.XMPPException; 28 import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; 29 import org.jivesoftware.smack.filter.AndFilter; 30 import org.jivesoftware.smack.filter.PacketFilter; 31 import org.jivesoftware.smack.filter.PacketIDFilter; 32 import org.jivesoftware.smack.filter.PacketTypeFilter; 33 import org.jivesoftware.smack.packet.IQ; 34 import org.jivesoftware.smack.packet.Packet; 35 import org.jivesoftware.smack.packet.Registration; 36 import org.jivesoftware.smack.provider.ProviderManager; 37 38 import android.content.Context; 39 import android.content.SharedPreferences; 40 import android.content.SharedPreferences.Editor; 41 import android.os.Handler; 42 import android.util.Log; 43 44 45 /** 46 * This class is to manage the XMPP connection between client and server. 47 * 48 * @author Sehwan Noh (devnoh@gmail.com) 49 */ 50 public class XmppManager { 51 52 private static final String LOGTAG = LogUtil.makeLogTag(XmppManager.class); 53 54 private static final String XMPP_RESOURCE_NAME = "AndroidpnClient"; 55 56 private Context context; 57 58 private NotificationService.TaskSubmitter taskSubmitter; 59 60 private NotificationService.TaskTracker taskTracker; 61 62 private SharedPreferences sharedPrefs; 63 64 private String xmppHost; 65 66 private int xmppPort; 67 68 private XMPPConnection connection; 69 70 private String username; 71 72 private String password; 73 74 private ConnectionListener connectionListener; 75 76 private PacketListener notificationPacketListener; 77 78 private Handler handler; 79 80 private List<Runnable> taskList; 81 82 private boolean running = false; 83 84 private Future<?> futureTask; 85 86 private Thread reconnection; 87 88 public XmppManager(NotificationService notificationService) { 89 context = notificationService; 90 taskSubmitter = notificationService.getTaskSubmitter(); 91 taskTracker = notificationService.getTaskTracker(); 92 sharedPrefs = notificationService.getSharedPreferences(); 93 94 xmppHost = sharedPrefs.getString(Constants.XMPP_HOST, "localhost"); 95 xmppPort = sharedPrefs.getInt(Constants.XMPP_PORT, 5222); 96 username = sharedPrefs.getString(Constants.XMPP_USERNAME, ""); 97 password = sharedPrefs.getString(Constants.XMPP_PASSWORD, ""); 98 99 connectionListener = new PersistentConnectionListener(this); 100 notificationPacketListener = new NotificationPacketListener(this); 101 102 handler = new Handler(); 103 taskList = new ArrayList<Runnable>(); 104 reconnection = new ReconnectionThread(this); 105 } 106 107 public Context getContext() { 108 return context; 109 } 110 111 public void connect() { 112 Log.d(LOGTAG, "connect()..."); 113 submitLoginTask(); 114 } 115 116 public void disconnect() { 117 Log.d(LOGTAG, "disconnect()..."); 118 terminatePersistentConnection(); 119 } 120 121 public void terminatePersistentConnection() { 122 Log.d(LOGTAG, "terminatePersistentConnection()..."); 123 Runnable runnable = new Runnable() { 124 125 final XmppManager xmppManager = XmppManager.this; 126 127 public void run() { 128 if (xmppManager.isConnected()) { 129 Log.d(LOGTAG, "terminatePersistentConnection()... run()"); 130 xmppManager.getConnection().removePacketListener( 131 xmppManager.getNotificationPacketListener()); 132 xmppManager.getConnection().disconnect(); 133 } 134 xmppManager.runTask(); 135 } 136 137 }; 138 addTask(runnable); 139 } 140 141 public XMPPConnection getConnection() { 142 return connection; 143 } 144 145 public void setConnection(XMPPConnection connection) { 146 this.connection = connection; 147 } 148 149 public String getUsername() { 150 return username; 151 } 152 153 public void setUsername(String username) { 154 this.username = username; 155 } 156 157 public String getPassword() { 158 return password; 159 } 160 161 public void setPassword(String password) { 162 this.password = password; 163 } 164 165 public ConnectionListener getConnectionListener() { 166 return connectionListener; 167 } 168 169 public PacketListener getNotificationPacketListener() { 170 return notificationPacketListener; 171 } 172 173 public void startReconnectionThread() { 174 synchronized (reconnection) { 175 if (!reconnection.isAlive()) { 176 reconnection.setName("Xmpp Reconnection Thread"); 177 reconnection.start(); 178 } 179 } 180 } 181 182 public Handler getHandler() { 183 return handler; 184 } 185 186 public void reregisterAccount() { 187 removeAccount(); 188 submitLoginTask(); 189 runTask(); 190 } 191 192 public List<Runnable> getTaskList() { 193 return taskList; 194 } 195 196 public Future<?> getFutureTask() { 197 return futureTask; 198 } 199 200 public void runTask() { 201 Log.d(LOGTAG, "runTask()..."); 202 synchronized (taskList) { 203 running = false; 204 futureTask = null; 205 if (!taskList.isEmpty()) { 206 Runnable runnable = (Runnable) taskList.get(0); 207 taskList.remove(0); 208 running = true; 209 futureTask = taskSubmitter.submit(runnable); 210 if (futureTask == null) { 211 taskTracker.decrease(); 212 } 213 } 214 } 215 taskTracker.decrease(); 216 Log.d(LOGTAG, "runTask()...done"); 217 } 218 219 private String newRandomUUID() { 220 String uuidRaw = UUID.randomUUID().toString(); 221 return uuidRaw.replaceAll("-", ""); 222 } 223 224 private boolean isConnected() { 225 return connection != null && connection.isConnected(); 226 } 227 228 private boolean isAuthenticated() { 229 return connection != null && connection.isConnected() 230 && connection.isAuthenticated(); 231 } 232 233 private boolean isRegistered() { 234 return sharedPrefs.contains(Constants.XMPP_USERNAME) 235 && sharedPrefs.contains(Constants.XMPP_PASSWORD); 236 } 237 238 private void submitConnectTask() { 239 Log.d(LOGTAG, "submitConnectTask()..."); 240 addTask(new ConnectTask()); 241 } 242 243 private void submitRegisterTask() { 244 Log.d(LOGTAG, "submitRegisterTask()..."); 245 submitConnectTask(); 246 addTask(new RegisterTask()); 247 } 248 249 private void submitLoginTask() { 250 Log.d(LOGTAG, "submitLoginTask()..."); 251 submitRegisterTask(); 252 addTask(new LoginTask()); 253 } 254 255 private void addTask(Runnable runnable) { 256 Log.d(LOGTAG, "addTask(runnable)..."); 257 taskTracker.increase(); 258 synchronized (taskList) { 259 if (taskList.isEmpty() && !running) { 260 running = true; 261 futureTask = taskSubmitter.submit(runnable); 262 if (futureTask == null) { 263 taskTracker.decrease(); 264 } 265 } else { 266 taskList.add(runnable); 267 } 268 } 269 Log.d(LOGTAG, "addTask(runnable)... done"); 270 } 271 272 private void removeAccount() { 273 Editor editor = sharedPrefs.edit(); 274 editor.remove(Constants.XMPP_USERNAME); 275 editor.remove(Constants.XMPP_PASSWORD); 276 editor.commit(); 277 } 278 279 /** 280 * A runnable task to connect the server. 281 */ 282 private class ConnectTask implements Runnable { 283 284 final XmppManager xmppManager; 285 286 private ConnectTask() { 287 this.xmppManager = XmppManager.this; 288 } 289 290 public void run() { 291 Log.i(LOGTAG, "ConnectTask.run()..."); 292 293 if (!xmppManager.isConnected()) { 294 // Create the configuration for this new connection 295 ConnectionConfiguration connConfig = new ConnectionConfiguration( 296 xmppHost, xmppPort); 297 // connConfig.setSecurityMode(SecurityMode.disabled); 298 connConfig.setSecurityMode(SecurityMode.required); 299 connConfig.setSASLAuthenticationEnabled(false); 300 connConfig.setCompressionEnabled(false); 301 302 XMPPConnection connection = new XMPPConnection(connConfig); 303 xmppManager.setConnection(connection); 304 305 try { 306 // Connect to the server 307 connection.connect(); 308 Log.i(LOGTAG, "XMPP connected successfully"); 309 310 // packet provider 311 ProviderManager.getInstance().addIQProvider("notification", 312 "androidpn:iq:notification", 313 new NotificationIQProvider()); 314 315 } catch (XMPPException e) { 316 Log.e(LOGTAG, "XMPP connection failed", e); 317 } 318 319 xmppManager.runTask(); 320 321 } else { 322 Log.i(LOGTAG, "XMPP connected already"); 323 xmppManager.runTask(); 324 } 325 } 326 } 327 328 /** 329 * A runnable task to register a new user onto the server. 330 */ 331 private class RegisterTask implements Runnable { 332 333 final XmppManager xmppManager; 334 335 private RegisterTask() { 336 xmppManager = XmppManager.this; 337 } 338 339 public void run() { 340 Log.i(LOGTAG, "RegisterTask.run()..."); 341 342 //如果账号不存在的话,随机生成一个uuid的用户名和mima 343 if (!xmppManager.isRegistered()) { 344 final String newUsername = newRandomUUID(); 345 final String newPassword = newRandomUUID(); 346 // final String newUsername = "af100042487d4b06a49adda8c3a82d41"; 347 // final String newPassword = "af100042487d4b06a49adda8c3a82d41"; 348 349 Registration registration = new Registration(); 350 351 PacketFilter packetFilter = new AndFilter(new PacketIDFilter( 352 registration.getPacketID()), new PacketTypeFilter( 353 IQ.class)); 354 355 PacketListener packetListener = new PacketListener() { 356 357 public void processPacket(Packet packet) { 358 Log.d("RegisterTask.PacketListener", 359 "processPacket()....."); 360 Log.d("RegisterTask.PacketListener", "packet=" 361 + packet.toXML()); 362 363 if (packet instanceof IQ) { 364 IQ response = (IQ) packet; 365 if (response.getType() == IQ.Type.ERROR) { 366 if (!response.getError().toString().contains( 367 "409")) { 368 Log.e(LOGTAG, 369 "Unknown error while registering XMPP account! " 370 + response.getError() 371 .getCondition()); 372 } 373 } else if (response.getType() == IQ.Type.RESULT) { 374 xmppManager.setUsername(newUsername); 375 xmppManager.setPassword(newPassword); 376 Log.d(LOGTAG, "username=" + newUsername); 377 Log.d(LOGTAG, "password=" + newPassword); 378 379 Editor editor = sharedPrefs.edit(); 380 editor.putString(Constants.XMPP_USERNAME, 381 newUsername); 382 editor.putString(Constants.XMPP_PASSWORD, 383 newPassword); 384 editor.commit(); 385 Log 386 .i(LOGTAG, 387 "Account registered successfully"); 388 xmppManager.runTask(); 389 } 390 } 391 } 392 }; 393 394 connection.addPacketListener(packetListener, packetFilter); 395 396 registration.setType(IQ.Type.SET); 397 // registration.setTo(xmppHost); 398 // Map<String, String> attributes = new HashMap<String, String>(); 399 // attributes.put("username", rUsername); 400 // attributes.put("password", rPassword); 401 // registration.setAttributes(attributes); 402 registration.addAttribute("username", newUsername); 403 registration.addAttribute("password", newPassword); 404 connection.sendPacket(registration); 405 406 } else { 407 Log.i(LOGTAG, "Account registered already"); 408 xmppManager.runTask(); 409 } 410 } 411 } 412 413 /** 414 * A runnable task to log into the server. 415 */ 416 private class LoginTask implements Runnable { 417 418 final XmppManager xmppManager; 419 420 private LoginTask() { 421 this.xmppManager = XmppManager.this; 422 } 423 424 public void run() { 425 Log.i(LOGTAG, "LoginTask.run()..."); 426 427 if (!xmppManager.isAuthenticated()) { 428 Log.d(LOGTAG, "username=" + username); 429 Log.d(LOGTAG, "password=" + password); 430 431 try { 432 xmppManager.getConnection().login( 433 xmppManager.getUsername(), 434 xmppManager.getPassword(), XMPP_RESOURCE_NAME); 435 Log.d(LOGTAG, "Loggedn in successfully"); 436 437 // connection listener 438 if (xmppManager.getConnectionListener() != null) { 439 xmppManager.getConnection().addConnectionListener( 440 xmppManager.getConnectionListener()); 441 } 442 443 // packet filter 444 PacketFilter packetFilter = new PacketTypeFilter( 445 NotificationIQ.class); 446 // packet listener 447 PacketListener packetListener = xmppManager 448 .getNotificationPacketListener(); 449 connection.addPacketListener(packetListener, packetFilter); 450 //判断是否处于连接状态(添加) 451 if(!getConnection().isConnected()) 452 { 453 xmppManager.runTask(); 454 } 455 xmppManager.runTask(); 456 } catch (XMPPException e) { 457 Log.e(LOGTAG, "LoginTask.run()... xmpp error"); 458 Log.e(LOGTAG, "Failed to login to xmpp server. Caused by: " 459 + e.getMessage()); 460 String INVALID_CREDENTIALS_ERROR_CODE = "401"; 461 String errorMessage = e.getMessage(); 462 if (errorMessage != null 463 && errorMessage 464 .contains(INVALID_CREDENTIALS_ERROR_CODE)) { 465 xmppManager.reregisterAccount(); 466 return; 467 } 468 xmppManager.startReconnectionThread(); 469 470 } catch (Exception e) { 471 Log.e(LOGTAG, "LoginTask.run()... other error"); 472 Log.e(LOGTAG, "Failed to login to xmpp server. Caused by: " 473 + e.getMessage()); 474 xmppManager.startReconnectionThread(); 475 } 476 //添加 477 xmppManager.runTask(); 478 } else { 479 Log.i(LOGTAG, "Logged in already"); 480 xmppManager.runTask(); 481 } 482 483 } 484 } 485 486 }
新添加代码450-454行和477行
还有一个问题是:当客户端的用户有不在线的时候,消息应怎么进行推送,是直接忽略呢还是下次登录的时候在进行推送,想qq那样,很显然对已一个具体的实用项目来说是不能忽略的,那么怎么进行消息的离线推送呢,下次告诉大家,因为我现在还没解决,不过快了。和大家说下我的思路吧:在androidpn服务端有一个UserController这个类,源码如下:
View Code
1 /* 2 * Copyright (C) 2010 Moduad Co., Ltd. 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation; either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License along 15 * with this program; if not, write to the Free Software Foundation, Inc., 16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 */ 18 package org.androidpn.server.console.controller; 19 20 import java.util.List; 21 22 import javax.servlet.http.HttpServletRequest; 23 import javax.servlet.http.HttpServletResponse; 24 25 import org.androidpn.server.model.User; 26 import org.androidpn.server.service.ServiceLocator; 27 import org.androidpn.server.service.UserService; 28 import org.androidpn.server.xmpp.presence.PresenceManager; 29 import org.springframework.web.servlet.ModelAndView; 30 import org.springframework.web.servlet.mvc.multiaction.MultiActionController; 31 32 /** 33 * A controller class to process the user related requests. 34 * 35 * @author Sehwan Noh (devnoh@gmail.com) 36 */ 37 public class UserController extends MultiActionController { 38 39 private UserService userService; 40 41 public UserController() { 42 userService = ServiceLocator.getUserService(); 43 } 44 45 //用户列表 46 public ModelAndView list(HttpServletRequest request, 47 HttpServletResponse response) throws Exception { 48 PresenceManager presenceManager = new PresenceManager(); 49 List<User> userList = userService.getUsers(); 50 for (User user : userList) { 51 if (presenceManager.isAvailable(user)) { 52 // Presence presence = presenceManager.getPresence(user); 53 user.setOnline(true); 54 } else { 55 user.setOnline(false); 56 } 57 // logger.debug("user.online=" + user.isOnline()); 58 } 59 ModelAndView mav = new ModelAndView(); 60 mav.addObject("userList", userList); 61 mav.setViewName("user/list"); 62 return mav; 63 } 64 65 }
该源码里面有用户是否在线的判断,我们只要将用户的列表取出来,如果用户在线就将消息进行推送,如果有不在线的用户,我们就把该消息放到缓存中(也可以放到数据库中更加保险),当然为了防止用户过长没有登陆系统,导致下次登录时出现过多托送过来的消息,我们还可以在服务端进行设置时间,比如服务端只缓存近N天的消息,利用
session.getCreationDate();
session.getLastActiveDate();
这两句代码应该可以完成,本人没尝试过,还不知道。效果如如下:
posted on 2012-04-28 19:01 Keep Running 阅读(11446) 评论(6) 编辑 收藏 举报