Java小项目——聊天室(多线程版本)

目录

1. 前言

2. 功能实现

3. 模块划分

4. 功能分析

4.1 前期分析

4.2 具体实现

5. 使用技术

6. 代码


1. 前言

之前写过单线程版本的聊天室,这次对之前的版本进行扩展与优化,将其升级为一个多线程版本的聊天室,更好的贴近一个真实的聊天工具。

在这个版本中,聊天室具有注册、私聊、群聊以及退出的功能。

扫描二维码关注公众号,回复: 5718974 查看本文章

2. 功能实现

  • 服务器需要实现
    • 用户注册
    • 用户私聊
    • 用户群聊
    • 用户退出
  • 客户端需要实现
    • 发送信息
    • 接收信息

3. 模块划分

  • 服务器主线程:创建服务器、创建线程池
    • 任务线程:进行业务处理
  • 客户端主线程:创建客户端、分发任务(输入、输出)
    • 两个任务线程:数据写入服务器、从服务器读取线程

4. 功能分析

该版本需要实现一个服务器同时处理多个客户端的请求   ->   利用多线程进行处理

  • 4.1 前期分析

    • 客户端和服务器的创建与连接类似于单线程版本

    • 为了避免硬编码,利用参数确定端口号和地址(也可利用外部文件、交互式输入)

    • 利用线程池约束线程创建的数量,防止负载过高。
      • 线程池的选择
        • 与用户交互相关,任务周期无法确定,不能用  CachesThreadPool
        • 该版本需要多线程,不能用  SingleThreadPool
        • 选择固定大小线程池  FixedThreadPool
      • 线程池大小
        • 通过 Runtime.getRuntime().availableProcessors() 方法获得当前设备的CPU个数(Ncpu)
        • I/O密集型任务线程并应配置尽可能多的线程,如2*Ncpu个线程的线程池
      • 让线程池专门去处理业务,客户端关闭,业务终止。
    • 利用循环实现多客户端的支持
    • 当一个客户端发出请求时,服务器启动一个线程,去处理数据的传输。
      • 数据的传输由一个读数据线程和一个写数据线程完成
  • 4.2 具体实现

    • 服务端实现:

      1.循环监听客户端连接

      2.维护所有在线的客户端,记录在线人数

      3.注册功能:将客户端名称添加到服务器客户端集合中

      4.群聊功能:接收客户端发送的消息,在发送给所有客户端(除过自己)

      5.私聊功能:客户端与指定客户端间的数据传送

      6.退出功能:从服务器客户端集合中移除客户端

    • 客户端实现:

      1.命令行的交互式输入输出

      2.注册功能:创建Socket,给服务器发送注册消息

      3.群聊功能:客户端发送和接收数据

      4.私聊功能:客户端指定客户端,发送和接收数据

      5.退出功能:向服务器发送退出指令

5. 使用技术

  1. Socket编程
  2. I/O
  3. 多线程

6. 代码

package com.qqy.chat.client.mul;

import java.io.IOException;
import java.net.Socket;

/**
 * 客户端
 * Author:qqy
 */
