socket编程-QQ聊天系统

img

  • 作者简介:大家好,我是五度鱼,一个普通的Java领域博主,不停输出Java技术博客和干货
  • 个人主页:五度鱼学Java的主页

文章目录


前言

跟随韩顺平老师学的qq项目,这篇博客记录一下自己的学习过程。


在这里插入图片描述


一、用户登录

打通客户端和服务端通信的通道

(1)需求分析

  • (1)一个客户端使用一个socket通信,一个服务端在9999端口进行监听,把socket放到一个线程里当成线程属性,开启两边线程进行循环的通信(接收发送)
  • (2)使用对象封装发送的消息,定义一个Message对象,封装各种消息
  • (3)客户端启动一个线程(也就是一个socket)发送消息,服务端也启动一个线程接收消息然后处理返回给客户端回复
  • (4)为了更好地管理socket线程,使用hashMap集合来管理该线程,把所有socket线程放到集合里,客户端可以有多个socket线程(为了满足发送消息和发送文件同时进行),单个线程只能执行完发送消息才能再发送文件

(2)需要用到的框架代码(客户端服务端各有一套)

起始包名com.wang.cn

(1)工具类Utils.java(处理键盘输入校验)
package com.wang.cn.qqclient.qqutils;


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

import java.util.Scanner;

/**


 */
public class Utils {
    //静态属性。。。
    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;
    }
}


(2)实体消息类(用户,消息)

User.java

package com.wang.cn.qqcommon;

import java.io.Serializable;

/**
 * @author 王
 * @Type User.java
 * @Desc
 * @date 2024/6/5 18:30
 * @version1.0
 */
public class User implements Serializable {
    private static final long serialVersionUID = 1L; // 或者其他长整型常量

    public User() {
    }

    public User(String userId, String passwd) {
        this.userId = userId;
        this.passwd = passwd;
    }

    //用户名
    private String userId;
    //密码
    private String 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;
    }
}

Message.java

package com.wang.cn.qqcommon;

import java.io.Serializable;

