网络聊天室————韩顺平版qq(有离线功能)

一、项目开发

网络聊天室————韩顺平版qq(有离线功能)_第1张图片

二、需求分析

网络聊天室————韩顺平版qq(有离线功能)_第2张图片

三、架构图(精华)

网络聊天室————韩顺平版qq(有离线功能)_第3张图片

四、功能展现
服务端
网络聊天室————韩顺平版qq(有离线功能)_第4张图片
客户端
登录
网络聊天室————韩顺平版qq(有离线功能)_第5张图片

多用户
网络聊天室————韩顺平版qq(有离线功能)_第6张图片
私聊
网络聊天室————韩顺平版qq(有离线功能)_第7张图片

接收
在这里插入图片描述
群聊
网络聊天室————韩顺平版qq(有离线功能)_第8张图片

网络聊天室————韩顺平版qq(有离线功能)_第9张图片
文件传输
网络聊天室————韩顺平版qq(有离线功能)_第10张图片
网络聊天室————韩顺平版qq(有离线功能)_第11张图片
服务器推送
网络聊天室————韩顺平版qq(有离线功能)_第12张图片
网络聊天室————韩顺平版qq(有离线功能)_第13张图片
离线信息(登录之后把离线的时候别人发的信息发送,支持多条)
网络聊天室————韩顺平版qq(有离线功能)_第14张图片

五、源码

服务端:
网络聊天室————韩顺平版qq(有离线功能)_第15张图片
因为序列化和反序列化的原因,在转送user和Message类型的对象的时候,包名也要一样,不然会报错。
客户端和服务端之间的通信对象

package com.hwk.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[]  fileByte;

    public byte[] getFileByte() {
        return fileByte;
    }

    public void setFileByte(byte[] fileByte) {
        this.fileByte = fileByte;
    }

    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 getSource() {
        return source;
    }

    public void setSource(String source) {
        this.source = source;
    }

    private int fileLen;
    private String dest;
    private String source;
    public Message(String sender, String getter, String content, String sendTime) {
        this.sender = sender;
        this.getter = getter;
        this.content = content;
        this.sendTime = sendTime;
    }
    public  Message(){

    }
    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;
    }

    @Override
    public String toString() {
        return "Message{" +
                "sender='" + sender + '\'' +
                ", getter='" + getter + '\'' +
                ", content='" + content + '\'' +
                ", sendTime='" + sendTime + '\'' +
                ", mesType='" + mesType + '\'' +
                '}';
    }
}

登录用到的

package com.hwk.qqcommon;

public interface MessageType {
    String MESSAGE_LONG_SUCCESS = "1";//登陆成功的
    String MESSAGE_LONG_FAILE = "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_T0_ALL_MES = "7";//群发的消息
    String MESSAGE_FILE_MES = "8";//文件的发送
}

登录的类型

package com.hwk.qqcommon;

import java.io.Serializable;

/*
*@author hwk
* 表示一个用户信息
*/
public class User implements Serializable {
    private String userId;
    private String passwd;
    private static final long serialVersionUID = 1L;//提高兼容性
    public User(String userId, String passwd) {
        this.userId = userId;
        this.passwd = passwd;
    }
    public User(){

    }
    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;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId='" + userId + '\'' +
                ", passwd='" + passwd + '\'' +
                '}';
    }


}

服务器的登录管理

package com.hwk.qqserve;



import com.hwk.qqcommon.Message;
import com.hwk.qqcommon.MessageType;
import com.hwk.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.Iterator;
import java.util.concurrent.ConcurrentHashMap;
//服务器管理
public class qqserver {
    //提升成员变量,扩大作用域
    private ServerSocket serverSocket;
    //线程安全,比hashmap安全
    private static ConcurrentHashMap validUser = new ConcurrentHashMap<>(15);
    private static ConcurrentHashMap> messdb = new ConcurrentHashMap<>();
   // 离线信息的存储
    public static ConcurrentHashMap> getMessdb() {
        return qqserver.messdb;
    }

    public  static  ArrayList getlxList(String key1){
            return qqserver.messdb.get(key1);
    }
    public static void addMessdb(String key ,Message message) {
       ArrayList arrayList = new ArrayList<>();
       if(qqserver.getlxList(key) != null){
           arrayList = qqserver.getlxList(key);
       }
       arrayList.add(message);
       qqserver.messdb.put(key,arrayList);
    }
    //初始化
    static {
        validUser.put("100",new User("100","123"));
        validUser.put("hwk",new User("hwk","12345"));
        validUser.put("200",new User("200","123"));
        validUser.put("300",new User("300","123"));
        validUser.put("舞剑",new User("舞剑","123"));
        validUser.put("紫霞",new User("紫霞","321"));
    }
    //构造函数里执行
    public qqserver() {
        System.out.println("服务器在9990端口监听");
        try {
            serverSocket = new ServerSocket(9990);
            new Thread(new sendNewstoAll()).start();
            Socket socket;
            while (true){
                socket = serverSocket.accept();
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                //接收客户端发的用户信息是否在服务端有记录
                User user1 = (User) ois.readObject();
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                Message mes = new Message();
                //有记录就放回MESSAGE_LONG_SUCCESS类型的mes
                if(checkUser(user1.getUserId(),user1.getPasswd())){
                    mes.setMesType(MessageType.MESSAGE_LONG_SUCCESS);
                    oos.writeObject(mes);
                    servserConnectClinetThread servserConnectClinetThread = new servserConnectClinetThread(socket, user1.getUserId());
                    servserConnectClinetThread.start();
                    ManageClientConnectServe.addClentConnetServierThread(user1.getUserId(),servserConnectClinetThread);

                    ArrayList arrayList = null;
                    if((arrayList = qqserver.getlxList(user1.getUserId())) != null){
                        for (Message mes1 : arrayList ) {
                            ObjectOutputStream oos1 =  new ObjectOutputStream(servserConnectClinetThread.getSocket().getOutputStream());
                            oos1.writeObject(mes1);
                            System.out.println(mes1.getSender()+"对"+mes1.getGetter()+"说:"+mes1.getContent());
                        }
                        qqserver.messdb.remove(user1.getUserId());
                    }
                }
                //发送登录失败的记录
                else{
                    mes.setMesType(MessageType.MESSAGE_LONG_FAILE);
                    oos.writeObject(mes);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            //循环最后关闭serverSocket
            try {
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    //检测用户传的user是否在map里面
    public boolean checkUser(String userId,String pwd){
        User user = validUser.get(userId);
        if(user == null){
            return false;
        }
        if(!user.getPasswd().equals(pwd)){
            return false;
        }
        return true;
    }
}

服务器的线程管理

package com.hwk.qqserve;

import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

//服务端线程管理,包括socekt的属性
public class ManageClientConnectServe {
    private static ConcurrentHashMap hm = new ConcurrentHashMap<>();
    //将线程加到map里面
    public static void addClentConnetServierThread(String userId, servserConnectClinetThread servserConnectClinetThread){
        hm.put(userId,servserConnectClinetThread);
    }
    //获得id线程
    public static servserConnectClinetThread getservserConnectClinetThread(String userId){
        return hm.get(userId);
    }
    //获得当前在线用户名
    public static String getOnlineUsers(){
        Set keySet = hm.keySet();
        Iterator iterator = keySet.iterator();
        String onlineUserList = "";
        while (iterator.hasNext()){
            onlineUserList += iterator.next().toString()+" ";
        }
        return onlineUserList;
    }
    //
    public static void removeConnectClinetThread(String userid){
        hm.remove(userid);
    }

    public static ConcurrentHashMap getHm() {
        return hm;
    }
}

服务器线程管理

package com.hwk.qqserve;


import com.hwk.qqcommon.Message;
import com.hwk.qqcommon.MessageType;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;

//自定义线程
public class servserConnectClinetThread extends Thread{
    private Socket socket;
    private String userId;
    public servserConnectClinetThread(Socket socket, String userId) {
        this.socket = socket;
        this.userId = userId;
    }
    @Override
    public void run() {
        while (true){
            //不断循环执行
            System.out.println("服务器和客服端保持通信");
            try {
                //读取用户发的信息
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                System.out.println("服务器和客户端"+userId+"保持通信,读取数据中");
                Message mes = (Message) ois.readObject();
                //判断是否是请求用户列表的
                if(mes.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)){
                    System.out.println(mes.getSender()+"要在线用户的列表");
                    //放回用户列表
                    String onlineUser = ManageClientConnectServe.getOnlineUsers();
                    Message mes1 = new Message();
                    //十设置相关属性
                    mes1.setContent(onlineUser);
                    mes1.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
                    mes1.setGetter(mes.getSender());
                    mes1.setSender(userId);
                    //发出
                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                    oos.writeObject(mes1);

                }
                else if(mes.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)){
                    System.out.println(mes.getSender()+"退出系统了");
                    ManageClientConnectServe.removeConnectClinetThread(mes.getSender());
                    socket.close();
                    break;
                }
                else if(mes.getMesType().equals(MessageType.MESSAGE_COMM_MES)){

                    try {
                        servserConnectClinetThread servserConnectClinetThread = ManageClientConnectServe.getservserConnectClinetThread(mes.getGetter());
                        //看接收方是否登录
                        if(servserConnectClinetThread != null){
                            System.out.println(mes.getSender()+"对"+mes.getGetter()+"说:"+mes.getContent());
                            ObjectOutputStream oos =  new ObjectOutputStream(servserConnectClinetThread.getSocket().getOutputStream());
                            oos.writeObject(mes);
                        }
                        //离线信息的存储
                         else{
                            qqserver.addMessdb(mes.getGetter(),mes);
                        }
                    } catch (IOException e) {

                    }
                }
                else if(mes.getMesType().equals(MessageType.MESSAGE_T0_ALL_MES)){
                    String onlineName = "";
                    ConcurrentHashMap hm = ManageClientConnectServe.getHm();
                    Iterator iterator = hm.keySet().stream().iterator();
                    while(iterator.hasNext()){
                        onlineName = iterator.next().toString();
                        if(!onlineName.equals(mes.getSender())){
                            ObjectOutputStream oos = new ObjectOutputStream(hm.get(onlineName).getSocket().getOutputStream());
                            oos.writeObject(mes);
                        }
                    }
                    System.out.println(mes.getSender()+"对大家说:"+mes.getContent());
                }
                else if(mes.getMesType().equals(MessageType.MESSAGE_FILE_MES)){
                    ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServe.getservserConnectClinetThread(mes.getGetter()).getSocket().getOutputStream());
                    oos.writeObject(mes);
                }
                else{
                    System.out.println("其他message,不做处理!");
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } finally {
            }
        }
    }

    public Socket getSocket() {
        return socket;
    }

    public void setSocket(Socket socket) {
        this.socket = socket;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }
}

服务器实现消息推送和退出

package com.hwk.qqserve;

import com.hwk.qqcommon.Message;
import com.hwk.qqcommon.MessageType;
import com.hwk.utils.utility;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class sendNewstoAll implements  Runnable{
    @Override
    public void run() {
        while (true) {
            System.out.println("请输入服务器要推送的新闻/消息 输入exit退出推送/quit退出服务器");
            String onlineUser = "";
            String str = utility.readString(50);
            if(str.equals("exit")){
                break;
            }
            if(str.equals("quit")){
                System.exit(0);
            }
            Message message = new Message();
            message.setSender("服务器");
            message.setContent(str);
            message.setSendTime(new Date().toString());
            message.setMesType(MessageType.MESSAGE_T0_ALL_MES);
            System.out.println("服务器给所有人说" + message.getContent());
            //遍历所有线程
            ConcurrentHashMap hm = ManageClientConnectServe.getHm();
            Set keySet = hm.keySet();
            Iterator iterator = keySet.iterator();
            while (iterator.hasNext()) {
                onlineUser = iterator.next();
                try {
                    servserConnectClinetThread servserConnectClinetThread = hm.get(onlineUser);
                    ObjectOutputStream oos = new ObjectOutputStream(servserConnectClinetThread.getSocket().getOutputStream());
                    oos.writeObject(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

入口

package com.hwk.qqframe;

import com.hwk.qqserve.qqserver;

//创建server,启动后台服务
public class qqframe {
    public static void main(String[] args) {
        new qqserver();
    }
}


客户端
网络聊天室————韩顺平版qq(有离线功能)_第16张图片
界面的

package com.hwk.qqclient.view;

import com.hwk.qqclient.service.FileClientService;
import com.hwk.qqclient.service.UserClientService;
import com.hwk.qqclient.service.messageClient;
import com.hwk.qqclient.utils.utility;


public class view {
    private boolean loop =true;
    private String key = "";
    //定义用户工具成员
    private final   UserClientService userClientService = new UserClientService();
    //信息控制的
    private messageClient messageClient1 = new messageClient();
    //文件处理的
    private FileClientService fileClientService  = new FileClientService();
    public static void main(String[] args) throws Exception{
        new view().mainMenu();
    }
    //菜单
    private void mainMenu() throws Exception{
        while (loop){
            System.out.println("-------------欢迎登录网络通信系统-------------");
            System.out.println("\t\t 1 登录系统");
            System.out.println("\t\t 9 退出系统");
            System.out.println("输入你的需求:");
            key = utility.readString(1);
            switch(key){
                case "1":
                    System.out.println("输入用户号");
                    String userId = utility.readString(50);
                    System.out.println("输入密码");
                    String pwd = utility.readString(50);
                    //判断登陆是否成功
                    if(userClientService.checkedUser(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.println("输入你的选择:");
                            key = utility.readString(1);
                            switch (key){
                                case "1":
                                    //在线用户显示
                                    userClientService.onlineFreindList();
                                    break;
                                case "2":
                                    System.out.println("输入想对大家说的话:");
                                    String str = utility.readString(100);
                                    messageClient1.sendMessagetoAll(str,userId);
                                    //群聊方法
                                    break;
                                case "3":
                                    System.out.println("输入想私聊的用户名");
                                    String getterId = utility.readString(50);
                                    System.out.println("输入想说的话:");
                                    String content = utility.readString(100);
                                    //编写方法,发送请求给服务器,私聊
                                    messageClient1.sendMessagetoOne(content,userId,getterId);
                                    break;
                                case "4":
                                    System.out.println("输入目标用户");
                                    String getterId1 = utility.readString(50);
                                    System.out.println("输入你的文件地址(形式 d:\\\\xx.jpg)");
                                    String src = utility.readString(50);
                                    System.out.println("输入目标用户的文件地址");
                                    String dest = utility.readString(51);
                                   // messageClient1.
                                    fileClientService.sendFiletoOne(src,dest,userId,getterId1);
                                    break;
                                case "9":
                                    loop = false;
                                    //通知服务端退出系统
                                    userClientService.logout();
                                    break;
                            }
                        }
                    }
                    else{
                        System.out.println("-------------登录失败-------------");
                    }
                    break;
                case "9":
                    loop = false;
                    break;
            }
        }
    }
}

工具类

package com.hwk.qqclient.utils;


/**
 工具类的作用:
 处理各种情况的用户输入,并且能够按照程序员的需求,得到用户的控制台输入。
 */

import java.util.*;
/**


 */
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;
    }
}


客户端线程管理

package com.hwk.qqclient.service;


import java.util.concurrent.ConcurrentHashMap;

//线程管理,是一个ConcurrentHashMap,线程安全
public class ManageClientConnectServe {
    private static ConcurrentHashMap hm = new ConcurrentHashMap<>();
    public static void addClentConnetServierThread(String userId, clientContentServerThread clientContentServerThread){
        hm.put(userId,clientContentServerThread);
    }
    public static clientContentServerThread getclientContentServerThread(String userId){
        return hm.get(userId);
    }
}

客户端的线程

package com.hwk.qqclient.service;



import com.hwk.qqcommon.Message;
import com.hwk.qqcommon.MessageType;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;
//用户线程,线程里有socket属性来联系服务
public class clientContentServerThread extends Thread{
    private Socket socket;
    public clientContentServerThread(Socket socket){
        this.socket = socket;
    }

    public Socket getSocket() {
        return socket;
    }

    @Override
    public void run() {
        ObjectInputStream ois = null;
        System.out.println("客户端线程等待,从服务器端来的数据");
       while (true){
           try {
                ois = new ObjectInputStream(socket.getInputStream());
//               如果服务器没有发送Message对象,线程会阻塞在这里
               Message mes = (Message) ois.readObject();
               if(mes.getMesType().equals(MessageType.MESSAGE_RET_ONLINE_FRIEND)){
                   String[] onlienUser = mes.getContent().split(" ");
                   System.out.println("当前在线的用户列表");
                   for(int i = 0;i < onlienUser.length; i++){
                       System.out.println("用户"+onlienUser[i]);
                   }
               }
               else if(mes.getMesType().equals(MessageType.MESSAGE_COMM_MES)){
                   System.out.println("\n"+mes.getSender()+"对"+mes.getGetter()+"说:"+mes.getContent());
               }
               else if(mes.getMesType().equals(MessageType.MESSAGE_T0_ALL_MES)){
                   System.out.println("\n"+mes.getSender()+"对大家说:"+mes.getContent());
               }
               else if(mes.getMesType().equals(MessageType.MESSAGE_FILE_MES)){
                   System.out.println("\n"+mes.getSender()+"给"+mes.getGetter()+"发文件"+mes.getSource()+"到"+mes.getDest());
                   FileOutputStream fileOutputStream = new FileOutputStream(mes.getDest());
                   fileOutputStream.write(mes.getFileByte());
                   fileOutputStream.close();
                   System.out.println("保存文件成功");
               }
               else{
                   System.out.println("其他类型,不做处理的");
               }
           } catch (IOException | ClassNotFoundException e) {
               e.printStackTrace();
           }
       }
    }
}

线程的登录工具类

package com.hwk.qqclient.service;

import com.hwk.qqcommon.Message;
import com.hwk.qqcommon.MessageType;
import com.hwk.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 user1 = new User();
    private Socket socket;
    public  boolean checkedUser(String name,String pwd) throws Exception{
        boolean b =false;
        user1.setUserId(name);
        user1.setPasswd(pwd);
        socket = new Socket(InetAddress.getByName("127.0.0.1"), 9990);
        //发送用户信息给服务器
        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
        oos.writeObject(user1);
        //读取服务器的信息
        ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
        Message mes = (Message) ois.readObject();
        //判断服务器发送的信息的类型,是MESSAGE_LONG_SUCCESS就登录成功
        if(mes.getMesType().equals(MessageType.MESSAGE_LONG_SUCCESS)){
            //创建自定义线程
            clientContentServerThread ccst = new clientContentServerThread(socket);
            ccst.start();
            //放入线程池,为了多线程
            b = true;
            ManageClientConnectServe.addClentConnetServierThread(name,ccst);
        }
        else{
            //登录不成功关闭主线程的套字节
            socket.close();
        }
        //返回是否登录成功
        return b;
    }
    //向服务端获取在线用户的列表
    public void onlineFreindList() throws Exception{
        Message mes = new Message();
        //设置请求类型
        mes.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);
        //说明发送者
        mes.setSender(user1.getUserId());
        try {
            //客户端也可以开启多个线程,所有不能直接用socket,取得当前线程的读取流
            ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServe.getclientContentServerThread(user1.getUserId()).getSocket().getOutputStream());
            oos.writeObject(mes);//发送要用户请求
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //通知服务端退出系统
    public void logout(){
        Message mes = new Message();
        mes.setMesType(MessageType.MESSAGE_CLIENT_EXIT);
        mes.setSender(user1.getUserId());
        try {
            ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServe.getclientContentServerThread(user1.getUserId()).getSocket().getOutputStream());
            oos.writeObject(mes);
            System.out.println(user1.getUserId()+"退出系统了");
            System.exit(0);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }
    }
}

线程的私聊工具类

package com.hwk.qqclient.service;

import com.hwk.qqcommon.Message;
import com.hwk.qqcommon.MessageType;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;

//提供给消息相关的方法
public class messageClient {
    public void sendMessagetoOne(String content,String senderId,String getterId){
        Message mes = new Message();
        mes.setSender(senderId);
        mes.setContent(content);
        mes.setGetter(getterId);
        mes.setSendTime(new Date().toString());
        mes.setMesType(MessageType.MESSAGE_COMM_MES);

        System.out.println(senderId+"对"+getterId+"说:"+content);
        try {
            ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServe.getclientContentServerThread(senderId).getSocket().getOutputStream());
            oos.writeObject(mes);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }
    }
    public void sendMessagetoAll(String content,String senderId){
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_T0_ALL_MES);
        message.setContent(content);
        message.setSender(senderId);
        message.setSendTime(new Date().toString());
        System.out.println(senderId+"对大家说:"+content);
        try {
            ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServe.getclientContentServerThread(senderId).getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

线程的文件传输工具类

package com.hwk.qqclient.service;


import com.hwk.qqcommon.Message;
import com.hwk.qqcommon.MessageType;

import java.io.*;

public class FileClientService {
    public void sendFiletoOne(String src,String dest,String senderId,String getterId) throws IOException {
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_FILE_MES);
        message.setSender(senderId);
        message.setGetter(getterId);
        message.setSource(src);
        message.setDest(dest);
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(src);
            byte[] bytes = new byte[(int) (new File(src).length())];
            fileInputStream.read(bytes);
            message.setFileByte(bytes);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fileInputStream !=null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        ObjectOutputStream oos =new ObjectOutputStream(ManageClientConnectServe.getclientContentServerThread(senderId).getSocket().getOutputStream());
        oos.writeObject(message);
        System.out.println(message.getSender()+"发送"+message.getGetter()+"文件"+message.getSource());
    }
}

qqcomom包里的类和服务端的一模一样。

六、离线功能思路

之前的功能,韩老师已经讲得很透彻了,不懂的建议去b站上去看,
这个离线本质就是服务端有一个hashmap(就理解成一个特殊的数组,只是这个数组的索引不是数组,变成了别的类型,必须唯一))。
用户发送私聊信息的时候,服务器接收,在转发的时候根据getterId判断,线程的集合里面是否有这个接收者的线程,也就是接收者登录美,如果登录,就转发,如果不登录就存到准备好的消息集合里;
此外用户在登录的时候,服务器需要根据客户端发送的这个user的里面的userid来判断这个消息集合里是否为空,也就是这个用户离线的时候有没有人发私聊信息给他,如果为空,则没有,就不管;如果有,就通过服务器遍历集合里该对象的消息列表,逐条转发过去,并且在转发之后,删除在集合里关于这个客户端的离线消息。
在转发的时候,每循环一次需要建立一个输出流,否则会报错。
网络聊天室————韩顺平版qq(有离线功能)_第17张图片

你可能感兴趣的:(网络,java,github)