JAVA简单聊天室的实现

2023-11-15

鉴于之前有不少同学在跟我要客户端的代码,我近期整理了一下,把整个工程都传到github上了。地址:https://github.co/Alexlingl/Chatroom

里面有比较详细的工程运行教程,这篇博客则主要对工程的代码实现进行介绍,没有通信知识基础的同学,在看这篇博客之前可以先看下我通信板块的另外几篇博客:

《JAVA通信(一)——输入数据到客户端》

《JAVA通信(二)——实现客户机和服务器通信》

《JAVA通信(三)——实现多人聊天》

前面我们已经了解了通信技术的基本原理,也通过多线程实现了一个服务器同时与多个客户机通信的程序。今天我们来实现一个简单的聊天室。也就是当有客户机给服务器发消息时,这个消息必须同时被发送到其他的客户机。(注意:并不是直接让客户机之间进行连接)

 

一、待实现的聊天室构想

1.我们先来看一下QQ群是怎样运作的。

首先,用户需要通过验证加入到某一个群;加入之后,每个用户都会有自己的一个聊天室界面,这个界面中实时更新所有群成员发送的消息。

2.整体框架图

3.服务器和单一客户机交互图

A.用户信息正确

B.用户信息错误

二、代码架构

按照我们前面的分析,感觉只需要构建服务器和客户机这两个类就可以实现这个聊天室了。但是这样一来就会造成这两个类中包含了过多的方法,有悖于面向对象的“单一职责原则”。(《面向对象的三大特征和六大基本原则》)不利于我们后期对这个程序进行修改扩展。因此这里我们对这两个类进行了更加仔细的职责划分。总共分成以下五个类。

ChatServer类:服务器类,也是主类,里面包含服务器的创建方法setUpServer(int port)和主函数入口main。当程序开始运行时,它会把相应的端口port设置为服务器。并让其始终处于待连接状态。每当有客户机连接上来时,就实例化一个线程类(ServerThread)对象,并启动一个线程去处理。(也就相当于我们为每个用户提供了一个独立的线程)。

ServerThread类:客户端类。它是一个线程类。里面实现了线程的启动方法run()和客户机服务器的通信处理方法processSocket()。当然在通信之前我们必须要先验证这个用户信息是否正确。这个验证方法我们在DaoTool类中实现。这里直接调用它的验证方法即可

DaoTool:用户信息验证类。里面实现了用户信息的验证方法checkLogin()。并且它还储存了一个模拟的用户信息库userDB。

UserInfo:用户信息类。里面保存了每一个用户的信息,包括用户名和密码。定义了获取用户名和密码的方法。

ChatTools:聊天室类。负责保存当前登录的每一个用户,并且当某一个客户机给服务器发了消息,它需要立即把这条消息转发给其他客户机。

细分后的构图如下:

三、具体的代码实现

package communicatetest4;

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

public class ChatServer {
	//主函数入口
	public static void main(String[] args) throws IOException {
		//实例化一个服务器类的对象
		ChatServer cs=new ChatServer();
		//调用方法,为指定端口创建服务器
		cs.setUpServer(9000);
	}

	private void setUpServer(int port) throws IOException {
		// TODO Auto-generated method stub
		ServerSocket server=new ServerSocket(port);
		//打印出当期创建的服务器端口号
		System.out.println("服务器创建成功!端口号:"+port);
		while(true) {
			//等待连接进入
			Socket socket=server.accept();
			System.out.println("进入了一个客户机连接:"+socket.getRemoteSocketAddress().toString());
			//启动一个线程去处理这个对象
			ServerThread st=new ServerThread(socket);
			st.start();
		}
	}
}
package communicatetest4;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;

/*
 * 每当有客户机和服务器连接时,都要定义一个接受对象来进行数据的传输
 * 从服务器的角度看,这个类就是客户端
 */
public class ServerThread extends Thread{
	private Socket client;//线程中的处理对象
	private OutputStream ous;//输出流对象
	private UserInfo user;//用户信息对象
	
	public ServerThread(Socket client) {
		this.client=client;
	}
	
	public UserInfo getOwerUser() {
		return this.user;
	}
	
