最近做了一个聊天软件的项目,界面高仿QQ,使用的协议是xml。
通信项目,感觉协议是整个项目的灵魂,告诉计算机该怎样处理消息,同时,也指导着我们该怎样处理消息。因此,协议要事先定好,尽可能的全面,不要写一步,才订立下一步。大概是经验不足的原因,一开始,我们感觉定的协议都差不多了。然而,写着写着,却发现这个地方还需要协议,所以我们后来感觉写的有点茫然了,这也是个教训。
这次的项目,核心是是用了ServerThread这个线程类,来不断地接收并且处理客户端发送给服务器的消息。客户端那边也是用来类似的线程类来接收并且处理消息。下面,贴上部分代码:
package com.chat.server;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Vector;
import javax.swing.JTable;
import javax.swing.RowFilter;
/**
* 服务器控制类(相当于服务员,每个顾客对应一个服务员)
*
* @author zhangtao
*
*/
public class ServerThread extends Thread implements IMS {
private Socket socket;
private OutputStream ops;
private InputStream ips;
private ObjectOutputStream oos;
private ObjectInputStream ois;
private DataOutputStream dos;
private DataInputStream dis;
public String currentUserId;// 当前的用户Id
public String currentUserName;// 当前的用户名
// 定义分组ID的基数
private static int groupNumber = 200000;
// 定义好友QQ号码的基数
private static int idNumber = 1000;
public static String idString;
public Vector<Vector<String>> value = ServerFrame.value;
public JTable jTable = ServerFrame.table;
Vector<String> row;
UserDao userDao;
/**
* 构造方法
*
* @param socket
*/
public ServerThread(Socket socket) {
super();
this.socket = socket;
}
public void run() {
super.run();
initServerThread();
}
private void initServerThread() {
try {
// 拿到输入输出流
ops = socket.getOutputStream();
ips = socket.getInputStream();
oos = new ObjectOutputStream(ops);
ois = new ObjectInputStream(ips);
// 接收消息
String msg = readMsg();
// 解析消息类型
String type = getXMLValue(msg, "type");
System.out.println("type======="+type);
if ("login".equals(type)) {
// 获取用户名和密码
String userId = getXMLValue(msg, "name");
String psw = getXMLValue(msg, "pwd");
System.out.println(userId+" "+psw);
String login_msg = "";
if (checkLogin(userId, psw)) {// 如果登陆成功
UserInfo currentUser = MyServer.userDB.get(userId);
// 得到当前用户的id name
currentUserId = currentUser.getUserId();
currentUserName = currentUser.getUerName();
// 更新服务器显示信息
row = new Vector<String>();// 行向量
row.add(currentUserName);
row.add(userId);
row.add(socket.getRemoteSocketAddress().toString());// IP地址
row.add("在线");
value.add(row);
jTable.updateUI();// 刷新界面
// 反馈登录成功消息
login_msg = "<msg><type>loginResp</type><state>LOGIN_TRUE</state></msg>";
// 反馈登陆应答消息
sendMsg(login_msg);// 这里有问题?????不需要接收对象吗?
// 发送好友列表
// <msg><type>budyList</type><users>用户1,用户2...</users></msg>
String idList = "";
for (int i = 0; i < MyServer.serverList.size(); i++) {
idList = idList
+ MyServer.serverList.get(i).currentUserId
+ ",";
}
String friend_Msg = "<msg><type>budyList</type><users>"
+ idList + "</users></msg>";
// 发送上线消息
String online_msg = "<msg>g<type>online</type><content>"
+ currentUserName + "用户上线了</content></msg>";
// 把消息发给所有在线用户
for (int i = 0; i < MyServer.serverList.size(); i++) {
ServerThread st = MyServer.serverList.get(i);
// 除了自己
if ((st.currentUserId).equals(currentUserId)) {// 如果是自己
} else {
st.sendMsg(friend_Msg);
st.sendMsg(online_msg);
}
}
// 接收并且处理消息
// 消息转发
castMsg();// 没有消息过来,就阻塞在read,读取到消息,就进行转发
} else {// 登陆失败
login_msg = "<msg><type>loginResp</type><state>LOGIN_FALSE</state></msg>";
// 发送登录应答消息
sendMsg(login_msg);
MyServer.serverList.remove(this);
oos.flush();
}
} else if ("reg".equals(type)) {
// 解析用户名和密码
String name = getXMLValue(msg, "name");
String pwd = getXMLValue(msg, "pwd");
//将用户信息保存到本地的map
// UserDao.saveDB();
System.out.println("name: " + name + "" + " psw: " + pwd);
// 验证用户名密码,并执行注册
String msg_reg = "";
if (checkReg(name, pwd)) {
msg_reg = "<msg><type>regResp</type><userId>"+idString+"</userId><state>REGIST_TRUE</state></msg>";
} else {
msg_reg = "<msg><type>regResp</type><state>REGIST_FALSE</state></msg>";
}
// 发送注册应答消息
sendMsg(msg_reg);
System.out.println("消息发送完成");
// 删除当前线程
MyServer.serverList.remove(this);
oos.flush();
oos.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 验证注册是否成功,成功的同时把用户添加到map
*
* @param name
* 用户名
* @param pwd
* 密码
* @return boolean
*/
private boolean checkReg(String name, String pwd) {
// 验证用户是否存在
// if (MyServer.userDB.containsKey(name)) {
// return false;
// }
// 创建新的user对象,并且加入到map中
UserInfo userInfo = new UserInfo();
userInfo.setUerName(name);
userInfo.setPsw(pwd);
idString = idNumber + MyServer.userDB.size() + "";// 真正的id要加上map的大小,才能保证正确
MyServer.userDB.put(idString, userInfo);
System.out.println("idString"+idString);
userInfo.setUserId(idString);
return true;
}
/**
* 转发消息
*
* @return 消息
*
*/
private String castMsg() {
try {
while (true) {
String src_id;
String des_id;
String msg = readMsg();
// 解析消息类型
String type = getXMLValue(msg, "type");
System.out.println("处理的type: " + type + "=============");
if (type.equals("chat")) {// 如果是聊天消息
// 解析消息
src_id = getXMLValue(msg, "sender");
UserInfo user_src = MyServer.userDB.get(src_id);
String src_name = user_src.getUerName();
des_id = getXMLValue(msg, "reciver");// 获取目标id
UserInfo user_des = MyServer.userDB.get(des_id);
String des_name = user_des.getUerName();
String msg_content = getXMLValue(msg, "content");// 获取消息内容
System.out.println("user_id: "+src_id +"des_id: "+des_id+" 消息:"+msg_content);
// 寻找目标线程
for (int i = 0; i < MyServer.serverList.size(); i++) {
ServerThread st = MyServer.serverList.get(i);
// 如果是目标Id
if (des_id.equals(st.currentUserId)) {
// 把消息内容发过去
String chat_msg = "<msg><type>chat</type><sender>"+src_name+"</sender><reciver>"+des_name+"</reciver><content>"+msg_content+"</content></msg>";
st.sendMsg(chat_msg);
System.out.println("chat_msg:"+chat_msg);
}
}
} else if (type.equals("addGroup")) {
// 解析消息
src_id = getXMLValue(msg, "sender");
int groupName = Integer.parseInt(getXMLValue(msg,
"GroupName"));
userDao = new UserDao(groupName, idNumber);
idNumber++;
} else if (type.equals("addFriends")) {
// 解析消息
// 得到收发者的id
src_id = getXMLValue(msg, "srcId");
des_id = getXMLValue(msg, "reciver");
System.out.println("srcId:" + src_id + " "
+ "receiver:" + des_id);
// 得到验证的内容
String checkString = getXMLValue(msg, "content");
// 进行转发
// 先找到对应的线程
for (int i = 0; i < MyServer.serverList.size(); i++) {
// 得到对应下标的线程对象
ServerThread st = MyServer.serverList.get(i);
// 如果是目标id
if ((st.currentUserId).equals(des_id)) {
// 得到源用户的用户名
UserInfo user = MyServer.userDB.get(src_id);
String src_name = user.getUerName();
String add_msg = src_name + "想添加你为好友:"
+ checkString;
String ims_String = "<msg><type>addFriends_checkMsg</type><content>"
+ add_msg + "</content></msg>";
System.out.println(add_msg);
sendMsg(ims_String);
}
// else {
//
// sendMsg(msg);
// }
}
} else if (type.equals("addResp")) {// 如果是添加反馈消息
System.out.println("进入addResp!!!!");
// 解析消息
// 得到发送者的id
// src_id = currentUserId;
src_id = getXMLValue(msg, "srcId");
// System.out.println(msg);
// 得到接受者的id
des_id = getXMLValue(msg, "reciver");
System.out.println("srcId: " + src_id + " des_id:"
+ des_id);
System.out.println("srcId:" + src_id + " addResp "
+ "receiver:" + des_id);
// 得到添加状态
String state = getXMLValue(msg, "state");
// *******后面可以改
// 暂时默认如果state为true,服务器帮助添加好友
// 1.先要得到双方的grouplist ----就要先得到user对象
// 2.在list中添加user
if (state.equals("ADD_TRUE")) {
// 添加好友
UserInfo user_src = MyServer.userDB.get(src_id);
UserInfo user_des = MyServer.userDB.get(des_id);
//把好友添加到group中
user_src.group.groupAddUser(user_des);
user_des.group.groupAddUser(user_src);
// 给客户端发送用户对象,交互发送对象
// 查找对应线程
for (int i = 0; i < MyServer.serverList.size(); i++) {
ServerThread st = MyServer.serverList.get(i);
if ((st.currentUserId).equals(des_id)) {
// 如果是目标线程就发送源id
String string = "<msg><type>friend</type><content>"
+ src_id + "</content></msg>";
st.sendMsg(string);// st是目标线程。没有st,就是当前线程对象
} else if ((st.currentUserId).equals(src_id)) {
// 如果是源线程,就发送目标对象
String string = "<msg><type>friend</type><content>"
+ des_id + "</content></msg>";
st.sendMsg(string);
}
}
}
// else if (state.equals("ADD_False")) {
// //添加失败
//
// }
}
}
} catch (Exception e) {// 有异常时,进入catch
// 客户下线了,移出当前线程
MyServer.serverList.remove(this);
// 发送下线消息
String offLine_msg = "<msg><type>offline</type><content>"
+ currentUserName + "用户下线了</content></msg>";
// 把消息发给所有在线用户
for (int i = 0; i < MyServer.serverList.size(); i++) {
ServerThread st = MyServer.serverList.get(i);
st.sendMsg(offLine_msg);
}
// 刷新界面
row.remove(3);
row.add("离线");
jTable.updateUI();
}
return null;
}
/**
* 发送消息
*
* @param msg
* 消息内容
*/
private void sendMsg(String msg) {
try {
// ops.write(msg.getBytes());
// ops.flush();
// dos.writeUTF(msg);
// dos.flush();
oos.writeObject(msg);
oos.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 验证用户名和密码的方法
*
* @param name
* 用户名
* @param psw
* 密码
* @return true或false
*/
private boolean checkLogin(String id, String psw) {
if (MyServer.userDB.containsKey(id)) {// 如果包含用户名
// 验证密码
UserInfo userInfo = MyServer.userDB.get(id);// 得到对应的用户
String userPsw = userInfo.getPsw();
if (psw.equals(userPsw)) {// 密码正确
return true;
}
}
return false;
}
/**
* 读取消息的方法
*
* @return 消息(一整条)
* @throws IOException
* @throws ClassNotFoundException
*/
private String readMsg() throws ClassNotFoundException, IOException {// 抛给调用它的那个
String str = "";
// 先读取一个字节
// int value = ips.read();
// StringBuffer sb = new StringBuffer();
// while (value != 13) {// 当不是回车时,循环读取
// sb.append((char) value);
// // 判断是否遇到</msg>,如果遇到,则视为一条消息返回
// str = sb.toString().trim();
// str = new String(str.getBytes("ISO-8859-1"), "GBK").trim();
// if (str.contains("</msg>")) {
// return str;
// }
// value = ips.read();
// }
// // 先读取一个字节
// return null;
while(true){
String string = (String) ois.readObject();
if (string.contains("</msg>")) {
return string;
}
}
}
/**
* 解析消息的类型
*
* @param msg
* 消息
* @param xmlFlag
* 目标的标签对
* @return 消息类型
*/
private String getXMLValue(String msg, String xmlFlag) {
// 找到开始位置
int start = msg.indexOf("<" + xmlFlag + ">") + xmlFlag.length() + 2;
int end = msg.indexOf("</" + xmlFlag + ">");
String result = msg.substring(start, end);
return result;
}
// public ServerThread(){}
// public static void main(String[] args) {
// //<msg><type>login</type><name>用户名</name><pwd>密码</pwd></msg>
// String msg =
// "<msg><type>login</type><name>用户名</name><pwd>密码</pwd></msg>";
// ServerThread st = new ServerThread();
// System.out.println(st.getXMLValue(msg, "pwd"));
// }
}
这次项目,我最大的问题也是迄今仍未实现的,是客户端那边。不知道如何将消息在聊天窗口展示出来,后台已经转发成功了。虽然我知道,要拿到聊天窗口的对象,可是不知道怎么回事,就是没办法拿到,哎!
作为通信阶段的最后一个项目,我做的不够完整,有两个主要原因:
1.时间的原因,我和小伙伴由于时间不是很多,总共才花了三天时间,做的可以说是相当的匆忙;
2.也是最主要的原因,一开始没有足够的重视,以为相对来说比较的简单,就没有太放在心上。
通过这次项目,我觉得无论难易,都要有一颗平常心,不畏难,不轻易,打好基础才能走的稳!!!