多线程TCP网络即时通讯项目

多线程TCP网络即时通讯项目

1、需求:

image-20220302200315692

2、界面设计:

image-20220302200403582

image-20220302200532291

image-20220302200545030

3、功能说明:

①.用户登录:

image-20220302200706172

②.拉取在线用户列表:

image-20220302200834770

③.无异常退出:

  • 客户端结束线程,并向服务端发送message,关闭其在服务端所对应的线程,并释放资源

④.私聊:

image-20220302200923277

⑤.群聊:

⑥.服务端推送消息:

  • 服务端另外开启一个线程

⑦.离线留言:

  • 维护离线消息队列

⑧.离线发送文件:

4、客户端项目结构:

image-20220302201951815

5、客户端项目代码:

①.ClientConnectThread

package qqclient.service;

import qqcommon.Message;
import qqcommon.MessageType;

import java.io.*;
import java.net.Socket;

//客户端线程类
public class ClientConnectThread extends Thread {
   
   
    //    由于在业务逻辑上需要用到该线程对应的socket和user,因此定义为成员属性
//    持有与服务端通信的socket对象
    private Socket socket;

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

    //    获取当前线程对应的socket
    public Socket getSocket() {
   
   
        return socket;
    }


    //客户端的业务逻辑
    @Override
    public void run() {
   
   
        while (true) {
   
   
            try {
   
   
//                监听当前线程对应socket从服务端发来的信息,若无信息,则阻塞
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());//当客户端服务端退出时,线程卡阻塞在此处

                Message message = (Message) ois.readObject();
//                根据消息种类跳转
                switch (message.getMesType()) {
   
   
                    case MessageType.MESSAGE_RET_ONLINE_FRIEND://获取服务端返回的在线用户列表
                        //                    在线用户由服务端线程集合中获取,理论上一定不为空,至少为1个用户
                        String[] s = message.getContent().split(" ");
                        if (s.length > 0) {
   
   
                            System.out.println("\n===当前在线用户列表====");
                            for (int i = 0; i < s.length; i++) {
   
   
                                System.out.println("用户" + i + ": " + s[i]);
                            }
                        }
                        break;
                    case MessageType.MESSAGE_TO_ONE_MES://从服务端接收到私聊信息
                        System.out.println("\n用户" + message.getSender() + "对你说:" + message.getContent());
                        break;
                    case MessageType.MESSAGE_TO_ALL_MES://从服务端接收到群聊消息
                        System.out.println("\n用户" + message.getSender() + "群发了:" + message.getContent());
                        break;
                    case MessageType.MESSAGE_FILE_TO_ONE_MES://从服务端接收到文件消息
                        System.out.println("\n用户" + message.getSender() + "向你发送了文件:" + message.getFileName() + ",文件大小为:" + message.getFileSize()+"KB");
                        FileOutputStream fos = new FileOutputStream(new File(message.getFileDes(),message.getFileName()));
                        fos.write(message.getFileContent());//将文件字节数组写入到输出流中
                        System.out.println("文件传输完毕");
                        fos.close();//关闭文件输出流
                        break;
                    case MessageType.MESSAGE_SEVER_MES://服务端推送的消息
                        System.out.println("\n服务端向你推送了消息:" + message.getContent());
                        break;
                }
            } catch (Exception e) {
   
   
                e.printStackTrace();
            }
        }
    }
}

②.ManageClientConnectThread

package qqclient.service;

import java.util.concurrent.ConcurrentHashMap;


//客户端线程管理类
public class ManageClientConnectThread {
   
   
    //    用ConcurrentHashMap<String, ClientConnectThread>对客户端线程进行管理
    private static ConcurrentHashMap<String, ClientConnectThread> hsm = new ConcurrentHashMap<>(16);//初始容量为16

    /**
     * 将客户端对应的线程添加到集合中
     * @param userid 用户id
     * @param clientThread 客户端与服务端通讯的线程
     */
    public static void addClientConnectThread(String userid, ClientConnectThread clientThread){
   
   
        hsm.put(userid, clientThread);
    }

    /**
     * 获取用户对应的线程
     * @param userid 用户id
     * @return 用户正在执行的线程
     */
    public static ClientConnectThread getClientConnectThread(String userid){
   
   
        return hsm.get(userid);
    }


    /**
     * 从线程集合中移除用户对应线程
     * @param userid
     */
    public static void removeClientConnectThread(String userid){
   
   
        hsm.remove(userid);
    }
}

③.UserClientService

package qqclient.service;

import qqcommon.Message;
import qqcommon.MessageType;
import qqcommon.User;
import utils.Utility;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

//用户在客户端涉及到的服务
public class UserClientService {
   
   

    //    当前服务持有socket和user属性,便于在其他服务中使用
    private Socket socket;
    private User user;

    //构造器
    public UserClientService() {
   
   

    }

    /**
     * 客户端退出,给服务端发送信息,释放资源
     */
    public void logout() {
   
   
        try {
   
   
            Message message = new Message();//创建信息对象
            message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);//设置信息类型为客户端退出
            message.setSender(user.getId());//设置发送者
//            根据当前服务对应socket向服务端传输数据包
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(message);
//            从客户端线程集合移除当前用户对应的线程
            ManageClientConnectThread.removeClientConnectThread(user.getId());
            System.out.println(user.getId() + "在客户端退出");
            System.exit(0);//进程结束指令,将会导致当前客户端运行的线程结束
        } catch (IOException e) {
   
   
            e.printStackTrace();
        }
    }

    /**
     * 获取在线用户列表,客户端向服务端发送数据包
     */
    public void getOnlineUsers() {
   
   
        try {
   
   
            Message message = new Message();//创建信息对象
            message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);//设置信息类型为客户端获取在线用户列表
            message.setSender(user.getId());//设置发送者
//            根据当前服务对应socket向服务端传输数据包
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
   
   
            e.printStackTrace();
        }
    }


    /**
     * 当前用户向服务端发送群聊消息
     */
    public void sendAllMessage() {
   
   
        System.out.println("请输入你要群发的消息:");
        String allMes = Utility.readString(100);
        try {
   
   
            Message message = new Message();//创建信息对象
            message.setMesType(MessageType.MESSAGE_TO_ALL_MES);//设置信息类型为客户端获取在线用户列表
            message.setSender(user.getId());//设置发送者
            message.setContent(allMes);//设置群聊消息的内容
//            根据当前服务对应socket向服务端传输数据包
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
   
   
            e.printStackTrace();
        }
    }


    /**
     * 当前用户向服务端发送私聊消息
     */
    public void sendOneMessage() {
   
   
        System.out.print("请输入你要私聊的对象:");
        String receiver = Utility.readString(50);
        System.out.print("请输入私聊的内容:");
        String content = Utility.readString(100);
        try {
   
   
            Message message = new Message();//创建信息对象
            message.setMesType(MessageType.MESSAGE_TO_ONE_MES);//设置信息类型为客户端获取在线用户列表
            message.setSender(user
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值