1、用户登录
2、拉取在线用户列表
3、无异常退出(客户端,服务端)
4、思路
5、群聊
6、发文件
7、服务器推送新闻
public class QQView {
private boolean loop = true;//控制是否显示菜单
private String key = "";//接收用户的键盘输入
private UserClientService userClientService = new UserClientService();//用于登录服务器、注册用户
private MessageService messageService = new MessageService();
private ClientFileService clientFileService = new ClientFileService();
//显示主菜单
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();
//根据用户输入来处理不同的逻辑
switch (key) {
case "1":
System.out.print("请输入用户号:");
String uid = Utility.readString();
System.out.print("请输入密 码:");
String password = Utility.readString();
//这里比较麻烦了,需要到服务端验证
if (userClientService.checkUser(uid,password)) {
//并移除该用户的离线留言内容
messageService.removeMsg(uid);
System.out.println("============欢迎(用户" + uid + ")登录成功============");
while (loop) {
//进入到二级菜单
System.out.println("============网络通信系统二级菜单(用户" + uid + ")============");
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();
switch (key) {
case "1":
userClientService.getUsers(uid);
break;
case "2":
System.out.println("\n请输入你的群发内容");
String contentAll = Utility.readString();
messageService.sendMsg(contentAll,uid);
break;
case "3":
System.out.println("\n请输入你要聊天的用户号(在线):");
String getter = Utility.readString();
System.out.println("\n请输入你要私聊的内容");
String content = Utility.readString();
//发送消息
messageService.sendMsg(content,getter,uid);
break;
case "4":
System.out.print("请输入你想把文件发送给的用户(在线用户):");
getter = Utility.readString();
System.out.print("请输入发送文件的路径(形式如 d:\\xx.jpg):");
String src = Utility.readString();
System.out.print("请输入把文件发送到对方的路径(形式如 d:\\xx.jpg):");
String dest = Utility.readString();
clientFileService.sendFileToOne(src,dest,uid,getter);
break;
case "9":
loop = false;
System.out.println("客户端退出系统");
userClientService.exit(uid);
break;
default:
System.out.println("输入错误");
}
}
} else {
System.out.println("登录失败");
}
break;
case "9":
loop = false;
System.out.println("客户端退出系统");
System.exit(0);
break;
default:
System.out.println("输入有误");
}
}
}
public static void main(String[] args) {
new QQView().mainMenu();
}
}
public class Utility {
private static Scanner sc = null;
static {
sc = new Scanner(System.in);
}
public static String readString(){
String next = sc.nextLine();
return next;
}
}
public class UserClientService {
//因为可能在其他地方需要使用到这个user信息,因此作成成员属性。
private User user = new User();
//因为socket在其他地方也可能使用,因此也做成属性
private Socket socket;
/**
* 根据用户id和密码 到服务器验证该用户是否有效
* @param userId 用户id
* @param pwd 用户密码
* @return 返回值
*/
public boolean checkUser(String userId,String pwd){
boolean flag = false;
user.setUserId(userId);
user.setPassword(pwd);
//连接到服务端,发送user对象
try {
//创建socket
socket = new Socket(InetAddress.getByName("127.0.0.1"), 9999);
//得到ObjectOutputStream
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
//发送user对象
oos.writeObject(user);
//读取服务端回复
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message)ois.readObject();
//判断是否登录成功
if(MessageType.MESSAGE_LOGIN_SUCCESS.equals(message.getMsgType())){
//如果有留言信息时,显示出来
if (!("".equals(message.getContent()))){
System.out.println("亲,你有离线留言信息哦");
System.out.println(message.getContent());
}
//创建一个和服务器端保持通信的线程-->创建线程类
ClientConnectServerThread clientConnectServerThread = new ClientConnectServerThread(socket);
//启动客户端线程
clientConnectServerThread.start();
//为了后面客户端的扩展,将线程放到集合中管理
ManageClientConnectServerThread.addClientConnectServerThread(userId,clientConnectServerThread);
flag = true;
}else {
//如果登录失败,则不能启动和服务器通信的线程,关闭socket
socket.close();
return false;
}
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
/**
* 向服务器端请求获取在线用户列表
* @return 在线用户列表
*/
public void getUsers(String userId){
Message message = new Message();
message.setSender(userId);
message.setMsgType(MessageType.MESSAGE_GET_ONLINE_FRIEND);
try {
//得到userId 得到线程对象,通过线程得到关联的socket 对应的 ObjectOutputStream
ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServerThread
.getClientConnectServerThread(userId)
.getSocket()
.getOutputStream());
//发送一个message,向服务端获取一个在线用户列表
oos.writeObject(message);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 退出客户端系统
* @param userId 用户id
*/
public void exit(String userId){
Message message = new Message();
message.setSender(userId);
message.setMsgType(MessageType.MESSAGE_CLIENT_EXIT);
try {
//得到userId 得到线程对象,通过线程得到关联的socket 对应的 ObjectOutputStream
ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServerThread
.getClientConnectServerThread(userId)
.getSocket()
.getOutputStream());
//发送一个message,向服务端获取一个在线用户列表
oos.writeObject(message);
System.exit(0);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class MessageService {
/**
* 给指定用户发送内容
* @param content 内容
* @param getter 指定用户
*/
public void sendMsg(String content,String getter,String userId){
Message message = new Message();
message.setMsgType(MessageType.MESSAGE_COMM_MES);
message.setSender(userId);
message.setContent(content);
message.setGetter(getter);
message.setSendTime(new Date().toString());
ObjectOutputStream oos = null;
try {
//获取当前用户线程的socket
oos = new ObjectOutputStream(
ManageClientConnectServerThread.getClientConnectServerThread(
userId)
.getSocket()
.getOutputStream());
oos.writeObject(message);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 群发内容
* @param content 内容
* @param userId 发送者id
*/
public void sendMsg(String content,String userId){
Message message = new Message();
message.setMsgType(MessageType.MESSAGE_COMM_MES_ALL);
message.setContent(content);
message.setGetter("所有人");
message.setSender(userId);
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(
ManageClientConnectServerThread.getClientConnectServerThread(userId).getSocket().getOutputStream());
oos.writeObject(message);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 移除用户离线留言的内容
* @param userId 用户id
*/
public void removeMsg(String userId){
try {
Message message = new Message();
message.setSender(userId);
message.setMsgType(MessageType.MESSAGE_REMOVE_MSG);
ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(userId).getSocket().getOutputStream());
oos.writeObject(message);
}catch (Exception e){
e.printStackTrace();
}
}
}
public class ClientFileService {
/**
* 发送文件
* @param src 源文件
* @param dest 把该文件传输到对方目录
* @param senderId 发送者id
* @param getterId 接收者id
*/
public void sendFileToOne(String src,String dest,String senderId,String getterId){
Message message = new Message();
message.setMsgType(MessageType.MESSAGE_SENDFILE);
message.setSender(senderId);
message.setGetter(getterId);
message.setDest(dest);
message.setSrc(src);
//创建文件数组
int fileLen = (int) new File(src).length();
byte[] fileBytes = new byte[fileLen];
FileInputStream fis = null;
try {
fis = new FileInputStream(src);
fis.read(fileBytes);
message.setFileBytes(fileBytes);
message.setFileLen(fileLen);
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if (fis != null) fis.close();
}catch (Exception 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 (Exception e){
e.printStackTrace();
}
}
}
public class ManageClientConnectServerThread {
//把多个线程放入到HashMap集合中,key 就是用户id,value 就是线程
private static HashMap<String,ClientConnectServerThread> hm = new HashMap<>();
//将某个线程加入到集合
public static void addClientConnectServerThread(String userId,ClientConnectServerThread clientThread){
hm.put(userId,clientThread);
}
//通过userId 获取对应线程
public static ClientConnectServerThread getClientConnectServerThread(String userId){
return hm.get(userId);
}
}
public class ClientConnectServerThread extends Thread{
//该线程要持有socket
private Socket socket;
//构造器接受Socket
public ClientConnectServerThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
//因为线程需要在后台和服务端通信,因此做成无限循环
while (true){
try {
System.out.println("客户端线程。等待读取从服务端发送的消息");
//读取服务端写回的信息
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
//如果服务器没有发送message对象,线程会阻塞在这里
Message message = (Message)ois.readObject();
//判断Message的类型,然后做相应的业务处理
if (MessageType.MESSAGE_RETURN_ONLINE_FRIEND.equals(message.getMsgType())){
//这个业务是获取在线用户逻辑
String[] onLine = message.getContent().split(" ");
System.out.println("\n============当前用户在线数============");
for (int i = 0; i < onLine.length; i++) {
System.out.println("用户:"+onLine[i]);
}
}else if(MessageType.MESSAGE_COMM_MES.equals(message.getMsgType())){//这个业务是获取私聊消息
System.out.println("\n"+message.getSender()+" 对 "+message.getGetter() + " 说:"+message.getContent());
}else if(MessageType.MESSAGE_COMM_MES_ALL.equals(message.getMsgType())){//这个业务是获取群发消息
System.out.println("\n"+message.getSender()+" 对 "+message.getGetter() + " 说:"+message.getContent());
}else if(MessageType.MESSAGE_SENDFILE.equals(message.getMsgType())){//是文件发送的逻辑
System.out.println("\n"+message.getSender() + " 给 " + message.getGetter()
+ " 发送了文件,到我的电脑的 "+message.getDest());
FileOutputStream fos = new FileOutputStream(message.getDest());
fos.write(message.getFileBytes());
//关闭流
fos.close();
System.out.println("文件保存成功!");
}else {//其他类型
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//为方便得到socket
public Socket getSocket() {
return socket;
}
public class Application {
public static void main(String[] args) {
new QQServer();
}
}
public class Utility {
private static Scanner sc = null;
static {
sc = new Scanner(System.in);
}
public static String readString(){
String next = sc.nextLine();
return next;
}
}
public class QQServer {
private ServerSocket ss = null;
//创建一个集合存放多个用户,如果是这些用户登录,就认为是合法的
private static HashMap<String,User> validUsers = new HashMap<>();
//可以处理并发的集合,保证线程
// private static ConcurrentHashMap validUsers = new ConcurrentHashMap<>();
static {
validUsers.put("123",new User("123","123"));
validUsers.put("admin",new User("admin","123"));
validUsers.put("111",new User("111","123"));
validUsers.put("100",new User("100","123"));
validUsers.put("至尊宝",new User("至尊宝","123"));
validUsers.put("紫霞仙子",new User("紫霞仙子","123"));
validUsers.put("菩提老祖",new User("菩提老祖","123"));
}
//验证用户是否有效
private boolean checkUser(String userId,String pwd){
User user = validUsers.get(userId);
if (user == null){
return false;
}else {
if (!(userId.equals(user.getUserId()) && pwd.equals(user.getPassword()))){
return false;
}
}
return true;
}
public QQServer(){
System.out.println("服务端在9999 端口监听");
new Thread(new SendNewsToAllService()).start();
try {
//监听是循环的,当和某个客户端建立连接后,会继续监听
ss = new ServerSocket(9999);
while (true){
//如果没有客户端连接就会阻塞,有客户端连接就往下走
Socket socket = ss.accept();
//得到socket关联的对象输入流
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
//读取客户端发送的user对象
User user = (User)ois.readObject();
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
//验证
if (checkUser(user.getUserId(), user.getPassword())){
Message message = new Message("系统", user.getUserId(), "登录成功", "", MessageType.MESSAGE_LOGIN_SUCCESS);
//根据用户id,从容器中获取用户相关信息
String msg = ManagerUserMessage.getMessage(user.getUserId());
if (!("".equals(msg))){
message.setContent(msg);
}
oos.writeObject(message);
//创建一个线程,和客户端保持通信,该线程需要持有Socket对象
ServerConnectClientThread serverConnectClientThread = new ServerConnectClientThread(socket,user.getUserId());
//启动线程
serverConnectClientThread.start();
//把线程放入集合中进行管理
ManagerClientThreads.addServerConnectClientThread(user.getUserId(),serverConnectClientThread);
}else {
System.out.println("用户 id="+user.getUserId()+" pwd="+user.getPassword()+" 登录失败");
Message message = new Message("系统", user.getUserId(), "登录失败", "", MessageType.MESSAGE_LOGIN_ERROR);
oos.writeObject(message);
//登录失败,关闭socket
socket.close();
}
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//如果服务器退出了while循环,说明服务器端不再监听了,因此关闭ServerSocket
try {
ss.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
public class SendNewsToAllService implements Runnable{
@Override
public void run() {
while (true){
System.out.println("请输入服务器要推送的消息【输入exit表示退出推送服务】");
String msg = Utility.readString();
if ("exit".equals(msg)) {
break;
}
//构建消息
Message message = new Message();
message.setSender("服务器");
message.setContent(msg);
message.setGetter("大家");
message.setSendTime(new Date().toString());
message.setMsgType(MessageType.MESSAGE_COMM_MES_ALL);
System.out.println("服务器推送消息给所有人 说:"+msg);
//获取所有线程的socket
String users = ManagerClientThreads.getValidUsers();
String[] userIds = users.split(" ");
if (userIds.length > 0){
for (String userId : userIds){
try {
ObjectOutputStream oos = new ObjectOutputStream(ManagerClientThreads
.getServerConnectClientThread(userId)
.getSocket()
.getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
public class ManagerUserMessage {
private static HashMap<String, ArrayList<String>> tempMessage;
static {
tempMessage = new HashMap<>();
}
public static void addMessage(String userId,String msg){
ArrayList<String> arrayList = tempMessage.get(userId);
//是否为空
if (arrayList != null){
arrayList.add(msg);
}else {
ArrayList<String> array = new ArrayList<>();
array.add(msg);
tempMessage.put(userId,array);
}
}
/**
* 根据用户id 获取离线内容
* @param userId 用户id
* @return 离线消息
*/
public static String getMessage(String userId){
ArrayList<String> arrayList = tempMessage.get(userId);
StringBuffer sb = null;
if (arrayList != null && arrayList.size() > 0){
sb = new StringBuffer();
for (String msg : arrayList){
sb.append(msg).append("\r\n");
}
return sb.toString();
}
return "";
}
/**
* 根据用户id将消息从容器中移除
* @param userId 用户id
*/
public static void removeMessage(String userId){
ArrayList<String> strings = tempMessage.get(userId);
strings = null;
tempMessage.remove(userId);
}
public static HashMap<String, ArrayList<String>> getTempMessage(){
return tempMessage;
}
}
public class ManagerClientThreads {
private static HashMap<String,ServerConnectClientThread> hm = new HashMap<>();
//添加线程对象到集合
public static void addServerConnectClientThread(String userId,ServerConnectClientThread serverConnectClientThread){
hm.put(userId,serverConnectClientThread);
}
//根据userid 返回线程
public static ServerConnectClientThread getServerConnectClientThread(String userId){
return hm.get(userId);
}
//获取在线的用户信息
public static String getValidUsers(){
StringBuffer sb = null;
Set<String> userIds = hm.keySet();
if (userIds.size() > 0){
sb = new StringBuffer();
for (String uid : userIds) {
sb.append(uid).append(" ");
}
return sb.toString();
}
return "";
}
//从集合中移除线程
public static void removeThread(String uid){
hm.remove(uid);
}
}
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() { //这里线程可以发送/接收消息
while (true){
try {
System.out.println("服务端数据=======》》");
System.out.println(ManagerUserMessage.getTempMessage());
System.out.println(ManagerClientThreads.getValidUsers());
System.out.println("服务端数据=======》》");
System.out.println("服务端和客户端"+userId+"保持通信,读取数据。。。");
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message) ois.readObject();
//如果是请求获取用户列表的
if (MessageType.MESSAGE_GET_ONLINE_FRIEND.equals(message.getMsgType())){
String onLineUsers = ManagerClientThreads.getValidUsers();
System.out.println("在线用用户列表"+onLineUsers);
//返回
Message msg = new Message();
msg.setMsgType(MessageType.MESSAGE_RETURN_ONLINE_FRIEND);
msg.setContent(onLineUsers);
//返回给客户端
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
//发送消息
oos.writeObject(msg);
}else if (MessageType.MESSAGE_CLIENT_EXIT.equals(message.getMsgType())){//如果是退出的
System.out.println(message.getSender()+" 退出");
//将客户端从 线程集合中移除
ManagerClientThreads.removeThread(message.getSender());
//关闭socket
socket.close();
//退出该线程
break;
}else if (MessageType.MESSAGE_COMM_MES.equals(message.getMsgType())){//如果是私聊
//获取对方线程的socket
//用户用户id,获取socket 并获取对应的流
ServerConnectClientThread clientThread = ManagerClientThreads.getServerConnectClientThread(message.getGetter());
if (clientThread == null){//用户不在线,存储到一个地方
ManagerUserMessage.addMessage(message.getGetter(),message.getContent());
}else {// 用户在线,走正常私聊
ObjectOutputStream oos = new ObjectOutputStream(clientThread
.getSocket()
.getOutputStream());
oos.writeObject(message);
}
}else if (MessageType.MESSAGE_COMM_MES_ALL.equals(message.getMsgType())){//如果是群发
//获取所有的在线用户
String users = ManagerClientThreads.getValidUsers();
String[] userIds = users.split(" ");
if (userIds.length > 0){
for (String uid : userIds){
//排除他自己
if (!(uid.equals(message.getSender()))){
ObjectOutputStream oos = new ObjectOutputStream(ManagerClientThreads
.getServerConnectClientThread(uid)
.getSocket()
.getOutputStream());
//发送消息
oos.writeObject(message);
}
}
}
}else if (MessageType.MESSAGE_SENDFILE.equals(message.getMsgType())){//如果是文件传输
ObjectOutputStream oos = new ObjectOutputStream(ManagerClientThreads
.getServerConnectClientThread(message.getGetter()).getSocket().getOutputStream());
//转发
oos.writeObject(message);
}else if (MessageType.MESSAGE_REMOVE_MSG.equals(message.getMsgType())){//如果是移除用户
ManagerUserMessage.removeMessage(message.getSender());
}
} catch (Exception e) {
e.printStackTrace();
}finally {
}
}
}
}
由于pojo下面的实体类代码是公共的,这里只贴一份
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 msgType;//消息类型【可以在接口中定义已知的消息类型】
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;
}
@Override
public String toString() {
return "Message{" +
"sender='" + sender + '\'' +
", getter='" + getter + '\'' +
", content='" + content + '\'' +
", sendTime='" + sendTime + '\'' +
", msgType='" + msgType + '\'' +
'}';
}
public String getMsgType() {
return msgType;
}
public void setMsgType(String msgType) {
this.msgType = msgType;
}
public Message() {
}
public Message(String sender, String getter, String content, String sendTime, String msgType) {
this.sender = sender;
this.getter = getter;
this.content = content;
this.sendTime = sendTime;
this.msgType = msgType;
}
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 interface MessageType {
//表示登录成功
String MESSAGE_LOGIN_SUCCESS = "1";
//表示登录失败
String MESSAGE_LOGIN_ERROR = "2";
//普通信息包
String MESSAGE_COMM_MES = "3";
//获取在线用户列表
String MESSAGE_GET_ONLINE_FRIEND = "4";
//返回在线用户列表
String MESSAGE_RETURN_ONLINE_FRIEND = "5";
//客户端请求退出
String MESSAGE_CLIENT_EXIT = "6";
//群聊信息
String MESSAGE_COMM_MES_ALL = "7";
//发送文件的消息
String MESSAGE_SENDFILE = "8";
//移除离线留言内容
String MESSAGE_REMOVE_MSG = "9";
}
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String userId;//用户id
private String password;//用户密码
public User(String userId, String password) {
this.userId = userId;
this.password = password;
}
public User() {
}
@Override
public String toString() {
return "User{" +
"userId='" + userId + '\'' +
", password='" + password + '\'' +
'}';
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}