Java实现多线程聊天室


Posted in Java/Android onJune 26, 2021

本文实例为大家分享了Java实现多线程聊天室的具体代码,供大家参考,具体内容如下

用多线程来实现,功能会比单线程聊天室更加齐全,也更人性化一点。

多线程版本的聊天室

1. 功能分析:

  • 实现用户注册,上线,下线
  • 实现群聊和私聊
  • 统计当前在线人数

2. 服务端实现

1.维护所有的在线用户

2.注册功能:客户端名称,添加到服务器的客户端集合里

3.群聊功能:客户端发送消息,所有的客户端都能接收到

4.私聊功能:客户端与指定客户端进发送和接收消息

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

3. 客户端实现

1.注册功能:创建Socket对象,给服务器发送注册执行(消息)

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

3.私聊功能:客户端指定客户端(用户),发送和接收数据

4.退出功能:给服务器发送退出指令(消息)

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

4.实现思路: 

首先,要实现服务端与客户端之间的连接

这里是使用套接字建立TCP连接:

(1)服务器端先实例化一个描述服务器端口号的ServerSocket对象

(2)客户端要创建Socket对象来连接指定的服务器端

(3)服务器端调用ServerSocket类的accept()方法来监听连接到服务器端的客户端信息

(4)若服务器端与客户端连接成功,双方将返回一个Socket对象,此时双方可以进行通信

(5)服务器端与客户端使用I/O流进行连接,服务端的输出流连接客户端的输入流,客户端的输出流连接服务端的输入流

(6)使用close()方法关闭套接字(一定要记得关闭)

2.因为是拥有一个服务端来实现多个客户端的连接,此处还要解决的是多线程的问题。

每个客户端需要两个线程,来分别处理向服务端发送消息和向服务端接收消息

而服务端,当每增加一个客户端与服务端连接,服务端都要多创建一个线程来处理与客户端的连接

5. 图解析 

Java实现多线程聊天室

6. 服务端代码实现

Server类

package test.Server;
 
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
/**
 * package:test.Server
 * Description:服务器端
 * @date:2019/8/14
 * @Author:weiwei
 **/