public class MulClient {
    public static void main(String[] args) {
        try {
            String host="127.0.0.1";
            int port=65521;
            //先读取地址,再读取端口号
            if(args.length==2){
                host=args[0];
                try{
                    port=Integer.parseInt(args[1]);
                }catch (NumberFormatException e){
                    System.out.println("指定端口号格式错误,采用默认端口号"+port);
                    port=65521;
                }
            }

            Socket clientSocket=new Socket(host,port);
            System.out.println("端口号为"+clientSocket.getLocalPort()+"的客户端已创建");
            System.out.println("已连接上端口号为"+clientSocket.getPort()+"的服务器...");

            System.out.println("\n请按照提示信息进行操作:");
            System.out.println("\t请求按行读取");
            System.out.println("\t注册: register:<userName> 例如: register:lila");
            System.out.println("\t群聊: groupChat:<message> 例如: groupChat:大家好");
            System.out.println("\t私聊: privateChat:<userName>:<message> 例如: privateChat:nina:你好呀");
            System.out.println("\t退出:  bye\n");

            new WriteDatatoServer(clientSocket).start();
            new ReadDataFromServer(clientSocket).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package com.qqy.chat.client.mul;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Scanner;

/**
 * 客户端给服务端发送数据的线程
 * Author:qqy
 */
public class WriteDatatoServer extends Thread {
    private Socket clientSocket;

    public WriteDatatoServer(Socket clientSocket) {
        this.clientSocket = clientSocket;
    }

    @Override
    public void run() {
        try {
            OutputStream out = clientSocket.getOutputStream();
            OutputStreamWriter writer = new OutputStreamWriter(out);
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入需求——————  ");
            while (true) {
                String data=scanner.nextLine();
                writer.write(data+"\n");
                writer.flush();
                if(data.equals("bye")){
                    break;
                }
            }
            clientSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package com.qqy.chat.client.mul;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * 客户端从服务端读取数据的线程
 * Author:qqy
 */
public class ReadDataFromServer extends Thread {
    private Socket clientSocket;

    public ReadDataFromServer(Socket clientSocket) {
        this.clientSocket = clientSocket;
    }

    @Override
    public void run() {
        try {
            InputStream in=clientSocket.getInputStream();
            Scanner scanner=new Scanner(in);
            while (true){
                System.out.println("来自服务器的消息:\n\t"+scanner.nextLine());
                System.out.println("\n请输入需求——————  ");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package com.qqy.chat.server.mul;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 服务器
 * Author:qqy
 */
public class MulServer {
    public static void main(String[] args) {
        try {
            int port=65521;
            if(args.length==1){
                try{
                port=Integer.parseInt(args[0]);
                }catch (NumberFormatException e){
                    System.out.println("指定端口号格式错误,采用默认端口号"+port);
                    port=65521;
                }
            }
            ServerSocket serverSocket = new ServerSocket(port);
            System.out.println("端口号为" + serverSocket.getLocalPort() + "服务器已创建,等待客户端的连接...");

            //创建线程池
            ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);

            //利用循环实现多线程,以支持多用户
            while (true) {
                //客户端连接
                Socket clientSocket = serverSocket.accept();
                System.out.println("接收到客户端"+clientSocket.getRemoteSocketAddress()+"的连接");
                /*
                   不在循环中直接进行业务处理
                   ∵accept()是阻塞方法,若在循环中直接处理业务,每次循环不能很快结束,阻塞了其他客户端的连接
                 */

                //线程池分配线程
                //每次有客户端连接到服务器的时候,就创建一个HandleClient的实例化对象来处理具体的业务
                executorService.execute(new HandleClient(clientSocket));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package com.qqy.chat.server.mul;

import java.io.*;
import java.net.Socket;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Author:qqy
 */
public class HandleClient implements Runnable {
    private static final Map<String, Socket> ONLINE_CLIENT = new ConcurrentHashMap<>();

    private final Socket client;

    public HandleClient(Socket client) {
        this.client = client;
    }

    @Override
    public void run() {
        try {
            //获取客户端的输入
            InputStream in = client.getInputStream();
            //将字符流转换为字节流
            Scanner scanner = new Scanner(in);

            while (true) {
                String data = scanner.nextLine();
                if (data.startsWith("register")) {
                    if (data.split(":").length == 2) {
                        String userName = data.split(":")[1];
                        //若当前用户存在
                        if (ONLINE_CLIENT.containsKey(userName)) {
                            sendMsg(this.client, "该用户名已存在,请重新注册!!!", false);
                        } else{
                            register(userName);
                        }
                    } else {
                        sendMsg(this.client, "请输入需要注册的用户名!!!", false);
                    }
                    continue;
                }
                if (data.startsWith("groupChat")) {
                    String msg = data.split(":")[1];
                    groupChat(msg);
                    continue;
                }
                if (data.startsWith("privateChat")) {
                    String[] require = data.split(":");
                    String object = require[1];
                    String msg = require[2];
                    privateChat(object, msg);
                    continue;
                }
                if (data.equals("bye")) {
                    bye();
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 客户端退出
     */
    private void bye() {
        for (Map.Entry<String, Socket> entry : ONLINE_CLIENT.entrySet()) {
            Socket target = entry.getValue();
            if (target == this.client) {
                ONLINE_CLIENT.remove(entry.getKey());
                break;
            }
        }
        printOnline();
    }

    /**
     * 打印当前在线人数
     */
    private void printOnline() {
        System.out.println("当前在线人数:" + ONLINE_CLIENT.size());
        System.out.println("在线用户为:");
        for (String userName : ONLINE_CLIENT.keySet()) {
            System.out.println(userName);
        }
    }

    /**
     * 私聊
     *
     * @param object 私聊对象
     * @param msg    发送信息
     */
    private void privateChat(String object, String msg) {
        Socket target = ONLINE_CLIENT.get(object);
        if (target == null) {
            sendMsg(this.client, "用户" + object + "不存在!", false);
        } else {
            sendMsg(target, msg, true);
        }
    }

    /**
     * 群聊
     *
     * @param msg 发送信息
     */
    private void groupChat(String msg) {
        for (Map.Entry<String, Socket> entry : ONLINE_CLIENT.entrySet()) {
            Socket target = entry.getValue();
            if (target != this.client) {
                sendMsg(target, msg+"(来自群聊)", true);
            }
        }
    }

    /**
     * 用户注册
     *
     * @param userName 用户名
     */
    private void register(String userName) {
        //TODO 用户名相同如何处理
        ONLINE_CLIENT.put(userName, this.client);
        printOnline();
        sendMsg(this.client, "恭喜" + userName + ",注册成功", false);
    }

    /**
     * 获取用户名
     *
     * @return
     */
    private String getUserName() {
        for (Map.Entry<String, Socket> entry : ONLINE_CLIENT.entrySet()) {
            Socket target = entry.getValue();
            if (target == this.client) {
                return entry.getKey();
            }
        }
        return "";
    }

    private void sendMsg(Socket target, String msg, boolean flag) {
        try {
            OutputStream out = target.getOutputStream();
            OutputStreamWriter writer = new OutputStreamWriter(out);
            if (flag) {
                writer.write("<" + getUserName() + "说>" + "\t" + msg + "\n");
            } else {
                writer.write(msg + "\n");
            }
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_42142477/article/details/88591377