文章和代码曾经归档至【Github仓库:https://github.com/timerring/java-tutorial 】或者公众号【AIShareLab】回复 java 也可获取。
我的项目波及
- 我的项目框架设计
- java面向对象编程
- 网络编程
- 多线程
- IO流
- Mysql/应用汇合充当内存数据库
我的项目开发流程
需要剖析
- 用户登录
- 拉取在线用户列表
- 无异样退出(客户端、服务端)
- 私聊
- 群聊
- 发文件
- 服务器推送新闻
性能实现-用户登录
性能阐明
因为还没有学习数据库,咱们人为规定用户名/id = 100, 明码123456 就能够登录,其它用户不能登录
前面应用HashMap模仿数据库,能够多个用户登录。
一个客户端也会有多个线程与服务端来分割。
性能实现-拉取在线用户列表
性能实现-无异样退出
略
性能实现-私聊
性能实现-群聊
略
性能阐明-发文件
性能实现-服务器推送新闻
客户端
`-- com `-- hspedu |-- qqclient | |-- service | | |-- ClientConnectServerThread.java | | |-- FileClientService.java | | |-- ManageClientConnectServerThread.java | | |-- MessageClientService.java | | `-- UserClientService.java | |-- utils | | `-- Utility.java | `-- view | `-- QQView.java `-- qqcommon |-- Message.java |-- MessageType.java `-- User.java
QQView
package com.hspedu.qqclient.view;import com.hspedu.qqclient.service.FileClientService;import com.hspedu.qqclient.service.MessageClientService;import com.hspedu.qqclient.service.UserClientService;import com.hspedu.qqclient.utils.Utility;/** * 客户端的菜单界面 */@SuppressWarnings("all")public class QQView { private boolean loop = true; //管制是否显示菜单 private String key = ""; // 接管用户的键盘输入 private UserClientService userClientService = new UserClientService();//对象是用于登录服务/注册用户 private MessageClientService messageClientService = new MessageClientService();//对象用户私聊/群聊. private FileClientService fileClientService = new FileClientService();//该对象用户传输文件 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); //这里就比拟麻烦了, 须要到服务端去验证该用户是否非法 //这里有很多代码, 咱们这里编写一个类 UserClientService[用户登录/注册] 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.print("请输出想说的话: "); 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); fileClientService.sendFileToOne(src,dest,userId,getterId); break; case "9": //调用办法,给服务器发送一个退出零碎的message userClientService.logout(); loop = false; break; } } } else { //登录服务器失败 System.out.println("=========登录失败========="); } break; case "9": loop = false; break; } } }}
com/hspedu/qqcommon/Message.java
package com.hspedu.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 String getMesType() { return mesType; } public void setMesType(String mesType) { this.mesType = mesType; } 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; }}
com/hspedu/qqcommon/MessageType.java
package com.hspedu.qqcommon;/** * 示意音讯类型 */public interface MessageType { //老师解读 //1. 在接口中定义了一些常量 //2. 不同的常量的值,示意不同的音讯类型. String MESSAGE_LOGIN_SUCCEED = "1"; //示意登录胜利 String MESSAGE_LOGIN_FAIL = "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_FILE_MES = "8"; //文件音讯(发送文件)}
com/hspedu/qqcommon/User.java
package com.hspedu.qqcommon;import java.io.Serializable;/** * 示意一个用户/客户信息 */public class User implements Serializable { private static final long serialVersionUID = 1L; private String userId;//用户Id/用户名 private String passwd;//用户明码 public User() {} 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; }}
com/hspedu/qqclient/utils/Utility.java
package com.hspedu.qqcommon;import java.io.Serializable;/** * 示意一个用户/客户信息 */public class User implements Serializable { private static final long serialVersionUID = 1L; private String userId;//用户Id/用户名 private String passwd;//用户明码 public User() {} 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; }}
com/hspedu/qqclient/service/ClientConnectServerThread.java
package com.hspedu.qqclient.service;import com.hspedu.qqcommon.Message;import com.hspedu.qqcommon.MessageType;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.net.Socket;public class ClientConnectServerThread extends Thread { //该线程须要持有Socket private Socket socket; //结构器能够承受一个Socket对象 public ClientConnectServerThread(Socket socket) { this.socket = socket; } // @Override public void run() { //因为Thread须要在后盾和服务器通信,因而咱们while循环 while (true) { try { System.out.println("客户端线程,期待从读取从服务器端发送的音讯"); ObjectInputStream ois = new ObjectInputStream(socket.getInputStream()); //如果服务器没有发送Message对象,线程会阻塞在这里 Message message = (Message) ois.readObject(); //留神,前面咱们须要去应用message //判断这个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_FILE_MES)) {//如果是文件音讯 //让用户指定保留门路。。。 System.out.println("\n" + message.getSender() + " 给 " + message.getGetter() + " 发文件: " + message.getSrc() + " 到我的电脑的目录 " + message.getDest()); //取出message的文件字节数组,通过文件输入流写出到磁盘 FileOutputStream fileOutputStream = new FileOutputStream(message.getDest(), true); fileOutputStream.write(message.getFileBytes()); fileOutputStream.close(); System.out.println("\n 保留文件胜利~"); } else { System.out.println("是其余类型的message, 临时不解决...."); } } catch (Exception e) { e.printStackTrace(); } } } //为了更不便的失去Socket public Socket getSocket() { return socket; }}
com/hspedu/qqclient/service/FileClientService.java
package com.hspedu.qqclient.service;import com.hspedu.qqcommon.Message;import com.hspedu.qqcommon.MessageType;import java.io.*;/** * 该类/对象实现 文件传输服务 */public class FileClientService { /** * * @param src 源文件 * @param dest 把该文件传输到对方的哪个目录 * @param senderId 发送用户id * @param getterId 接管用户id */ public void sendFileToOne(String src, String dest, String senderId, String getterId) { //读取src文件 --> message Message message = new Message(); message.setMesType(MessageType.MESSAGE_FILE_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文件读入到程序的字节数组 //将文件对应的字节数组设置message message.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(ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream()); oos.writeObject(message); } catch (IOException e) { e.printStackTrace(); } }}
com/hspedu/qqclient/service/ManageClientConnectServerThread.java
package com.hspedu.qqclient.service;import java.util.HashMap;/** * 该类治理客户端连贯到服务器端的线程的类 */public class ManageClientConnectServerThread { //咱们把多个线程放入一个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); }}
com/hspedu/qqclient/service/MessageClientService.java
package com.hspedu.qqclient.service;import com.hspedu.qqcommon.Message;import com.hspedu.qqcommon.MessageType;import java.io.IOException;import java.io.ObjectOutputStream;import java.util.Date;/** * 该类/对象,提供和音讯相干的服务办法 */public class MessageClientService { /** * @param content 内容 * @param senderId 发送者 */ public void sendMessageToAll(String content, String senderId) { //构建message Message 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(ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream()); oos.writeObject(message); } catch (IOException e) { e.printStackTrace(); } } /** * @param content 内容 * @param senderId 发送用户id * @param getterId 接管用户id */ public void sendMessageToOne(String content, String senderId, String getterId) { //构建message Message 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(ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream()); oos.writeObject(message); } catch (IOException e) { e.printStackTrace(); } }}
com/hspedu/qqclient/service/UserClientService.java
package com.hspedu.qqclient.service;import com.hspedu.qqcommon.Message;import com.hspedu.qqcommon.MessageType;import com.hspedu.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 { // 因为咱们可能在其余中央用应用user信息, 因而作出成员属性 private User u = new User(); // 因为Socket在其它中央也可能应用,因而作出属性 private Socket socket; // 依据userId 和 pwd 到服务器验证该用户是否非法 public boolean checkUser(String userId, String pwd) { boolean b = false; //创立User对象 u.setUserId(userId); u.setPasswd(pwd); try { // 连贯到服务端,发送u对象 socket = new Socket(InetAddress.getByName("127.0.0.1"), 9999); // 失去ObjectOutputStream对象 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 // 创立一个和服务器端放弃通信的线程-> 创立一个类 ClientConnectServerThread ClientConnectServerThread clientConnectServerThread = new ClientConnectServerThread(socket); // 启动客户端的线程 clientConnectServerThread.start(); // 这里为了前面客户端的扩大,咱们将线程放入到汇合治理 ManageClientConnectServerThread.addClientConnectServerThread(userId, clientConnectServerThread); b = true; } else { // 如果登录失败, 咱们就不能启动和服务器通信的线程, 敞开 socket socket.close(); } } catch (Exception e) { e.printStackTrace(); } return b; } // 向服务器端申请在线用户列表 public void onlineFriendList() { // 发送一个Message , 类型MESSAGE_GET_ONLINE_FRIEND Message message = new Message(); message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND); message.setSender(u.getUserId()); // 发送给服务器 try { // 从治理线程的汇合中,通过userId, 失去这个线程对象 ClientConnectServerThread clientConnectServerThread = ManageClientConnectServerThread.getClientConnectServerThread(u.getUserId()); // 通过这个线程失去关联的socket Socket 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()); // 肯定要指定我是哪个客户端id // 发送message try { // ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream()); ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(u.getUserId()).getSocket().getOutputStream()); oos.writeObject(message); System.out.println(u.getUserId() + " 退出零碎 "); System.exit(0); // 完结过程 } catch (IOException e) { e.printStackTrace(); } }}
服务器端
`-- com `-- hspedu |-- qqcommon | |-- Message.java | |-- MessageType.java | `-- User.java |-- qqframe | `-- QQFrame.java |-- qqserver | `-- service | |-- ManageClientThreads.java | |-- QQServer.java | |-- SendNewsToAllService.java | `-- ServerConnectClientThread.java `-- utils `-- Utility.java
com/hspedu/qqcommon/Message.java
package com.hspedu.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 String getMesType() { return mesType; } public void setMesType(String mesType) { this.mesType = mesType; } 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; }}
com/hspedu/qqcommon/MessageType.java
package com.hspedu.qqcommon;/** * 示意音讯类型 */public interface MessageType { //1. 在接口中定义了一些常量 //2. 不同的常量的值,示意不同的音讯类型. String MESSAGE_LOGIN_SUCCEED = "1"; //示意登录胜利 String MESSAGE_LOGIN_FAIL = "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_FILE_MES = "8"; //文件音讯(发送文件)}
com/hspedu/qqcommon/User.java
package com.hspedu.qqcommon;import java.io.Serializable;/** * 示意一个用户/客户信息 */public class User implements Serializable { // 如果一个对象须要通过对象流的形式读取,则该对象对应的类须要序列化。(IO讲的)!!!! // 保障兼容性 private static final long serialVersionUID = 1L; private String userId;//用户Id/用户名 private 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; }}
com/hspedu/qqframe/QQFrame.java
package com.hspedu.qqframe;import com.hspedu.qqserver.service.QQServer;/** * 该类创立QQServer ,启动后盾的服务 */public class QQFrame { public static void main(String[] args) { new QQServer(); }}
com/hspedu/qqserver/service/ManageClientThreads.java
package com.hspedu.qqserver.service;import java.util.HashMap;import java.util.Iterator;/** * 该类用于治理和客户端通信的线程 */public class ManageClientThreads { private static HashMap<String, ServerConnectClientThread> hm = new HashMap<>(); // 返回 hm 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.remove(userId); } // 这里编写办法,能够返回在线用户列表 public static String getOnlineUser() { //汇合遍历 ,遍历 hashmap 的 key Iterator<String> iterator = hm.keySet().iterator(); String onlineUserList = ""; while (iterator.hasNext()) { onlineUserList += iterator.next().toString() + " "; } return onlineUserList; }}
com/hspedu/qqserver/service/QQServer.java
package com.hspedu.qqserver.service;import com.hspedu.qqcommon.Message;import com.hspedu.qqcommon.MessageType;import com.hspedu.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.ArrayList;import java.util.HashMap;import java.util.concurrent.ConcurrentHashMap;/** * 这是服务器, 在监听9999,期待客户端的连贯,并放弃通信 */public class QQServer { private ServerSocket ss = null; //创立一个汇合,寄存多个用户,如果是这些用户登录,就认为是非法 //这里咱们也能够应用 ConcurrentHashMap, 能够解决并发的汇合,没有线程平安 //HashMap 没有解决线程平安,因而在多线程状况下是不平安 //ConcurrentHashMap 解决的线程平安,即线程同步解决, 在多线程状况下是平安 private static ConcurrentHashMap<String, User> validUsers = new ConcurrentHashMap<>(); //private static ConcurrentHashMap<String, ArrayList<Message>> offLineDb = new ConcurrentHashMap<>(); static { //在动态代码块,初始化 validUsers 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 passwd) { User user = validUsers.get(userId); //过关的验证形式 if(user == null) {//阐明userId没有存在validUsers 的key中 return false; } if(!user.getPasswd().equals(passwd)) {//userId正确,然而明码谬误 return false; } return true; } public QQServer() { //留神:端口能够写在配置文件. try { System.out.println("服务端在9999端口监听..."); //启动推送新闻的线程 new Thread(new SendNewsToAllService()).start(); ss = new ServerSocket(9999); while (true) { //当和某个客户端连贯后,会持续监听, 因而while Socket socket = ss.accept(); // 如果没有客户端连贯,就会阻塞在这里 //失去socket关联的对象输出流 ObjectInputStream ois = new ObjectInputStream(socket.getInputStream()); //失去socket关联的对象输入流 ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream()); 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_FAIL); oos.writeObject(message); //敞开socket socket.close(); } } } catch (Exception e) { e.printStackTrace(); } finally { //如果服务器退出了while,阐明服务器端不在监听,因而敞开ServerSocket try { ss.close(); } catch (IOException e) { e.printStackTrace(); } } }}
com/hspedu/qqserver/service/SendNewsToAllService.java
package com.hspedu.qqserver.service;import com.hspedu.qqcommon.Message;import com.hspedu.qqcommon.MessageType;import com.hspedu.utils.Utility;import java.io.IOException;import java.io.ObjectOutputStream;import java.util.*;public class SendNewsToAllService implements Runnable { @Override public void run() { //为了能够推送屡次新闻,应用while 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,并发送message HashMap<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(); } } } }}
com/hspedu/qqserver/service/ServerConnectClientThread.java
package com.hspedu.qqserver.service;import com.hspedu.qqcommon.Message;import com.hspedu.qqcommon.MessageType;import java.io.IOException;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;// 连贯到服务端的用户id public ServerConnectClientThread(Socket socket, String userId) { this.socket = socket; this.userId = userId; } public Socket getSocket() { return socket; } @Override public void run() { // 这里线程处于run的状态,能够发送/接管音讯 // 读数据和写数据须要通过 socket while (true) { try { System.out.println("服务端和客户端" + userId + " 放弃通信,读取数据..."); ObjectInputStream ois = new ObjectInputStream(socket.getInputStream()); Message message = (Message) ois.readObject(); // 前面会应用message, 依据message的类型,做相应的业务解决 if (message.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)) { // 客户端要在线用户列表 /* 在线用户列表模式 100 200 紫霞仙子 */ 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()) { //取出在线用户id String onLineUserId = iterator.next().toString(); if (!onLineUserId.equals(message.getSender())) {//排除群发音讯的这个用户 //进行转发message ObjectOutputStream oos = new ObjectOutputStream(hm.get(onLineUserId).getSocket().getOutputStream()); oos.writeObject(message); } } } else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)) { //依据getter id 获取到对应的线程,将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(); } } }}
com/hspedu/utils/Utility.java
package com.hspedu.utils;/** 工具类的作用: 解决各种状况的用户输出,并且可能依照程序员的需要,失去用户的控制台输出。*/import java.util.Scanner;/** */public class Utility { //动态属性。。。 private static Scanner scanner = new Scanner(System.in); /** * 性能:读取键盘输入的一个菜单选项,值:1——5的范畴 * @return 1——5 */ public static char readMenuSelection() { char c; for (; ; ) { String str = readKeyBoard(1, false);//蕴含一个字符的字符串 c = str.charAt(0);//将字符串转换成字符char类型 if (c != '1' && c != '2' && c != '3' && c != '4' && c != '5') { System.out.print("抉择谬误,请从新输出:"); } else break; } return c; } /** * 性能:读取键盘输入的一个字符 * @return 一个字符 */ public static char readChar() { String str = readKeyBoard(1, false);//就是一个字符 return str.charAt(0); } /** * 性能:读取键盘输入的一个字符,如果间接按回车,则返回指定的默认值;否则返回输出的那个字符 * @param defaultValue 指定的默认值 * @return 默认值或输出的字符 */ public static char readChar(char defaultValue) { String str = readKeyBoard(1, true);//要么是空字符串,要么是一个字符 return (str.length() == 0) ? defaultValue : str.charAt(0); } /** * 性能:读取键盘输入的整型,长度小于2位 * @return 整数 */ public static int readInt() { int n; for (; ; ) { String str = readKeyBoard(10, false);//一个整数,长度<=10位 try { n = Integer.parseInt(str);//将字符串转换成整数 break; } catch (NumberFormatException e) { System.out.print("数字输出谬误,请从新输出:"); } } return n; } /** * 性能:读取键盘输入的 整数或默认值,如果间接回车,则返回默认值,否则返回输出的整数 * @param defaultValue 指定的默认值 * @return 整数或默认值 */ public static int readInt(int defaultValue) { int n; for (; ; ) { String str = readKeyBoard(10, true); if (str.equals("")) { return defaultValue; } //异样解决... try { n = Integer.parseInt(str); break; } catch (NumberFormatException e) { System.out.print("数字输出谬误,请从新输出:"); } } return n; } /** * 性能:读取键盘输入的指定长度的字符串 * @param limit 限度的长度 * @return 指定长度的字符串 */ public static String readString(int limit) { return readKeyBoard(limit, false); } /** * 性能:读取键盘输入的指定长度的字符串或默认值,如果间接回车,返回默认值,否则返回字符串 * @param limit 限度的长度 * @param defaultValue 指定的默认值 * @return 指定长度的字符串 */ public static String readString(int limit, String defaultValue) { String str = readKeyBoard(limit, true); return str.equals("")? defaultValue : str; } /** * 性能:读取键盘输入的确认选项,Y或N * 将小的性能,封装到一个办法中. * @return Y或N */ public static char readConfirmSelection() { System.out.println("请输出你的抉择(Y/N): 请小心抉择"); char c; for (; ; ) {//有限循环 //在这里,将承受到字符,转成了大写字母 //y => Y n=>N String str = readKeyBoard(1, false).toUpperCase(); c = str.charAt(0); if (c == 'Y' || c == 'N') { break; } else { System.out.print("抉择谬误,请从新输出:"); } } return c; } /** * 性能: 读取一个字符串 * @param limit 读取的长度 * @param blankReturn 如果为true ,示意 能够读空字符串。 * 如果为false示意 不能读空字符串。 * * 如果输出为空,或者输出大于limit的长度,就会提醒从新输出。 * @return */ private static String readKeyBoard(int limit, boolean blankReturn) { //定义了字符串 String line = ""; //scanner.hasNextLine() 判断有没有下一行 while (scanner.hasNextLine()) { line = scanner.nextLine();//读取这一行 //如果line.length=0, 即用户没有输出任何内容,间接回车 if (line.length() == 0) { if (blankReturn) return line;//如果blankReturn=true,能够返回空串 else continue; //如果blankReturn=false,不承受空串,必须输出内容 } //如果用户输出的内容大于了 limit,就提醒重写输出 //如果用户如的内容 >0 <= limit ,我就承受 if (line.length() < 1 || line.length() > limit) { System.out.print("输出长度(不能大于" + limit + ")谬误,请从新输出:"); continue; } break; } return line; }}