	public void run() {
		try {
			processSocket();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	//在显示屏中打印信息,例如"用户名"、"密码"等等
	public void sendMsg2Me(String msg) throws IOException {
		msg+="\r\n";
		ous.write(msg.getBytes());
		ous.flush();
	}
	
	
	private void processSocket() throws IOException {
		// TODO Auto-generated method stub
		InputStream ins=client.getInputStream();
		ous=client.getOutputStream();
		BufferedReader brd=new BufferedReader(new InputStreamReader(ins));
		
		sendMsg2Me("欢迎你来聊天,请输入你的用户名:");
		String userName=brd.readLine();
		sendMsg2Me("请输入密码:");
		String pwd=brd.readLine();
		user=new UserInfo();
		user.setName(userName);
		user.setPassword(pwd);
		//调用数据库,验证用户是否存在
		boolean loginState=DaoTools.checkLogin(user);
		if(!loginState) {
			//如果不存在这个账号则关闭
			this.closeMe();
			return;
		}
		ChatTools.addClient(this);//认证成功,把这个用户加入服务器队列
		String input=brd.readLine();
		while(!input.equals("bye")) {
			System.out.println("服务器读到的是:"+input);
			ChatTools.castMsg(this.user, input);
			input=brd.readLine();
		}
		ChatTools.castMsg(this.user, "bye");
		this.closeMe();
	}
	
	//关闭当前客户机与服务器的连接。
	public void closeMe() throws IOException {
		client.close();
	}
	
	
}
package communicatetest4;

import java.io.IOException;
import java.util.ArrayList;

/*
 * 定义一个管理类,相当于一个中介,处理线程,转发消息
 * 这个只提供方法调用,不需要实例化对象,因此都是静态方法
 */
public class ChatTools {
	//保存线程处理的对象
	private static ArrayList<ServerThread> stList=new ArrayList();
	//不需要实例化类,因此构造器为私有
	private ChatTools() {}
	
	//将一个客户对应的线程处理对象加入到队列中
	public static void addClient(ServerThread st) throws IOException {
		stList.add(st);//将这个线程处理对象加入到队列中
		castMsg(st.getOwerUser(),"我上线了!目前人数:"+stList.size());
	}
	
	//发送消息给其他用户
	public static void castMsg(UserInfo sender,String msg) throws IOException {
		msg=sender.getName()+"说:"+msg;//加上说的对象
		for(int i=0;i<stList.size();i++) {
			ServerThread st=stList.get(i);
			st.sendMsg2Me(msg);//发消息给每一个客户机
		}
	}
}
package communicatetest4;

import java.util.HashMap;
import java.util.Map;

//定义一个处理用户登录信息的类
public class DaoTools {
	//内存用户信息数据库
	private static Map<String,UserInfo>userDB=new HashMap();
	//静态块:模拟生成内存中的用户数据,用户名为1~10
	//当程序启动时这段代码会自动执行向userDB放入数据
	static {
		for(int i=1;i<=10;i++) {
			UserInfo user=new UserInfo();
			user.setName("user"+i);
			user.setPassword("psw"+i);
			userDB.put(user.getName(), user);
		}
	}
	
	public static boolean checkLogin(UserInfo user) {
		//在只验证用户名是否存在
		if(userDB.containsKey(user.getName())) {
			return true;
		}
		System.out.println(user.getName()+"用户验证失败!");
		return false;
	}
}
package communicatetest4;
//定义一个用户信息的类
public class UserInfo {
	private String name;//用户名
	private String password;//密码
	private String loignTime;//登录时间
	private String address;//客户机端口名
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		// TODO Auto-generated method stub
		this.name=name;
	}
	
	public void setPassword(String psw) {
		this.password=password;
	}
}

四、程序实现图

五、小总结

1.服务器和客户机的连接。其实在验证用户名和密码之前,客户机就已经和服务器连接上了。如果没有连接,我们是无法把用户名和密码送给服务器进行验证的。只是当我们验证完用户信息后,如果发现这个用户名不存在或者密码不正确时,再去断开客户机和服务器的连接。

2.ChatTools里面都是静态属性和静态方法,因为我们不需要实例化这个类的对象,我们只想要调用它的方法,对静态方法不了解的同学可以看一下我的另一篇博客(《静态方法和非静态方法的区别JAVA》

3、这种基于BIO模型所写出来的服务器性能比较有限,最大并发数大约在2000到2300左右个客户端。后期我进一步提升了服务器的性能,详情可见博客(《C10k破局(一)——线程池和消息队列实现高并发服务器》)。当然使用线程池只是在BIO模型的基础上做了一定的优化,真想要要大幅度地提升服务器性能就只能使用NIO或者AIO模型进行重构,有兴趣的小伙伴可以看下我的另一篇博客(《基于netty NIO开发的聊天室》

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JAVA简单聊天室的实现 的相关文章

  • Java 中等效的并行扩展

    我在 Net 开发中使用并行扩展有一些经验 但我正在考虑在 Java 中做一些工作 这些工作将受益于易于使用的并行库 JVM 是否提供任何与并行扩展类似的工具 您应该熟悉java util concurrent http java sun
  • Spring Batch 多线程 - 如何使每个线程读取唯一的记录?

    这个问题在很多论坛上都被问过很多次了 但我没有看到适合我的答案 我正在尝试在我的 Spring Batch 实现中实现多线程步骤 有一个包含 100k 条记录的临时表 想要在 10 个线程中处理它 每个线程的提交间隔为 300 因此在任何时
  • 如何默认将 Maven 插件附加到阶段?

    我有一个 Maven 插件应该在编译阶段运行 所以在项目中consumes我的插件 我必须做这样的事情
  • 为什么 i++ 不是原子的?

    Why is i Java 中不是原子的 为了更深入地了解 Java 我尝试计算线程中循环的执行频率 所以我用了一个 private static int total 0 在主课中 我有两个线程 主题 1 打印System out prin
  • 如何在 Play java 中创建数据库线程池并使用该池进行数据库查询

    我目前正在使用 play java 并使用默认线程池进行数据库查询 但了解使用数据库线程池进行数据库查询可以使我的系统更加高效 目前我的代码是 import play libs Akka import scala concurrent Ex
  • 在画布上绘图

    我正在编写一个 Android 应用程序 它可以在视图的 onDraw 事件上直接绘制到画布上 我正在绘制一些涉及单独绘制每个像素的东西 为此我使用类似的东西 for int x 0 x lt xMax x for int y 0 y lt
  • JAXb、Hibernate 和 beans

    目前我正在开发一个使用 Spring Web 服务 hibernate 和 JAXb 的项目 1 我已经使用IDE hibernate代码生成 生成了hibernate bean 2 另外 我已经使用maven编译器生成了jaxb bean
  • 控制Android的前置LED灯

    我试图在用户按下某个按钮时在前面的 LED 上实现 1 秒红色闪烁 但我很难找到有关如何访问和使用前置 LED 的文档 教程甚至代码示例 我的意思是位于 自拍 相机和触摸屏附近的 LED 我已经看到了使用手电筒和相机类 已弃用 的示例 但我
  • Java按日期升序对列表对象进行排序[重复]

    这个问题在这里已经有答案了 我想按一个参数对对象列表进行排序 其日期格式为 YYYY MM DD HH mm 按升序排列 我找不到正确的解决方案 在 python 中使用 lambda 很容易对其进行排序 但在 Java 中我遇到了问题 f
  • Java TestNG 与跨多个测试的数据驱动测试

    我正在电子商务平台中测试一系列商店 每个商店都有一系列属性 我正在考虑对其进行自动化测试 是否有可能有一个数据提供者在整个测试套件中提供数据 而不仅仅是 TestNG 中的测试 我尝试不使用 testNG xml 文件作为机制 因为这些属性
  • 在两个活动之间传输数据[重复]

    这个问题在这里已经有答案了 我正在尝试在两个不同的活动之间发送和接收数据 我在这个网站上看到了一些其他问题 但没有任何问题涉及保留头等舱的状态 例如 如果我想从 A 类发送一个整数 X 到 B 类 然后对整数 X 进行一些操作 然后将其发送
  • Java Integer CompareTo() - 为什么使用比较与减法?

    我发现java lang Integer实施compareTo方法如下 public int compareTo Integer anotherInteger int thisVal this value int anotherVal an
  • 无法捆绑适用于 Mac 的 Java 应用程序 1.8

    我正在尝试将我的 Java 应用程序导出到 Mac 该应用程序基于编译器合规级别 1 7 我尝试了不同的方法来捆绑应用程序 1 日食 我可以用来在 Eclipse 上导出的最新 JVM 版本是 1 6 2 马文 看来Maven上也存在同样的
  • Android 中麦克风的后台访问

    是否可以通过 Android 手机上的后台应用程序 服务 持续监控麦克风 我想做的一些想法 不断聆听背景中的声音信号 收到 有趣的 音频信号后 执行一些网络操作 如果前台应用程序需要的话 后台应用程序必须能够智能地放弃对麦克风的访问 除非可
  • 在mockito中使用when进行模拟ContextLoader.getCurrentWebApplicationContext()调用。我该怎么做?

    我试图在使用 mockito 时模拟 ContextLoader getCurrentWebApplicationContext 调用 但它无法模拟 here is my source code Mock org springframewo
  • Java列表的线程安全

    我有一个列表 它将在线程安全上下文或非线程安全上下文中使用 究竟会是哪一个 无法提前确定 在这种特殊情况下 每当列表进入非线程安全上下文时 我都会使用它来包装它 Collections synchronizedList 但如果不进入非线程安
  • 玩!框架:运行“h2-browser”可以运行,但网页不可用

    当我运行命令时activator h2 browser它会使用以下 url 打开浏览器 192 168 1 17 8082 但我得到 使用 Chrome 此网页无法使用 奇怪的是它以前确实有效 从那时起我唯一改变的是JAVA OPTS以启用
  • 静态变量的线程安全

    class ABC implements Runnable private static int a private static int b public void run 我有一个如上所述的 Java 类 我有这个类的多个线程 在里面r
  • Firebase 添加新节点

    如何将这些节点放入用户节点中 并创建另一个节点来存储帖子 我的数据库参考 databaseReference child user getUid setValue userInformations 您需要使用以下代码 databaseRef
  • 如何实现仅当可用内存较低时才将数据交换到磁盘的写缓存

    我想将应用程序生成的数据缓存在内存中 但如果内存变得稀缺 我想将数据交换到磁盘 理想情况下 我希望虚拟机通知它需要内存并将我的数据写入磁盘并以这种方式释放一些内存 但我没有看到任何方法以通知我的方式将自己挂接到虚拟机中before an O

随机推荐

  • 递归递归递归

    function DG htmlDom n n for var i 0 i lt htmlDom length i var navSubmenu htmlDom i nav submenu var item htmlDom i if nav
  • 2023年Java面试题_Mongodb

    Index Mongodb 1 基本概念 1 1 文档 1 2 集合 1 3 数据类型 1 4 id 和 ObjectId 2 基本操作 3 索引介绍 4 应用场景 4 1 MySQL VS MongoDB 4 2 应用场景 4 3 压测结
  • MySQL——关系型数据库管理系统

    目录 01 数据库 02 SQL 结构化查询语言 关于SQL语句的分类 03 MySQL常用命令 1 退出mysql exit 2 查看mysql中有哪些数据库 3 选择使用某个数据库 4 创建数据库 5 查看某个数据库下有哪些表 6 查看
  • 操作系统读书笔记- 01 x86系统架构概览.md-html

    x86系统架构概览 真看不懂了 今天就写这些吧 2 0 处理器工作模式 一般来讲 x86 64处理器具有5种工作模式 实模式 Real address Mode 处理器以16位8086的方式工作 只能以简单的段地址 偏移地址方式进行寻址 地
  • 在Javascript中怎样判断用户按下的是回车键?

  • 本地chrome,访问(超链接跳转)本地文件解决方案

    问题和背景描述 1 用html php写了一个脚本 先从数据库中获取pdf文件的路径 然后将这个路径映射成一个html中的超链接 但是我在浏览器中点击这个超链接 死活跳转不了 2 经过多方调查 和搜索 最终找到了问题的原因 chrome中有
  • Java加密算法有几种?

    前言 编程中常见的加密算法有以下几种 你都知道是哪些吗 它们在不同场景中分别有应用 除信息摘要算法外 其它加密方式都会需要密钥 密钥 密钥 key 又常称金钥 是指某个用来完成加密 解密 完整性验证等密码学应用的秘密信息 密钥分类 加解密中
  • centos7更换和升级JDK版本

    卸载 查询是否安装 jdk rpm qa grep jdk rpm qa grep java 卸载安装的 jdk yum y remove java yum 查询支持的版本 可以先更新一下 yum 源 以便支持最新版本 yum y upda
  • 机器学习之线性回归

    什么是线性回归 线性回归利 回归 程 函数 对 个或多个 变量 特征值 和因变量 标值 之间 关系进 建模的 种分析 式 一般只有一个特征值的称之为单变量回归 多个特征值的称之为多变量回归 线性回归 线性回归可以分为两类 线性关系和非线性关
  • Leetcode42.接雨水——双指针法

    文章目录 引入 双指针法 引入 本题是这样的 42 接雨水 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图 计算按此排列的柱子 下雨之后能接多少雨水 题目给出的图片一眼就能了然题目要问的是什么 很明显 某一列能装多少水 取决于该列左
  • android ui状态栏高度,移动界面尺寸!安卓720*1280界面尺寸规范参考

    今天25学堂的老谭童鞋跟大家继续分享720 1080的界面设计尺寸规范 主要讲解 屏幕分辨 1280 720像素 720P APP界面设计规范 这样的手机又vivo智能收款机 三星Galaxy A5 华为荣耀等手机 这样的手机屏幕尺寸是 5
  • 4年经验来面试20K的测试岗,连基础都不会,还不如招应届生。。。

    公司前段时间缺人 也面了不少测试 结果竟然没有一个合适的 一开始瞄准的就是中级的水准 也没指望来大牛 提供的薪资在10 20k 面试的人很多 但平均水平很让人失望 看简历很多都是3 4年工作经验 但面试中 不提工具和编程 仅仅基础的技术很多
  • 解决Visual Studio设置C++标准 但是_cplusplus始终为199711

    目录 场景复现 Visual Studio官方说明 C 标准对应表 解决方案 方法一 恢复 cplusplus宏 方法二 使用 MSVC LANG宏 场景复现 我在VS2022偶然的一次测试C 标准开发环境 发现无论我怎么修改C 语言标准
  • List 集合 —— ArrayList

    ArrayList 简介 成员变量 构造方法 成员方法 增 删 其他 总结 参考 简介 ArrayList 是 Java 集合框架中比较常用的类 是用来存储数据的容器 可存储重复的元素 允许存储null值 底层基于数组实现容量大小动态变化
  • windows11安装cp210x驱动

    windows11安装cp210x驱动 1 第一步官网下载驱动 官网地址如下 CP210x USB to UART Bridge VCP Drivers Silicon Labs 第二步 解压文件夹并安装如图所示 3 第三步安装成功后会给你
  • mysql 建表 null,MySQL:唯一,但默认为NULL-创建表允许。允许插入多个NULL。为什么?...

    I ve just checked and it s allowed to create a table with a column that is NULL by default although it s a UNIQUE KEY at
  • Cocos Creator Android 平台接入 Google Firebase (Analytics功能)

    在项目推广运营过程中 经常有分析用户行为的需求 如用户安装 注册 充值等事件 因此需要接入Google Firebase Analytics功能 下面以 Android 平台接入为例 进行详细说明 一 准备工作 1 应用targetSdkV
  • 谐振子方程

    设谐振子质量为 m m m 弹簧弹性系数为 k k k 由胡克定律及牛顿运动定律 有 m x k x m ddot x kx mx kx 其中 x x x为偏离平衡位置的距离 x ddot x x 为 x x x对时间 t t t的二阶导数
  • 解决在Linux系统中创建qt项目时没有生成.cpp和.h的问题

    创建QT项目时没有生成 cpp和 h文件 在linux中的解决方法 1 Qt creater 工具 gt 构建和运行 先查看自己安装版本支持的构建套件 我这里是默认MinGW 32bit 2 新建项目时 选择对应的kits即可 至此 如果还
  • JAVA简单聊天室的实现

    鉴于之前有不少同学在跟我要客户端的代码 我近期整理了一下 把整个工程都传到github上了 地址 https github co Alexlingl Chatroom 里面有比较详细的工程运行教程 这篇博客则主要对工程的代码实现进行介绍 没