public class server {
    public static void main(String[] args) {
        try {
            int port = 6666;
 
            ServerSocket serverSocket = new ServerSocket(port);
 
            System.out.println("服务器启动..." + serverSocket.getLocalSocketAddress());  //服务器启动,打印本地地址
 
            //线程池
            ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
 
            while (true) {  //死循环
                Socket client = serverSocket.accept();
                System.out.println("有客户端连接到服务器:" + client.getRemoteSocketAddress());
                executorService.execute(new HandlerClient(client));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

HandlerClient类

package test.Server;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.ConcurrentHashMap;
 
 
/**
 * Author:weiwei
 * description:HandlerClient
 * Creat:2019/3/12
 **/
public class HandlerClient implements Runnable {
 
    /**
     * 维护所有的连接到服务端的客户端对象
     */
    private static final Map<String,Socket> ONLINE_CLIENT_MAP =
            new ConcurrentHashMap<String, Socket>();  //静态是为了不让对象变化,final不让对象被修改,ConcurrentHashMap是线程安全的类
                                        //static final修饰后变量名应该用常量--大写字母加下划线分隔
    private final Socket client;
    public HandlerClient(Socket client) {  //HandlerClient在多线程环境下调用,所以会产生资源竞争,用一个并发的HashMap
        this.client = client;          //为了防止变量被修改,用final修饰
    }
 
    //@Override
    public void run() {
        try {
            InputStream clientInput=client.getInputStream(); //获取客户端的数据流
            Scanner scanner = new Scanner(clientInput); //字节流转字符流
 
            /**
             *消息是按行读取
             * 1.register:<username> 例如: register:张三
             * 2.群聊: groupChat:<message> 例如:groupChat:大家好
             * 3.私聊: privateChat:张三:你好,还钱
             * 4.退出:bye
             */
 
            while(true){
                String data = scanner.nextLine();  //读数据,按行读
                if(data.startsWith("register:")){
                    //注册
                    String userName = data.split(":")[1];//冒号分隔,取第一个
                    register(userName);
                    continue;
                }
 
                if(data.startsWith("groupChat:")){
                    String message = data.split(":")[1];
                    groupChat(message);
                    continue;
                }
 
                if(data.startsWith("privateChat:")){
                    String [] segments = data.split(":");
                    String targetUserName = segments[1].split("\\-")[0]; //取目标用户名
                    String message = segments[1].split("\\-")[1];   //因为要取两次,所以用数组 //取发送的消息内容
                    privateChat(targetUserName,message);
                    continue;
                }
 
                if(data.equals("bye")){
                    //表示退出
                    bye();
                    continue;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 当前客户端退出
     */
    private void bye() {
        for(Map.Entry<String,Socket> entry : ONLINE_CLIENT_MAP.entrySet()){
            Socket target = entry.getValue();
            if(target.equals(this.client)){   //在在线用户中找到自己并且移除
                ONLINE_CLIENT_MAP.remove(entry.getKey());
                break;
            }
            System.out.println(getCurrentUserName()+"退出聊天室");
        }
        printOnlineClient();//打印当前用户
    }
 
    private String getCurrentUserName(){
        for (Map.Entry<String, Socket> entry : ONLINE_CLIENT_MAP.entrySet()) {
            Socket target = entry.getValue(); //getvalue得到Socket对象
            if(target.equals(this.client)){ //排除群聊的时候自己给自己发消息的情况
                return entry.getKey();
            }
        }
        return "";
    }
 
    /**
     * 私聊,给targetUserName发送message消息
     * @param targetUserName
     * @param message
     */
    private void privateChat(String targetUserName, String message) {
        Socket target = ONLINE_CLIENT_MAP.get(targetUserName);//获取目标用户名
        if(target == null){
            this.sendMessage(this.client,"没有这个人"+targetUserName,false);
        }else{
            this.sendMessage(target,message,true);
        }
    }
 
    /**
     * 群聊,发送message
     * @param message
     */
    private void groupChat(String message) {
        for (Map.Entry<String, Socket> entery : ONLINE_CLIENT_MAP.entrySet()) {
            Socket target = entery.getValue(); //getvalue得到Socket对象
            if(target.equals(this.client)){
                continue;            //排除群聊的时候自己给自己发消息的情况
            }
            this.sendMessage(target,message,true);
        }
    }
 
    /**
     * 以userName为key注册当前用户(Socket client)
     * @param userName
     */
    private void register(String userName) {
        if(ONLINE_CLIENT_MAP.containsKey(userName)){
            this.sendMessage(this.client,"您已经注册过了,无需重复注册",false);
        }else{
            ONLINE_CLIENT_MAP.put(userName,this.client);
            printOnlineClient();
            this.sendMessage(this.client,"恭喜"+userName+"注册成功\n",false);
        }
    }
 
    private void sendMessage(Socket target,String message,boolean prefix){
        OutputStream clientOutput = null;      //value是每一个客户端
        try {
            clientOutput = target.getOutputStream();
            OutputStreamWriter writer = new OutputStreamWriter(clientOutput);
            if(prefix) {
                String currentUserName = this.getCurrentUserName();
                writer.write("<" + currentUserName + "说:>" + message + "\n");
            }else{
                writer.write( message + "\n");
            }
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 打印在线客户端
     */
    private void printOnlineClient(){
        System.out.println("当前在线人数:"+ONLINE_CLIENT_MAP.size()+","+"用户名如下列表:");
        for(String userName : ONLINE_CLIENT_MAP.keySet()){  //Map的key为用户名
            System.out.println(userName);
        }
    }
}

7. 客户端代码实现 

Client类

package Cilent;
 
import java.io.IOException;
import java.net.Socket;
 
/**
 * package:Cilent
 * Description:客户端
 * @date:2019/8/14
 * @Author:weiwei
 **/
public class cilent {
    public static void main(String[] args) {
        try {
            //读取地址
            String host = "127.0.0.1";
            //读取端口号
            int port = 6666;
 
            Socket client = new Socket(host,port); //先写数据再读数据,读写线程分离
            new ReadDataFromServerThread(client).start();//启动读线程
            new WriteDataToServerThread(client).start();//启动写线程
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

WriteDateToServer类

package Cilent;
 
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Scanner;
 
/**
 * Author:weiwei
 * description:客户端给服务端发送数据的线程
 * 发送的数据来自命令行的交互式输入
 * Creat:2019/3/12
 **/
public class WriteDataToServerThread extends Thread{
    private final Socket client;
    public WriteDataToServerThread(Socket client){
        this.client = client;
    }
    @Override
    public void run(){
        try {
            OutputStream clientOutput = this.client.getOutputStream();
            OutputStreamWriter writer = new OutputStreamWriter(clientOutput);
            Scanner scanner = new Scanner(System.in);  //有客户端输入数据
            while(true){
                System.out.print("请输入>>");
                String data = scanner.nextLine(); //读数据
                writer.write(data+"\n");
                writer.flush();
                if(data.equals("bye")){
                    System.out.println("您已下线...");
                    break;
                }
            }
            this.client.close();
        } catch (IOException e) {
           // e.printStackTrace();
        }
    }
}

ReadDateFromServer类

package Cilent;
 
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.Scanner;
 
/**
 * Author:weiwei
 * description:客户端从服务端读取数据的线程
 * Creat:2019/3/12
 **/
public class ReadDataFromServerThread extends Thread {
    private final Socket client;
    public ReadDataFromServerThread(Socket client){
        this.client=client;
    }
 
    @Override
    public void run(){
        try {
            InputStream clientInput = this.client.getInputStream();
            Scanner scanner = new Scanner(clientInput);
            while(true){
                String data = scanner.nextLine();//按行读数据
                System.out.println("来自服务端消息:"+data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Java/Android 相关文章推荐
Spring Boot 启动、停止、重启、状态脚本
Jun 26 Java/Android
Spring mvc是如何实现与数据库的前后端的连接操作的?
Jun 30 Java/Android
浅谈resultMap的用法及关联结果集映射
Jun 30 Java/Android
java实现对Hadoop的操作
Jul 01 Java/Android
Spring实现内置监听器
Jul 09 Java/Android
SpringRetry重试框架的具体使用
Jul 25 Java/Android
java如何实现socket连接方法封装
Sep 25 Java/Android
JPA 通过Specification如何实现复杂查询
Nov 23 Java/Android
springmvc直接不经过controller访问WEB-INF中的页面问题
Feb 24 Java/Android
Spring Boot优化后启动速度快到飞起技巧示例
Jul 23 Java/Android
Spring boot实现上传文件到本地服务器
Aug 14 Java/Android
java获取一个文本文件的编码(格式)信息
Sep 23 Java/Android
eclipse创建项目没有dynamic web的解决方法
Feign调用传输文件异常的解决
springcloud之Feign超时问题的解决
Feign调用全局异常处理解决方案
总结一下关于在Java8中使用stream流踩过的一些坑
IDEA使用SpringAssistant插件创建SpringCloud项目
使用feign服务调用添加Header参数
You might like
谈谈PHP语法(3)
2006/10/09 PHP
一个简单的PHP入门源程序
2006/10/09 PHP
我的论坛源代码(五)
2006/10/09 PHP
php cache类代码(php数据缓存类)
2010/04/15 PHP
WordPress主题制作之模板文件的引入方法
2015/12/28 PHP
Extjs TriggerField在弹出窗口显示不出问题的解决方法
2010/01/08 Javascript
基于jQuery的的一个隔行变色,鼠标移动变色的小插件
2010/07/06 Javascript
js使用函数绑定技术改变事件处理程序的作用域
2011/12/26 Javascript
网站内容禁止复制和粘贴、另存为的js代码
2014/02/26 Javascript
JavaScript中的逻辑判断符&amp;&amp;、||与!介绍
2014/12/31 Javascript
如何用JavaScript实现动态修改CSS样式表
2016/05/20 Javascript
实例解析Array和String方法
2016/12/14 Javascript
js实现符合国情的日期插件详解
2017/01/19 Javascript
js按条件生成随机json:randomjson实现方法
2017/04/07 Javascript
Angular学习教程之RouterLink花式跳转
2018/05/03 Javascript
bootstrap table表格插件之服务器端分页实例代码
2018/09/12 Javascript
Vue实现移动端页面切换效果【推荐】
2018/11/13 Javascript
让 babel webpack vue 配置文件支持智能提示的方法
2019/06/22 Javascript
js设计模式之代理模式及订阅发布模式实例详解
2019/08/15 Javascript
[01:02:26]DOTA2-DPC中国联赛 正赛 SAG vs RNG BO3 第二场 1月18日
2021/03/11 DOTA
在Python中操作字符串之replace()方法的使用
2015/05/19 Python
Pycharm设置界面全黑的方法
2018/05/23 Python
Python基于类路径字符串获取静态属性
2020/03/12 Python
详解python with 上下文管理器
2020/09/02 Python
智能电子秤、手表和健康监测仪:Withings(之前为诺基亚健康)
2018/10/30 全球购物
湖南卫视在线视频媒体平台:芒果TV
2019/10/30 全球购物
师范教师大学生职业生涯规划范文
2014/01/05 职场文书
新郎父亲婚宴答谢词
2014/01/11 职场文书
绿色家庭事迹材料
2014/05/01 职场文书
水利专业大学生职业生涯规划书范文
2014/09/17 职场文书
群众路线自查报告及整改措施
2014/11/04 职场文书
欠款纠纷起诉状
2015/05/19 职场文书
2016年大学生社会实践心得体会
2015/10/09 职场文书
如何让vue长列表快速加载
2021/03/29 Vue.js
HDFS免重启挂载新磁盘
2022/04/06 Servers
mysql sql常用语句大全
2022/06/21 MySQL