/**
 * @author 王
 * @Type Message.java
 * @Desc
 * @date 2024/6/5 18:29
 * @version1.0
 */
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 messageType;//成功标识
    private String src;//发送的源文件路径
    private String desc;//发送的目标文件路径
    private byte[] fileBytes;//封装文件的字节数组
    private int fileLen = 0;//发送文件的长度

    public String getSrc() {
        return src;
    }

    public void setSrc(String src) {
        this.src = src;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    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 getMessageType() {
        return messageType;
    }

    public void setMessageType(String messageType) {
        this.messageType = messageType;
    }

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

(3)消息类型

此处消息类型使用接口里的常量,定义一个包含多个常量的接口
Message

package com.wang.cn.qqcommon;

import com.sun.org.apache.xpath.internal.objects.XString;

/**
 * @author 王
 * @Type Messager.java
 * @Desc
 * @date 2024/6/5 18:30
 * @version1.0
 */
public interface Messager {
    String MESSAGE_LOGIN_SUCCESS = "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";//文件消息包
}

(4)客户端窗口

没有使用前端编写界面,使用窗口键盘输出界面,窗口类QQMainView.java

package com.wang.cn.qqclient.qqview;

import com.wang.cn.qqclient.qqservice.FileClientService;
import com.wang.cn.qqclient.qqservice.MessageClientService;
import com.wang.cn.qqclient.qqservice.MessageClientService;
import com.wang.cn.qqclient.qqservice.UserClientService;
import com.wang.cn.qqclient.qqutils.Utils;

import java.util.Scanner;

/**
 * @author 王
 * @Type QQViewMain.java
 * @Desc
 * @date 2024/6/5 19:13
 * @version1.0
 */
public class QQMainView {
    private boolean loop = true;
    private UserClientService userClientService = new UserClientService();
    private MessageClientService messageClientService = new MessageClientService();//提供私聊业务方法
    private FileClientService fileClientService = new FileClientService();//提供文件发送业务
    public static void main(String[] args) {
        new QQMainView().mainView();
        System.out.println("您已退出客户端...");
    }
    public void mainView(){
        while (loop) {
            System.out.println("==============欢迎登录网络通信系统=================");
            System.out.println("\t\t 1 登录系统");
            System.out.println("\t\t 9 退出系统");
            Scanner sc = new Scanner(System.in);
            System.out.print("请输入你的选择: ");
            String key = Utils.readString(1);
            switch (key) {
                case "1":
                    System.out.print("请输入你的用户名:");
                    String userId = Utils.readString(50);
                    System.out.print("请输入你的密码:");
                    String passwd = Utils.readString(50);
                    if (userClientService.checkUser(userId,passwd)) {
                        while (loop) {
                            System.out.println("===========(欢迎" + userId + "用户)============");
                            System.out.println("===========网络通信系统二级菜单(用户" + userId + ")============");
                            System.out.println("\t\t\t\t 1 显示在线用户列表");
                            System.out.println("\t\t\t\t 2 群发消息");
                            System.out.println("\t\t\t\t 3 私聊消息");
                            System.out.println("\t\t\t\t 4 发送文件");
                            System.out.println("\t\t\t\t 9 退出系统");
                            System.out.print("请输入你的选择:");
                            String choice = Utils.readString(1);
                            switch (choice) {
                                case "1":
                                    userClientService.onlineFriendList();
                                    break;
                                case "2":
                                    System.out.print("请输入想说的话:");
                                    String content1 = Utils.readString(50);
                                    messageClientService.sentMessageToAll(content1,userId);
                                    break;
                                case "3":
                                    System.out.print("请输入想聊天的用户号(在线):");
                                    String getterId = Utils.readString(50);
                                    System.out.print("请输入想说的话:");
                                    String content = Utils.readString(50);
                                    messageClientService.sentMessage(content,userId,getterId);
                                    break;
                                case "4":
                                    System.out.print("请输入你把文件发送给的用户(在线用户)");
                                    getterId = Utils.readString(50);
                                    System.out.println("请输入发送文件的路径(形式 d:\\xx.jpg)");
                                    String src = Utils.readString(50);
                                    System.out.println("请输入把文件发送到对应的路径(形式 d:\\yy.jpg)");
                                    String desc = Utils.readString(50);
                                    fileClientService.sendFileToOne(src,desc,userId,getterId);
                                    break;
                                case "9":
                                    //客户端退出
                                    userClientService.loginout();
                                    loop = false;
                                    break;
                            }
                        }
                }else{
                         System.out.println("登录失败!请尝试重新登录");
                }
                break;
                case "9":
                loop = false;
                break;
                }
            }
        }
    }

(5)管理客户端线程集合的类

ManageClientConnectServerThread.java

package com.wang.cn.qqclient.qqservice;

import com.wang.cn.qqcommon.User;

import java.util.HashMap;

/**
 * @author 王
 * @Type ManageClientConnectServerThread.java
 * @Desc
 * @date 2024/6/6 21:20
 * @version1.0
 */
public class ManageClientConnectServerThread {
    //保存客户端线程的集合
    private static HashMap<String,ClientSocketServerThread> hm = new HashMap<>();

    //把线程添加到集合
    public static void addClientSocketServerThread(String userId,ClientSocketServerThread clientSocketServerThread){
        hm.put(userId,clientSocketServerThread);
    }
    //获取集合中的线程
    public static ClientSocketServerThread getClientSocketServerThread(String userId){
        return hm.get(userId);
    }
}

(7)管理服务端线程集合的类

ManageServerConnectClientThread.java

package com.wang.cn.server;

import java.util.HashMap;
import java.util.Iterator;

/**
 * @author 王
 * @Type ManageServerConnectClientThread.java
 * @Desc
 * @date 2024/6/6 22:39
 * @version1.0
 * 管理和客户端通信的线程集合
 */
public class ManageServerConnectClientThread {
    private static HashMap<String,ServerConnectClientThread> hm = new HashMap<>();
    //添加线程到集合中
    public static void addServerConnectClientThread(String userId,ServerConnectClientThread serverConnectClientThread){
        hm.put(userId,serverConnectClientThread);
    }
    //获取集合中的线程
    public static ServerConnectClientThread getServerConnectClientThread(String userId){
        return hm.get(userId);
    }
    //从服务端集合获取客户端请求的在线用户列表
    public static String getonlineUser(){
        String onlineUser = "";
        Iterator<String> iterator = hm.keySet().iterator();
        //遍历循环取出用户
        while(iterator.hasNext()){
            onlineUser += iterator.next().toString() + " ";
        }
        return onlineUser;
    }
    //删除集合中的线程
    public static void removeServerConnectClientThread(String userId){
        hm.remove(userId);
    }
    //获取集合中所有的线程
    public static HashMap<String,ServerConnectClientThread> getHm(){
        return hm;
    }
}

(8)用户登录校验
(1)客户端
package com.wang.cn.qqclient.qqservice;

import com.wang.cn.qqcommon.Message;
import com.wang.cn.qqcommon.Messager;
import com.wang.cn.qqcommon.User;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * @author 王
 * @Type UserClientService.java
 * @Desc
 * @date 2024/6/6 18:05
 * @version1.0
 */
public class UserClientService {
    private User user = new User();
    private Socket socket;
    //客户端发送用户名密码校验
    public boolean checkUser(String userId,String pwd) {
        boolean b = false;
        user.setUserId(userId);
        user.setPasswd(pwd);
        try {
            //创建socket向9999端口发送消息
            socket = new Socket(InetAddress.getByName("192.168.0.4"), 9999);
            //获得输出流,向服务端发送消息
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            objectOutputStream.writeObject(user);
            //从服务端接受验证消息
            ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
            Message ms = (Message)objectInputStream.readObject();
            //判断返回的信息是否登陆成功
            if(ms.getMessageType().equals(Messager.MESSAGE_LOGIN_SUCCESS)){//显示登陆成功!
                //启动一个和服务端保持通信的线程
                ClientSocketServerThread clientSocketServerThread = new ClientSocketServerThread(socket);
                clientSocketServerThread.start();
                //把用户线程保存到集合中
                ManageClientConnectServerThread.addClientSocketServerThread(userId,clientSocketServerThread);
                b = true;
            }else{//登录失败
                //不启动通信线程,关闭socket
                socket.close();
            }
        } catch (Exception e) {
            e.getMessage();
        }
        return b;
    }
}

(2)服务端
package com.wang.cn.server;

import com.wang.cn.qqcommon.Message;
import com.wang.cn.qqcommon.Messager;
import com.wang.cn.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.HashMap;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author 王
 * @Type Service.java
 * @Desc
 * @date 2024/6/6 22:18
 * @version1.0
 */
public class qqService {
    private ServerSocket ss = null;
    public qqService() {
        try {
            //循环监听9999端口有没有连接
            ss = new ServerSocket(9999);
            //开启推送线程
            new Thread(new SendNewsToAllService()).start();
            while (true) {
                Socket socket = ss.accept();
                //创建消息对象,封装处理结果
                Message message = new Message();
                //获取输入流读取客户端发送的消息
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                User user = (User)ois.readObject();
                //获取输出流传送检验消息
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                //检验用户名和密码
                if (user.getUserId().equals("123") && user.getPasswd().equals(123456)) {//登陆成功
                    //获取输入流返回给客户端校验结果
                    message.setMessageType(Messager.MESSAGE_LOGIN_SUCCESS);
                    oos.writeObject(message);
                    //创建一个线程和客户端保持通信
                    ServerConnectClientThread serverConnectClientThread = new ServerConnectClientThread(socket, user.getUserId());
                    serverConnectClientThread.start();
                    //把该线程放到集合中管理
                    ManageServerConnectClientThread.addServerConnectClientThread(user.getUserId(),serverConnectClientThread);
                }else{//登录失败
                    System.out.println("用户 id=" + user.getUserId() + " pwd=" + user.getPasswd() + " 验证失败");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();

        }finally {
            try {
                //如果服务端退出while循环,说明服务端不在监听,因此关闭serverSocket
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

二、拉取在线用户列表

(1)客户端向服务端请求在线用户列表

  //向服务端请求在线用户列表
    public void onlineFriendList(){
        //发送一个Message类型MESSAGE_GET_ONLINE_FRIEND
        Message message = new Message();
        message.setMessageType(Messager.MESSAGE_GET_ONLINE_FRIEND);
        //设置发送者
        message.setSender(user.getUserId());
        try {
            //从集合中获取当前线程的socket发送message信息
            ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServerThread
                    .getClientSocketServerThread(user.getUserId())
                    .getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();

        }
    }

(2)客户端处理返回的消息

package com.wang.cn.qqclient.qqservice;

import com.wang.cn.qqcommon.Message;
import com.wang.cn.qqcommon.Messager;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
 * @author 王
 * @Type ClientSocketServerThread.java
 * @Desc
 * @date 2024/6/6 20:49
 * @version1.0
 */
public class ClientSocketServerThread extends Thread{
    private Socket socket;

    public ClientSocketServerThread(Socket socket) {
        this.socket = socket;
    }

    public Socket getSocket() {
        return socket;
    }

    @Override
    public void run() {
        while (true) {
            try {
                System.out.println("客户端等待服务端发送的消息...");
                ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
                //如果服务端没有返回消息,就会在这里阻塞等待
                Message ms = (Message)objectInputStream.readObject();
                //获得从服务端返回的在线用户列表
                if(ms.getMessageType().equals(Messager.MESSAGE_RET_ONLINE_FRIEND)){//如果获取的是在线用户列表
                    String[] onlineUsers = ms.getContent().split(" ");
                    System.out.println("========当前在线用户列表===========");
                    for(int i = 0;i < onlineUsers.length; i++){
                        System.out.println("用户:" + onlineUsers[i]);
                    }
                }
                else{
                    System.out.println("不匹配的类型,暂时不做处理...");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

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

(3)服务端处理

(1)设置一个保存用户账号的集合

//设置一个保存用户账号的集合
    private static ConcurrentHashMap<String,User> verification = new ConcurrentHashMap<>();//支持并发

(2)定义静态代码块在类加载时就创建这个集合

 static{
        verification.put("100",new User("100","123456"));
        verification.put("200",new User("200","123456"));
        verification.put("300",new User("300","123456"));
        verification.put("至尊宝",new User("至尊宝","123456"));
        verification.put("张三",new User("张三","123456"));
        verification.put("李四",new User("李四","123456"));
    }

(3)编写校验用户方法

public static boolean checkUser(String userId,String passwd){
        User user = verification.get(userId);
        if(user == null){
            return false;
        }
        if(!user.getUserId().equals(userId)){
            return false;
        }
        return true;
    }

(4)替换校验处理逻辑

if (checkUser(user.getUserId(),user.getPasswd())) {//登陆成功

(5)服务端线程类

package com.wang.cn.server;

import com.wang.cn.qqcommon.Message;
import com.wang.cn.qqcommon.Messager;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Iterator;

/**
 * @author 王
 * @Type ServerConnectClientThread.java
 * @Desc
 * @date 2024/6/6 22:31
 * @version1.0
 */
public class ServerConnectClientThread extends Thread{
    private Socket socket;
    private String userId;//和服务端通信的是哪个线程

    public Socket getSocket() {
        return socket;
    }

    public ServerConnectClientThread(Socket socket, String userId) {
        this.socket = socket;
        this.userId = userId;
    }

    @Override
    public void run() {
        //循环读取服务端发送的消息
        try {
            while (true){
                System.out.println("服务端和客户端" + userId + " 保持通信,读取数据...");
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                Message ms = (Message)ois.readObject();
                //返回客户端请求的用户列表
                if(ms.getMessageType().equals(Messager.MESSAGE_GET_ONLINE_FRIEND)){//如果消息类型是用户列表
                    System.out.println(ms.getSender() + "要求在线用户列表");
                    String onlineUser = ManageServerConnectClientThread.getonlineUser();
                    //返回message
                    Message message = new Message();
                    //构建一个Message,返回给客户端
                    message.setMessageType(Messager.MESSAGE_RET_ONLINE_FRIEND);
                    message.setContent(onlineUser);//设置消息内容为在线列表
                    message.setGetter(ms.getSender());//设置接受者
                    //返回给客户端
                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                    oos.writeObject(message);
                }
               else{
                    System.out.println("其他类型的message 暂时不做处理...");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();

        }
    }
}

三、无异常退出(客户端、服务端)

当在客户端选择9选项时,退出的是主方法,并没有退出线程,要想退出线程,加入退出的逻辑

(1)客户端编写程序,退出客户端

 //编写程序,退出客户端
    public void loginout(){
        //向客户端发送退出系统的消息
        Message message = new Message();
        //设置退出的是那个线程
        message.setSender(user.getUserId());
        //设置退出标识
        message.setMessageType(Messager.MESSAGE_CLIENT_EXIT);
        //从集合中获取该线程发送给服务端
        try {
            ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServerThread
                    .getClientSocketServerThread(user.getUserId())
                    .getSocket().getOutputStream());
            oos.writeObject(message);
            //调用方法退出客户端
            System.exit(0);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

(2)服务端读取退出消息并做处理

else if(ms.getMessageType().equals(Messager.MESSAGE_CLIENT_EXIT)) {//如果消息是退出消息
                    System.out.println(ms.getSender() + " 退出");
                    //从集合中删除该线程
                    ManageServerConnectClientThread.removeServerConnectClientThread(userId);
                    //关闭socket
                    socket.close();
                    //退出线程
                    break;

四、私聊

(1)客户端接受发送私聊的消息内容

System.out.print("请输入想聊天的用户号(在线):");
String getterId = Utils.readString(50);
System.out.print("请输入想说的话:");
String content = Utils.readString(50);
messageClientService.sentMessage(content,userId,getterId);
break;

(2)客户端封装私聊的消息内容发送给服务端

package com.wang.cn.qqclient.qqservice;

import com.wang.cn.qqcommon.Message;
import com.wang.cn.qqcommon.Messager;

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

/**
 * @author 王
 * @Type MessageClientService.java
 * @Desc
 * @date 2024/6/7 15:48
 * @version1.0
 */
public class MessageClientService {
    //定义发送消息的方法

    /**
     *
     * @param content 消息内容
     * @param senderId 发送者
     * @param getterId 接受者
     */
    public void sentMessage(String content,String senderId,String getterId){
        Message message = new Message();
        message.setMessageType(Messager.MESSAGE_COMM_MES);//定义发送消息的类型
        message.setSender(senderId);//定义发送者id
        message.setGetter(getterId);//定义接受者id
        message.setContent(content);//定义发送的消息
        message.setSendTime(new Date().toString());//定义发送消息时间
        System.out.println(message.getSender() + " 对 " + message.getGetter() + " 说 " + message.getContent());
        try {
            ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServerThread.getClientSocketServerThread(senderId).getSocket().getOutputStream());
            //发送
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();

        }

    }
}

(3)客户端读取服务端返回的消息内容并处理

else if(ms.getMessageType().equals(Messager.MESSAGE_COMM_MES)) {
 //显示发送的消息
System.out.println("\n" + ms.getSender() + " 对 " + ms.getGetter() + " 说 " + ms.getContent());
                }

(4)服务端读取客服端发送的消息并处理

else if(ms.getMessageType().equals(Messager.MESSAGE_COMM_MES)){//如果消息类型是私聊消息
//从集合中获取对应线程
ServerConnectClientThread serverConnectClientThread = ManageServerConnectClientThread.getServerConnectClientThread(ms.getGetter());
ObjectOutputStream oos = new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());
oos.writeObject(ms);
                }

五、群聊

(1)客户端接受发送群聊的消息内容

System.out.print("请输入想说的话:");
String content1 = Utils.readString(50);                                   
messageClientService.sentMessageToAll(content1,userId);
break;

(2)客户端封装群聊的消息内容发送给服务端

//定义群发消息的方法
    public void sentMessageToAll(String content,String senderId){
        Message message = new Message();
        message.setMessageType(Messager.MESSAGE_TO_ALL_MES);//定义发送消息的类型
        message.setSender(senderId);//定义发送者id
        message.setContent(content);//定义发送的消息
        message.setSendTime(new Date().toString());//定义发送消息时间
        System.out.println(message.getSender() + " 对大家说:" + message.getContent());
        try {
            ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServerThread
                    .getClientSocketServerThread(senderId)
                    .getSocket()
                    .getOutputStream());
            //发送
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();

        }

    }

(3)客户端读取服务端返回的消息内容并处理

else if(ms.getMessageType().equals(Messager.MESSAGE_TO_ALL_MES)){
//显示群发消息
System.out.println("\n" + ms.getSender() + " 对大家说:" + ms.getContent());

(4)服务端读取客服端发送的消息并处理

else if(ms.getMessageType().equals(Messager.MESSAGE_TO_ALL_MES)){//如果消息类型是群发消息
//获取服务端所有线程socket
HashMap<String, ServerConnectClientThread> hm =
ManageServerConnectClientThread.getHm();
//遍历集合
Iterator<String> iterator = hm.keySet().iterator();
while(iterator.hasNext()){
//在线用户
String onlineUser = iterator.next().toString();
//排除发消息的自身
if(!onlineUser.equals(hm.get(ms.getSender()))){
//开始转发
ObjectOutputStream oos = new ObjectOutputStream(hm.get(onlineUser)
                                    .getSocket().getOutputStream());
                            oos.writeObject(ms);
                        }
                    }
                }

六、发文件

(1)把文件读取到客户端封装到字节数组,并发送到服务端

package com.wang.cn.qqclient.qqservice;

import com.wang.cn.qqcommon.Message;
import com.wang.cn.qqcommon.Messager;

import java.io.*;

/**
 * @author 王
 * @Type FileClientService.java
 * @Desc
 * @date 2024/6/7 19:27
 * @version1.0
 */
public class FileClientService {
    //发送单个文件的处理方法
    public void sendFileToOne(String src,String desc,String senderId,String getterId){
        //创建要发送的消息对象
        Message message = new Message();
        //设置消息类型
        message.setMessageType(Messager.MESSAGE_FILE_MES);
        //设置发送者
        message.setSender(senderId);
        //设置接受者
        message.setGetter(getterId);
        //设置源文件路径
        message.setSrc(src);
        //设置目标文件的路径
        message.setDesc(desc);

        //读取文件封装到字节数组里面
        byte[] bytes = new byte[(int)new File(src).length()];

        //创建字节输入流读取文件
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(src);
            fileInputStream.read(bytes);
            //把字节数组封装到消息对象里
            message.setFileBytes(bytes);
            //向服务端发送此消息
            ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServerThread
                    .getClientSocketServerThread(senderId)
                    .getSocket().getOutputStream());
            //发送
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();

        }finally {
            try {
                if(fileInputStream != null){
                    fileInputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();

            }
        }
        System.out.println("\n" + senderId + " 对" + getterId + " 发送文件: "
                + src + " 到对方的电脑 " + desc);
    }
}

(2)收到服务端返回的消息,并处理保存到指定路径

else if(ms.getMessageType().equals(Messager.MESSAGE_FILE_MES)){
System.out.println("\n" + ms.getSender() + " 对" + ms.getGetter() + " 发送文件: " + ms.getSrc() + " 到对方的电脑 " + ms.getDesc());
//将文件保存到磁盘
FileOutputStream fileOutputStream = new FileOutputStream(ms.getDesc());
fileOutputStream.write(ms.getFileBytes());
fileOutputStream.close();
System.out.println("保存成功");
}

(3)服务端接受发送的文件,拆解消息对象,获取指定用户id,再通过id获得指定线程,再把消息转发给指定对象

else if(ms.getMessageType().equals(Messager.MESSAGE_FILE_MES)){
//接收到message对象,拆解对象的getterId,获取该用户的线程
ServerConnectClientThread serverConnectClientThread = ManageServerConnectClientThread                            .getServerConnectClientThread(ms.getGetter());
//转发此消息到指定的客户端
ObjectOutputStream oos = new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());
oos.writeObject(ms);

七、服务器推送新闻

(1)另起一个线程,当服务端读取消息时启动该线程,循环获取集合中的所有socket,向服务端发送消息

package com.wang.cn.server;

import com.wang.cn.qqcommon.Message;
import com.wang.cn.qqcommon.Messager;
import com.wang.cn.qqutils.Utils;

import javax.rmi.CORBA.Util;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Scanner;

/**
 * @author 王
 * @Type SendNewsToAllService.java
 * @Desc
 * @date 2024/6/7 20:32
 * @version1.0
 * 服务端向所有客户端发送推送消息
 */
public class SendNewsToAllService implements Runnable{
    private Scanner sc = new Scanner(System.in);
    @Override
    public void run() {
        while (true){//循环不停地推送
            System.out.println("请输入服务端向客户端推送的新闻/消息[输入exit表示退出推送服务]:");
            String news = Utils.readString(50);
            if("exit".equals(news)){
                break;
            }
            Message message = new Message();
            //设置推送消息类型为群发
            message.setMessageType(Messager.MESSAGE_TO_ALL_MES);
            //设置推送者
            message.setSender("服务器");
            //设置推送内容
            message.setContent(news);
            System.out.println("服务端 对所有人说:" + news);

            HashMap<String, ServerConnectClientThread> hm_ = ManageServerConnectClientThread.getHm();
            Iterator<String> iterator = hm_.keySet().iterator();
            while (iterator.hasNext()){
                //在线用户
                String onlineUserId = iterator.next().toString();
                try {
                    OutputStream oos = ManageServerConnectClientThread
                            .getServerConnectClientThread(onlineUserId)
                            .getSocket().getOutputStream();
                    ObjectOutputStream oos_ = new ObjectOutputStream(oos);
                    //发送
                    oos_.writeObject(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

(2)在构造器里启动

public qqService() {
try {
//循环监听9999端口有没有连接
ss = new ServerSocket(9999);
//开启推送线程
new Thread(new SendNewsToAllService()).start();

(3)客户端处理相当于处理群发消息


源码已上传至csdn里主页获取
码文不易,最后求个关注点赞收藏,谢谢!

  • 19
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

五度鱼学Java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值