📄 packetprocessor.java
字号:
/*
* LumaQQ - Java QQ Client
*
* Copyright (C) 2004 luma <stubma@163.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.tsinghua.lumaqq.qq;
import java.util.Date;
import java.util.Iterator;
import java.util.Vector;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.tsinghua.lumaqq.qq.beans.FriendOnlineEntry;
import edu.tsinghua.lumaqq.qq.beans.FriendStatus;
import edu.tsinghua.lumaqq.qq.beans.OnlineUser;
import edu.tsinghua.lumaqq.qq.beans.QQFriend;
import edu.tsinghua.lumaqq.qq.beans.QQUser;
import edu.tsinghua.lumaqq.qq.events.PacketEvent;
import edu.tsinghua.lumaqq.qq.events.PacketListener;
import edu.tsinghua.lumaqq.qq.events.QQEvent;
import edu.tsinghua.lumaqq.qq.events.QQListener;
import edu.tsinghua.lumaqq.qq.packets.InPacket;
import edu.tsinghua.lumaqq.qq.packets.in.AddFriendAuthReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.in.AddFriendReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.in.ChangeStatusReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.in.ClusterCommandReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.in.DeleteFriendReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.in.DownloadGroupFriendReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.in.FriendChangeStatusPacket;
import edu.tsinghua.lumaqq.qq.packets.in.FriendRemarkOpReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.in.GetFriendListReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.in.GetFriendOnlineReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.in.GetUserInfoReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.in.GroupNameOpReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.in.KeepAliveReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.in.LoginReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.in.ModifyInfoReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.in.ReceiveIMPacket;
import edu.tsinghua.lumaqq.qq.packets.in.RemoveSelfReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.in.SearchUserReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.in.SendIMReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.in.SystemNotificationPacket;
import edu.tsinghua.lumaqq.qq.packets.in.RequestKeyReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.in.UploadGroupFriendReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.out.ClusterCommandPacket;
import edu.tsinghua.lumaqq.qq.packets.out.ModifyInfoPacket;
import edu.tsinghua.lumaqq.qq.packets.out.SendIMPacket;
/**
* 这个类用来处理收到的数据,其注册为IRecevier的包事件监听器,这样每当一个包到来时便
* 被调用处理
*
* @author 马若劼
*/
class PacketProcessor implements PacketListener {
// Log对象
static Log log = LogFactory.getLog(PacketProcessor.class);
// QQ客户端
private QQClient client;
// QQ用户,我们将在收到登陆包后设置user不为null,所以如果user为null意味着尚未登陆
private QQUser user;
// QQ监听器
private Vector qqListeners, qqListenersBackup;
// 收到的包计数,注意这个是没有冲突的包的计数,所以超过65535就清0
private int total;
// 检查重复包的类
private PacketMonitor monitor;
// 是否监听器有改变,这是为了处理同步问题而设置的,在实际fireQQEvent时,
// 不使用qqListeners,而是用backup,这样在事件触发期间如果要修改
// 监听器也是可以的,不然有可能造成并发修改异常。如果有更好的办法当
// 然,目前只想出这种办法
private boolean listenerChanged;
private static int i = 0;
/**
* @param udp 如果要以UDP方式解析包,则为true
*/
public PacketProcessor(QQClient client) {
this.client = client;
this.user = null;
this.qqListeners = new Vector();
this.qqListenersBackup = new Vector();
this.total = 0;
this.monitor = PacketMonitor.getInstance();
this.listenerChanged = false;
}
// 处理登陆应答
private void processLoginReply(InPacket in) throws PacketParseException {
QQEvent e;
LoginReplyPacket packet = (LoginReplyPacket)in;
// 开始判断应答内容
switch(packet.replyCode) {
case QQ.QQ_LOGIN_REPLY_OK:
log.debug("登陆成功,用户IP为: " + Utils.getIpStringFromBytes(packet.ip) + " 端口: " + packet.port);
// 登陆成功的话我们就设置用户的一些信息,然后触发事件
user.setSessionKey(packet.sessionKey);
user.setIp(packet.ip);
user.setServerIp(packet.serverIp);
user.setLastLoginIp(packet.lastLoginIp);
user.setPort(packet.port);
user.setServerPort(packet.serverPort);
user.setLoginTime(packet.loginTime);
user.setLastLoginTime(packet.lastLoginTime);
// 得到文件传输密钥
byte[] b = new byte[4 + QQ.QQ_KEY_LENGTH];
b[0] = (byte)((user.getQqNum() >>> 24) & 0xFF);
b[1] = (byte)((user.getQqNum() >>> 16) & 0xFF);
b[2] = (byte)((user.getQqNum() >>> 8) & 0xFF);
b[3] = (byte)(user.getQqNum() & 0xFF);
System.arraycopy(user.getSessionKey(), 0, b, 4, QQ.QQ_KEY_LENGTH);
user.setFileSessionKey(Utils.doMD5(b));
// 触发事件
e = new QQEvent(packet);
e.type = QQEvent.QQ_LOGIN_SUCCESS;
this.fireQQEvent(e);
break;
case QQ.QQ_LOGIN_REPLY_PWD_ERROR:
log.debug("用户密码错误,错误信息: " + packet.replyMessage);
// 如果是密码错误,触发事件
e = new QQEvent(packet);
e.type = QQEvent.QQ_LOGIN_PASSWROD_ERROR;
this.fireQQEvent(e);
break;
case QQ.QQ_LOGIN_REPLY_REDIRECT:
log.debug("登陆重定向");
// 如果是登陆重定向,继续登陆
this.user = null;
client.setLoginRedirect(true);
client.login(Utils.getIpStringFromBytes(packet.redirectIp), packet.redirectPort);
break;
case QQ.QQ_LOGIN_REPLY_MISC_ERROR:
log.debug("未知登陆错误");
// 如果是其他未知错误,触发事件
e = new QQEvent(packet);
e.type = QQEvent.QQ_LOGIN_UNKNOWN_ERROR;
this.fireQQEvent(e);
break;
}
}
/* (non-Javadoc)
* @see edu.tsinghua.lumaqq.protocol.qq.PacketListener#packetArrived(edu.tsinghua.lumaqq.protocol.qq.PacketEvent)
*/
public void packetArrived(PacketEvent e) {
InPacket in = (InPacket) e.getSource();
log.debug("开始处理" + in.toString());
// 检查是否是在登陆之前收到的包,如果是,推迟处理该包
if(in.getCommand() != QQ.QQ_CMD_LOGIN && (user == null || !user.isLoggedIn())) {
/*
* 以下代码用来将在登录之前收到的包推回队列延迟处理,但是在实际中观察发现,登录之前收到的
* 东西基本没用,所以在这里不启用这段代码
*/
// 把这个包压回队列延迟处理
//client.getPort().pushBack(in);
//log.debug("在登陆之前收到了其他包,压回队列延迟处理,包序号为: " + (int)in.getSequence());
return;
}
// 这里检查是不是服务器发回的确认包
// 为什么要检查这个呢,因为有些包是服务器主动发出的,对于这些包我们是不需要
// 从超时发送队列中移除的,如果是服务器的确认包,那么我们就需要把超时发送队列
// 中包移除
switch (in.getCommand()) {
// 这三种包是服务器先发出的,我们要确认
case QQ.QQ_CMD_RECV_IM:
case QQ.QQ_CMD_RECV_MSG_SYS:
case QQ.QQ_CMD_RECV_MSG_FRIEND_CHANGE_STATUS:
break;
// 其他情况我们删除超时队列中的包
default:
IPort port = client.getPort();
if(port != null) port.removeResend(in);
log.debug("包" + (int)in.getSequence() + "的确认已经收到,将不再发送");
break;
}
// 现在开始判断包的类型,作出相应的处理
try {
switch (in.getCommand()) {
case QQ.QQ_CMD_KEEP_ALIVE:
processKeepAliveReply(in);
break;
case QQ.QQ_CMD_MODIFY_INFO:
processModifyInfoReply(in);
break;
case QQ.QQ_CMD_SEARCH_USER:
processSearchUserReply(in);
break;
case QQ.QQ_CMD_ADD_FRIEND:
processAddFriendReply(in);
break;
case QQ.QQ_CMD_DELETE_FRIEND:
processDeleteFriendReply(in);
break;
case QQ.QQ_CMD_REMOVE_SELF:
processRemoveSelfReply(in);
break;
case QQ.QQ_CMD_ADD_FRIEND_AUTH:
processAddFriendAuthReply(in);
break;
case QQ.QQ_CMD_GET_USER_INFO:
processGetUserInfoReply(in);
break;
case QQ.QQ_CMD_CHANGE_STATUS:
processChangeStatusReply(in);
break;
case QQ.QQ_CMD_SEND_IM:
processSendIMReply(in);
break;
case QQ.QQ_CMD_RECV_IM:
processReceiveIM(in);
break;
case QQ.QQ_CMD_LOGIN:
this.user = client.getUser();
processLoginReply(in);
break;
case QQ.QQ_CMD_GET_FRIEND_LIST:
processGetFriendListReply(in);
break;
case QQ.QQ_CMD_GET_FRIEND_ONLINE:
processGetFriendOnlineReply(in);
break;
case QQ.QQ_CMD_RECV_MSG_SYS:
processSystemNotification(in);
break;
case QQ.QQ_CMD_RECV_MSG_FRIEND_CHANGE_STATUS:
processFriendChangeStatus(in);
break;
case QQ.QQ_CMD_UPLOAD_GROUP_FRIEND:
processUploadGroupFriendReply(in);
break;
case QQ.QQ_CMD_DOWNLOAD_GROUP_FRIEND:
processDownloadGroupFriendReply(in);
break;
case QQ.QQ_CMD_GROUP_NAME_OP:
processGroupNameOpReply(in);
break;
case QQ.QQ_CMD_FRIEND_REMARK_OP:
processFriendRemarkOpReply(in);
break;
case QQ.QQ_CMD_CLUSTER_CMD:
processClusterCommandReply(in);
break;
case QQ.QQ_CMD_REQUEST_KEY:
processRequestKeyReply(in);
break;
default:
break;
}
} catch (PacketParseException ex) {
log.error(ex.getMessage() + ": 抛弃改包");
if(ex.getEvent() != null)
this.fireQQEvent(ex.getEvent());
}
}
// 处理群命令的回复包
private void processClusterCommandReply(InPacket in) throws PacketParseException {
ClusterCommandReplyPacket packet = (ClusterCommandReplyPacket)in;
switch(packet.subCommand) {
case QQ.QQ_CLUSTER_CMD_CREATE_CLUSTER:
processClusterCreateReply(packet);
break;
case QQ.QQ_CLUSTER_CMD_ACTIVATE_CLUSTER:
processClusterActivateReply(packet);
break;
case QQ.QQ_CLUSTER_CMD_GET_CLUSTER_INFO:
processClusterGetInfoReply(packet);
break;
case QQ.QQ_CLUSTER_CMD_GET_ONLINE_MEMBER:
processClusterGetOnlineMemberReply(packet);
break;
case QQ.QQ_CLUSTER_CMD_GET_MEMBER_INFO:
processClusterGetMemberInfoReply(packet);
break;
case QQ.QQ_CLUSTER_CMD_EXIT_CLUSTER:
processClusterExitReply(packet);
break;
case QQ.QQ_CLUSTER_CMD_JOIN_CLUSTER:
processClusterJoinReply(packet);
break;
case QQ.QQ_CLUSTER_CMD_JOIN_CLUSTER_AUTH:
processClusterJoinAuthReply(packet);
break;
case QQ.QQ_CLUSTER_CMD_MODIFY_CLUSTER_INFO:
processClusterModifyInfoReply(packet);
break;
case QQ.QQ_CLUSTER_CMD_SEARCH_CLUSTER:
processClusterSearchReply(packet);
break;
case QQ.QQ_CLUSTER_CMD_SEND_IM:
processClusterSendIMReply(packet);
break;
}
}
// 处理发送群消息的回复包
private void processClusterSendIMReply(ClusterCommandReplyPacket packet) {
QQEvent e;
if(packet.replyCode == QQ.QQ_CLUSTER_CMD_REPLY_OK) {
log.debug("群消息发送成功");
e = new QQEvent(packet);
e.type = QQEvent.QQ_SEND_CLUSTER_IM_SUCCESS;
this.fireQQEvent(e);
} else {
log.debug("群消息发送失败");
ClusterCommandPacket ccp = (ClusterCommandPacket)monitor.retrieveRequest(packet);
packet.clusterId = ccp.getClusterId();
e = new QQEvent(packet);
e.type = QQEvent.QQ_SEND_CLUSTER_IM_FAIL;
this.fireQQEvent(e);
}
}
// 处理搜索群的回复包
private void processClusterSearchReply(ClusterCommandReplyPacket packet) {
QQEvent e;
if(packet.replyCode == QQ.QQ_CLUSTER_CMD_REPLY_OK) {
log.debug("群搜索成功");
e = new QQEvent(packet);
e.type = QQEvent.QQ_SEARCH_CLUSTER_SUCCESS;
this.fireQQEvent(e);
} else {
log.debug("搜索群失败");
e = new QQEvent(packet);
e.type = QQEvent.QQ_SEARCH_CLUSTER_FAIL;
this.fireQQEvent(e);
}
}
// 处理修改群信息的回复包
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -