服务器
用户
package qq.QQServer.qqcommon;import java.io.Serializable;/*
表示一个用户信息*/
public class User implements Serializable {private static final long serialVersionUID = 1L;private String userId;//用户Idprivate String passwd;//用户密码public User(String userId, String passwd) {this.userId = userId;this.passwd = passwd;}public String getUserId() {return userId;}public void setUserId(String userId) {this.userId = userId;}public String getPasswd() {return passwd;}public void setPasswd(String passwd) {this.passwd = passwd;}
}
消息对象
package qq.QQServer.qqcommon;import java.io.Serializable;/*
客户端和服务器的消息对象*/
public class Message implements Serializable {private static final long serialVersionUID = 1L;private String sender;//发送者private String getter;//接收者private String content;//消息内容private String sendTime;//发送消息private String mesType;//消息类型 可以在接口定义消息类型//进行扩展 和文件相关成员private byte[] fileBytes;private int filelen = 0;private String dest;//将文件传输到。。private String src;//源文件路径public byte[] getFileBytes() {return fileBytes;}public void setFileBytes(byte[] fileBytes) {this.fileBytes = fileBytes;}public int getFilelen() {return filelen;}public void setFilelen(int filelen) {this.filelen = filelen;}public String getDest() {return dest;}public void setDest(String dest) {this.dest = dest;}public String getSrc() {return src;}public void setSrc(String src) {this.src = src;}public static long getSerialVersionUID() {return serialVersionUID;}public String getSender() {return sender;}public void setSender(String sender) {this.sender = sender;}public String getGetter() {return getter;}public void setGetter(String getter) {this.getter = getter;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}public String getSendTime() {return sendTime;}public void setSendTime(String sendTime) {this.sendTime = sendTime;}public String getMesType() {return mesType;}public void setMesType(String mesType) {this.mesType = mesType;}
}
接口常量
package qq.QQServer.qqcommon;public interface MessageType {//在接口中定义一些常量String MESSAGE_LOGIN_SUCCEED = "1";//登录成功String MESSAGE_LOGIN_FAIT = "2";//登录失败String MESSAGE_COMM_MES = "3";//普通信息包String MESSAGE_GET_ONLINE_FRIEND = "4";//要求返回在线用户列表String MESSAGE_RET_ONLINE_FRIEND = "5";//返回在线用户列表String MESSAGE_CLIENT_EXIT = "6";//客户端请求退出String MESSAGE_To_All_MES = "7";//群发消息String MESSAGE_FILT_MES = "8";//文件消息
}
package qq.QQServer.qqserver.server;import qq.QQClient.qqcommon.Message;
import qq.QQClient.qqcommon.MessageType;
import qq.QQClient.qqcommon.User;import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;/*
这是服务端*/
public class QQServer {private ServerSocket ss = null;//创建一个集合,存放多个用户,如果是这些用户,就是合法的//这里也可以使用ConcurrentHashMap,可以处理并发的集合,没有线程安全//HashMap没有处理线程安全,因此多线程不安全//ConcurrentHashMap 处理线程安全,即线程同步处理,在多线程下是安全的private static ConcurrentHashMap<String, User> validUsers = new ConcurrentHashMap<>();static {validUsers.put("100",new User("100", "123456"));validUsers.put("200",new User("200", "123456"));validUsers.put("300",new User("300", "123456"));validUsers.put("至尊宝",new User("至尊宝", "123456"));validUsers.put("紫霞仙子",new User("紫霞仙子", "123456"));validUsers.put("菩提老祖",new User("菩提老祖", "123456"));}//验证用户是否有效的方法private boolean checkUser(String userId, String passad){User user = validUsers.get(userId);if(user == null){//说明UserId不存在validUser 的key中return false;}if (!user.getPasswd().equals(passad)){//userId正确,但密码错误return false;}return true;}public QQServer(){//端口写在配置文件try {System.out.println("服务端在端口9999监听。。。。");ss = new ServerSocket(9999);while (true) { //当和某个客户端连接后继续监听,因此whileSocket socket = ss.accept();//启动推送新闻线程new Thread(new SendNewsToAllService()).start();//得到socket关联的对象输入流ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());User u = (User) ois.readObject();//读取客户端发送的User对象//创建一个Message对象,准备回复客户端Message message = new Message();//验证if (checkUser(u.getUserId(),u.getPasswd())){//登录通过message.setMesType(MessageType.MESSAGE_LOGIN_SUCCEED);//将message对象回复给客户端oos.writeObject(message);//创建一个线程,和客户端保持通信,该线程需要有socket对象ServerConnectClientThread serverConnectClientThread = new ServerConnectClientThread(socket, u.getUserId());//启动线程serverConnectClientThread.start();//吧该线程对象放入到一个集合中进行管理ManageClientThreads.addClientThread(u.getUserId(), serverConnectClientThread);} else {//登录失败System.out.println("用户 id" + u.getUserId() + " pwd " + u.getPasswd() + "验证失败");message.setMesType(MessageType.MESSAGE_LOGIN_FAIT);oos.writeObject(message);//关闭socketsocket.close();}}} catch (Exception e) {e.printStackTrace();} finally {//如果服务器退出while,说明服务端不在监听,因此关闭ServerSockettry {ss.close();} catch (IOException e) {e.printStackTrace();}}}
}
服务端线程
package qq.QQServer.qqserver.server;import qq.QQClient.qqcommon.Message;
import qq.QQClient.qqcommon.MessageType;import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Iterator;/*
该类的一个对象和某个客户端保持通信*/
public class ServerConnectClientThread extends Thread{private Socket socket;private String userId;public ServerConnectClientThread(Socket socket, String userId){this.socket = socket;this.userId = userId;}public Socket getSocket(){return socket;}@Overridepublic void run() {while (true){try {System.out.println("服务端和客户端" + userId + "保持通信,读取数据。。。");ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());Message message = (Message) ois.readObject();//根据message的类型做相应的业务处理if (message.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)){System.out.println(message.getSender() + "要在线用户列表");String onlineUser = ManageClientThreads.getOnlineUser();//返回message//构建一个message对象,返回给客户端Message message2 = new Message();message2.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND);message2.setContent(onlineUser);message2.setGetter(message.getSender());//返回给客户端ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(message2);} else if(message.getMesType().equals(MessageType.MESSAGE_COMM_MES)){//根据message获取getter id,然后在得到对应线程ServerConnectClientThread serverConnectClientThread =ManageClientThreads.getServerConnectClientThread(message.getGetter());//得到对应socket的对象输出流,将message对象转发给指定的客户端ObjectOutputStream oos =new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());oos.writeObject(message);//转发,提示,如果客户不在线,可以保存到数据库,就可以实现离线留言} else if (message.getMesType().equals(MessageType.MESSAGE_To_All_MES)){//需要遍历 管理线程的集合,把所以的线程的socket得到,然后把message进行转发HashMap<String, ServerConnectClientThread> hm = ManageClientThreads.getHm();Iterator<String> iterator = hm.keySet().iterator();while (iterator.hasNext()){//取出在线用户IdString onLineUserId = iterator.next();if(!onLineUserId.equals(message.getSender())){//排除群发消息的这个用户//进行转发messageObjectOutputStream oos = new ObjectOutputStream(hm.get(onLineUserId).getSocket().getOutputStream());oos.writeObject(message);}}} else if (message.getMesType().equals(MessageType.MESSAGE_FILT_MES)){//根据gettterId 获取对于的线程 将message对象转发ObjectOutputStream oos =new ObjectOutputStream(ManageClientThreads.getServerConnectClientThread(message.getGetter()).getSocket().getOutputStream());oos.writeObject(message);}else if (message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)){//客户端退出System.out.println(message.getSender() + "退出");ManageClientThreads.removeServerConnectClientThread(message.getSender());socket.close();//关闭连接//退出线程break;} else {System.out.println("其他类型的message暂时不处理");}} catch (Exception e) {e.printStackTrace();}}}
}
服务端线程管理
package qq.QQServer.qqserver.server;import java.util.HashMap;
import java.util.Iterator;public class ManageClientThreads {private static HashMap<String, ServerConnectClientThread> hm = new HashMap<>();public static HashMap<String, ServerConnectClientThread> getHm() {return hm;}//添加线程对象到hm集合public static void addClientThread(String userId, ServerConnectClientThread serverConnectClientThread){hm.put(userId, serverConnectClientThread);}//根据userId 返回ServerConnectClientThread线程public static ServerConnectClientThread getServerConnectClientThread(String userId){return hm.get(userId);}//增加一个方法,从集合中,移除某个线程对象public static void removeServerConnectClientThread(String userId){hm.get(userId);}//编写方法,返回在线用户列表public static String getOnlineUser(){//集合遍历,遍历hashmap的keyIterator<String> iterator = hm.keySet().iterator();String onlineUserList = "";while (iterator.hasNext()){onlineUserList += iterator.next().toString() + " ";}return onlineUserList;}
}
整体发送
package qq.QQServer.qqserver.server;import qq.QQClient.qqclient.utils.Utility;
import qq.QQClient.qqcommon.Message;
import qq.QQServer.qqcommon.MessageType;import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Scanner;public class SendNewsToAllService implements Runnable{private Scanner scanner = new Scanner(System.in);@Overridepublic void run() {//为了多次使用while (true) {System.out.println("请输入服务器要推送的消息[输入exit退出推送服务]");String news = Utility.readString(100);if("exit".equals(news)){break;}//构建一个消息类型Message message = new Message();message.setSender("服务器");message.setMesType(MessageType.MESSAGE_To_All_MES);message.setContent(news);message.setSendTime(new Date().toString());;System.out.println("服务器推送消息给所有人说:" + news);//遍历当前所有的通信线程,得到socket,并发送messageHashMap<String, ServerConnectClientThread> hm = ManageClientThreads.getHm();Iterator<String> iterator = hm.keySet().iterator();while (iterator.hasNext()) {String onLineUserId = iterator.next().toString();try {ObjectOutputStream oos = new ObjectOutputStream(hm.get(onLineUserId).getSocket().getOutputStream());oos.writeObject(message);} catch (IOException e) {e.printStackTrace();}}}}
}
启动服务器
package qq.QQServer.qqframe;import qq.QQServer.qqserver.server.QQServer;public class QQFrame {public static void main(String[] args) {new QQServer();}
}
客户端主体
package qq.QQClient.qqclient.view;import qq.QQClient.qqclient.messageclientservice.MessageClientService;
import qq.QQClient.qqclient.service.FileClientSerivice;
import qq.QQClient.qqclient.service.UserClientService;
import qq.QQClient.qqclient.utils.Utility;/*
客户端菜单界面*/
public class QQView {private boolean loop = true;//控制是否显示主菜单private String key = "";//接收用户键盘输入private UserClientService userClientService = new UserClientService();//对象用来登录服务器/注册用户private MessageClientService messageClientService = new MessageClientService();//对象用户的私聊//群发private FileClientSerivice fileClientSerivice = new FileClientSerivice();//该对象用于传输public static void main(String[] args){new QQView().mainMenu();System.out.println("客户端退出系统");}//显示主菜单private void mainMenu(){while (loop){System.out.println("============欢迎登录网络系统==========");System.out.println("\t\t 1 登录系统");System.out.println("\t\t 9 退出系统");System.out.print("请输入你的选择");key = Utility.readString(1);//根据用户输入,来出来不同逻辑switch (key){case "1":System.out.print("请输入用户号:");String userId = Utility.readString(50);System.out.print("请输入用密 码:");String pwd = Utility.readString(50);if(userClientService.checkUser(userId,pwd)){System.out.println("============欢迎(用户 " + userId + "登录成功==========");//进入二级菜单while (loop){System.out.println("\n============网络通信系统二级菜单(用户 " + userId + "==========");System.out.println("\t\t 1 显示在线用户列表");System.out.println("\t\t 2 群发消息");System.out.println("\t\t 3 私聊消息");System.out.println("\t\t 4 发送文件");System.out.println("\t\t 9 退出系统");System.out.print("请输入你的选择");key = Utility.readString(1);switch (key){case "1":userClientService.onlineFriendList();break;case "2":System.out.println("请输入想对大家说的话");String s = Utility.readString(100);messageClientService.sendMessageToAll(s, userId);break;case "3":System.out.print("请输入向聊天的用户号(在线):");String getterId = Utility.readString(50);System.out.println("请输入想说的话:");String content = Utility.readString(100);//编写一个方法,将消息发送给服务器端messageClientService.sendMessageToOne(content, userId, getterId);break;case "4":System.out.print("请输入你想把文件发送给的用户(在线用户):");getterId = Utility.readString(50);System.out.print("请输入发送文件的路径(形式 d:\\xx.jpg)");String src = Utility.readString(100);System.out.print("请输入把文件发送到对应的目录(形式 d:\\yy.jpg)");String dest = Utility.readString(100);fileClientSerivice.sendFileToOne(src,dest,userId,getterId);break;case "9"://调用一个方法,给服务器发送一个退出系统的messageuserClientService.logout();loop = false;break;}}} else {//登录服务器失败System.out.println("============登录失败===============");}break;case "2":loop = false;break;}}}}
用户登录等
package qq.QQClient.qqclient.service;import qq.QQClient.qqcommon.Message;
import qq.QQClient.qqcommon.MessageType;
import qq.QQClient.qqcommon.User;import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;/*
该类完成用户登录验证和用户注册等功能*/
public class UserClientService {private User u = new User();private Socket socket;//根据suerId 和 pwd 到服务器验证用户是否合法public boolean checkUser(String userId, String pwd){boolean b = false;//创建User对象u.setUserId(userId);u.setPasswd(pwd);//链接服务器,发送u对象try {socket = new Socket(InetAddress.getByName("127.0.0.1"), 9999);//得到ObjOutpStrea对象ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(u);//发送User对象//读取从服务器回复的Message对象ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());Message ms = (Message) ois.readObject();if(ms.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCEED)){//登录OK//创建一个和服务器端保持通信的线程-》创建一个类ClientConnectServerThreadClientConnectServerThread clientConnectServerThread = new ClientConnectServerThread(socket);//启动客户端线程clientConnectServerThread.start();//为了后面客户端的扩展,我们将线程放入到集合管理MessageClientConnectServerThread.addClientConnectServerThread(userId, clientConnectServerThread);b = true;} else {//如果登录失败 我们就不能启动服务器通信的线程,关闭socketsocket.close();}} catch (Exception e) {e.printStackTrace();}return b;}//向服务器请求在线用户列表public void onlineFriendList(){//发送一个Message,类型MESSAGE_GET_ONLINE_FRIENDMessage message = new Message();message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);message.setSender(u.getUserId());//发送给服务器try {//从管理线程的集合中,通过userID,得到这个线程ClientConnectServerThread clientConnectServerThread =MessageClientConnectServerThread.getClientConnectServerThread(u.getUserId());//通过这个线程得到关联的socketSocket socket = clientConnectServerThread.getSocket();//得到当前线程的Socket 对于的ObjectOutputStream对象ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(message);//发送一个Message对象,向服务端要求在线用户列表} catch (IOException e) {e.printStackTrace();}}//编写方法,退出客户端,并给服务端发送一个退出系统的message对象public void logout(){Message message = new Message();message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);message.setSender(u.getUserId());try {//ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());ObjectOutputStream oos =new ObjectOutputStream(MessageClientConnectServerThread.getClientConnectServerThread(u.getUserId()).getSocket().getOutputStream());oos.writeObject(message);System.out.println(u.getUserId() + "退出系统");System.exit(0);//退出进程} catch (IOException e) {e.printStackTrace();}}
}
线程问题
package qq.QQClient.qqclient.service;import qq.QQClient.qqcommon.Message;
import qq.QQClient.qqcommon.MessageType;import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.net.Socket;public class ClientConnectServerThread extends Thread{//该线程要Socketprivate Socket socket;public ClientConnectServerThread(Socket socket){this.socket = socket;}@Overridepublic void run() {//因为Thread需要在后台和服务器通信,因此我们while循环while (true){try {System.out.println("客户端线程,等待从服务器端读取发送的消息");ObjectInputStream osi = new ObjectInputStream(socket.getInputStream());//如果服务器没有发送Message对象,线程会阻塞在这Message message = (Message) osi.readObject();//判断Message类型,然后做相应的处理if(message.getMesType().equals(MessageType.MESSAGE_RET_ONLINE_FRIEND)){//取出在线列表信息,并显示String[] onlineUsers = message.getContent().split(" ");System.out.println("\n===============当前在线用户列表================");for (int i = 0; i < onlineUsers.length; i++) {System.out.println("用户: " + onlineUsers[i]);}} else if(message.getMesType().equals(MessageType.MESSAGE_COMM_MES)){//吧从服务器转发的消息,显示到控制台就行System.out.println("\n" + message.getSender()+ "对 " + message.getGetter() + " 说 " + message.getContent());} else if (message.getMesType().equals(MessageType.MESSAGE_To_All_MES)){//显示在客户端的控制台System.out.println("\n" + message.getSender() + " 对大家说 " + message.getContent());} else if (message.getMesType().equals(MessageType.MESSAGE_FILT_MES)){//如果是文件消息System.out.println("\n" + message.getSender() + " 给 "+ message.getGetter()+ " 发送文件:" + message.getSrc() + " 到我的电脑目录 " + message.getDest());//取出message的文件字节数组,通过文件输出流写道磁盘FileOutputStream fileOutputStream = new FileOutputStream(message.getDest());fileOutputStream.write(message.getFileBytes());fileOutputStream.close();System.out.println("\n保存文件成功");} else {System.out.println("是其他类型的message,暂时不处理");}} catch (Exception e) {e.printStackTrace();}}}public Socket getSocket() {return socket;}
}
线程管理
package qq.QQClient.qqclient.service;import java.util.HashMap;/*
该类管理客户端连接到服务端的线程*/
public class MessageClientConnectServerThread {//我们吧多个线程放入一个HashMap集合,key就是用户id,value就是线程private static HashMap<String, ClientConnectServerThread> hm = new HashMap<>();//将某个线程加入到集合public static void addClientConnectServerThread(String userId, ClientConnectServerThread clientConnectServerThread){hm.put(userId,clientConnectServerThread);}//通过userId 得到对应的线程public static ClientConnectServerThread getClientConnectServerThread(String userId){return hm.get(userId);}
}
文件管理
package qq.QQClient.qqclient.service;import qq.QQClient.qqcommon.Message;
import qq.QQClient.qqcommon.MessageType;import java.io.*;public class FileClientSerivice {public void sendFileToOne(String src, String dest, String senderId, String getterId){//读取src文件 ——》 messageMessage message = new Message();message.setMesType(MessageType.MESSAGE_FILT_MES);message.setSender(senderId);message.setGetter(getterId);message.setSrc(src);message.setDest(dest);//需要将文件读取FileInputStream fileInputStream = null;byte[] fileBytes = new byte[(int) new File(src).length()];try {fileInputStream = new FileInputStream(src);fileInputStream.read(fileBytes);//将src文件读入到程序的字节数组//将文件对应的字节数组设置messagemessage.setFileBytes(fileBytes);} catch (Exception e) {e.printStackTrace();}finally {if (fileInputStream != null){try {fileInputStream.close();} catch (IOException e) {e.printStackTrace();}}}System.out.println("\n" + senderId + " 给 " + getterId + " 发送文件: " + src + "到对方的电脑目录 " + dest);try {ObjectOutputStream oos =new ObjectOutputStream(MessageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());oos.writeObject(message);} catch (IOException e) {e.printStackTrace();}}
}
package qq.QQClient.qqclient.messageclientservice;import qq.QQClient.qqclient.service.MessageClientConnectServerThread;
import qq.QQClient.qqcommon.Message;
import qq.QQServer.qqcommon.MessageType;import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;public class MessageClientService {public void sendMessageToAll(String content, String senderId){//构建messageMessage message = new Message();message.setMesType(MessageType.MESSAGE_To_All_MES);//群发消息message.setSender(senderId);message.setContent(content);message.setSendTime(new Date().toString());//发送数据到message对象System.out.println(senderId + " 对大家说 " + content);//发送给服务端try {ObjectOutputStream oos =new ObjectOutputStream(MessageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());oos.writeObject(message);} catch (IOException e) {e.printStackTrace();}}public void sendMessageToOne(String content, String senderId, String getterId){//构建messageMessage message = new Message();message.setMesType(MessageType.MESSAGE_COMM_MES);//普通的聊天消息message.setSender(senderId);message.setGetter(getterId);message.setContent(content);message.setSendTime(new Date().toString());//发送数据到message对象System.out.println(senderId + "对" + getterId + "说" + content);//发送给服务端try {ObjectOutputStream oos =new ObjectOutputStream(MessageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());oos.writeObject(message);} catch (IOException e) {e.printStackTrace();}}
}