欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程语言 > java >内容正文

java

JavaSE聊天室项目

发布时间:2024/1/8 java 36 豆豆
生活随笔 收集整理的这篇文章主要介绍了 JavaSE聊天室项目 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

功能

1.用户名登录注册
2.上下线提醒
3.在线列表
4.私聊
5.公聊
6.发送文字文件
7.聊天记录的保存 查询 删除
8.下线

项目步骤

效果图UI→→→项目文档→→→接口文档→→→数据库的表→→→分组开发
GIT/SVN→→→测试→→→出Bug→→→调试→→→正式上线→→→版本更新维护

统一开发环境

Java JDK 1.8 IDEA2018.2.8 win10

客户端 用户

分包(分别管理不同类)
chatroom.ui
chatroom.utils
chatroom.config(创建一个数据类型的借口,方便服务器进行判断)

public interface MsgType { public static final int MSG_PRIVATE=100;//私聊类型 public static final int MSG_GROUP=200;//群聊类型 public static final int MSG_ONLINE=300;//上线提醒 public static final int MSG_WHOONLINE=400;//在线列表 public static final int MSG_OFFLINE=500;//下线 public static final int MSG_SENDFILES=600;//发送文件 public static final int MSG_HIDDEN=700;//切换 }

服务端 服务器

分包(分别管理不同类)
chatroom.ui
chatroom.utils
chatroom.config(创建一个数据类型的借口,方便服务器进行判断)

public interface MsgType { public static final int MSG_PRIVATE=100;//私聊类型 public static final int MSG_GROUP=200;//群聊类型 public static final int MSG_ONLINE=300;//上线提醒 public static final int MSG_WHOONLINE=400;//在线列表 public static final int MSG_OFFLINE=500;//下线 public static final int MSG_SENDFILES=600;//发送文件 public static final int MSG_HIDDEN=700;//切换 }

第一步(提示用户注册并判断没有重复注册之后开启客户端线程弹出选择目录)

客户端提示用户输入用户名注册

while (true) {System.out.println("1.注册 2.登录");//登录注册,需要保存账户名和密码当选择登录输入用户名和密码 System.out.println("请输入用户名");//对用户名校验String userName = scanner.nextLine();//把用户名写给服务端out.write(userName.getBytes());//读取服务端,是否注册成功byte[] bytes = new byte[1024];int len = in.read(bytes);String fk = new String(bytes, 0, len);if ("yes".equals(fk)) {System.out.println("用户名注册成功");break;} else {System.out.println("用户名已注册,请重新注册");}}

服务器开启通道读取是否已经存在该用户

InputStream in = sk.getInputStream();OutputStream out = sk.getOutputStream();while (true){byte[] bytes = new byte[1024];int len =in.read(bytes);userName = new String(bytes, 0, len);//判断用户名是否存在if (!hashMap.containsKey(userName)){hashMap.put(userName,sk);//顺便给单列集合存储用户名list.add(userName);//给客户一个反馈out.write("yes".getBytes());break;}else {out.write("no".getBytes());}}

当注册成功之后开启一个客户端线程并为用户提供选择目录

所有功能都是客户端发送消息给服务端统一格式是由"#"分割成三部分方便服务端读取

服务端读取消息之后又再次拼接消息给客户端线程,由"-"拼接,统一由四部分组成方便客户端线程读取

难点发送文件,切换转态

发送文件可以将消息分为三个部分1文件名和文件路径2空格3所要发送的文件接收时也可由此方式截取文件

切换状态,在创建双列集合存储用户名跟通道时创建单列集合存储用户名,在切换时只需要操作单列集合

boolean flag = true;while (flag) {System.out.println("请选择1.私聊 2.群聊 3.在线列表 4.下线 5.发送文件 6.隐身/上线 7.查询聊天记录");//int num = scanner.nextInt();int num = InputUtil.inputIntType(new Scanner(System.in));switch (num) {case 1://私聊privateChat(out, scanner)break;case 2://群聊publicChat();break;case 3://在线列表whoOnline();break;case 4://下线offline();flag = false;case 5://发送文件sendFiles();break;case 6:switchState();break;case 7://查询聊天记录break;}}ct.stop();//强制关闭线程sk.close();//关闭客户端socket

目录选择实现功能

1.客户端向服务端转发消息形式统一
2.“如果有需要可以是具体值”+"#"+“否则也可以是空值”+"#"+MsgType.MSG_HIDDEN
;3.主要目的是服务端收到字节数据之后方便截取

1私聊功能

客户端发出消息给服务端

private static void privateChat(OutputStream out, Scanner scanner) throws IOException {while (true) {//发消息 格式 接受者#消息内容System.out.println("你处于私聊模式,请输入消息 接收者:消息内容 退出输入-q");String msg = scanner.nextLine();if ("-q".equals(msg)) {break;}msg=msg+"#"+ MsgType.MSG_PRIVATE;out.write(msg.getBytes());}

服务端接收消息并且转发给客户端开启的线程

服务端转发消息的格式统一由“-”分割为四部分,目的同理是方便接收消息的客户端线程读取

if (msgType == MsgType.MSG_PRIVATE) {//私聊//转发 发送者-消息内容-消息类型-时间String zfMsg = userName + "-" + msgContent + "-" + msgType + "-" + System.currentTimeMillis();//取出接收者管道,把组拼好的消息发送hashMap.get(reciver).getOutputStream().write(zfMsg.getBytes());

客户端开启线程接收消息

if (msgType == MsgType.MSG_PRIVATE) {System.out.println(dateStr);System.out.println(sender + "对你说:" + "---" + msgContent);

2.群发消息功能

客户端发出消息给服务端

private static void publicChat() throws IOException {while (true) {//发消息 格式 接受者#消息内容System.out.println("你处于群聊模式,请输入消息 消息内容 退出输入-q");String msg = scanner.nextLine();if ("-q".equals(msg)) {break;}msg="群消息"+"#"+msg+"#"+ MsgType.MSG_GROUP;out.write(msg.getBytes());} }

服务端接收消息并且转发给客户端开启的线程(与私聊不同之处就是将消息发个每个客户端,以为每个客户端储存在集合中。所以遍历集合,每个key作为接收者接收消息即可)

else if (msgType == MsgType.MSG_GROUP) {//群聊 遍历所有人Set<String> keySet = hashMap.keySet();for (String key : keySet) {//排除自己,不给自己发if (userName.equals(key)) {continue;}Socket socket = hashMap.get(key);//服务器转发格式:发送者-消息内容-消息类型-时间String zfMsg = userName + "-" + msgContent + "-" + MsgType.MSG_GROUP + "-" + System.currentTimeMillis();socket.getOutputStream().write(zfMsg.getBytes());}

客户端开启线程接收消息

else if (msgType == MsgType.MSG_GROUP) {System.out.println(dateStr);System.out.println(sender + "对大家说:" + "---" + msgContent);

3.上线提醒功能

服务端转发给客户端开启的线程(与群发消息相似,只需遍历集合将消息转发个每个元素即可,两者都不需要给本身发消息)

Set<String> keySet = hashMap.keySet();for (String key : keySet) {//排除自己,不给自己发上线提醒if(userName.equals(key)){continue;}Socket socket = hashMap.get(key);String zfMsg=userName+"-"+"上线了"+"-"+MsgType.MSG_ONLINE+"-"+System.currentTimeMillis();socket.getOutputStream().write(zfMsg.getBytes());}

客户端开启线程接收消息

else if (msgType == MsgType.MSG_ONLINE) {System.out.println(dateStr + sender + "---上线了---");}

4.在线列表

客户端发出消息给服务端(请求者的客户端直接将消息转发给服务端)

private static void whoOnline() throws IOException{//获取在线列表String msg ="当前在线"+"#"+"在线"+"#"+MsgType.MSG_WHOONLINE;out.write(msg.getBytes()); }

服务端遍历当前连接的客户端并且转发给请求者

else if (msgType == MsgType.MSG_WHOONLINE) {//获取在线列表 把集合中的人拼接好发送给请求者Set<String> keySet = hashMap.keySet();StringBuffer sb = new StringBuffer();int i = 1;for (String key : list) {if (userName.equals(key)) {continue;}sb.append((i++)).append(".").append(key).append("\n");}String zfMsg = userName + "-" + sb.toString() + "-" + MsgType.MSG_WHOONLINE + "-" + System.currentTimeMillis();//发给请求者hashMap.get(userName).getOutputStream().write(zfMsg.getBytes());}

请求者接收服务端转发过来的消息并且打印

else if (msgType == MsgType.MSG_WHOONLINE) {
System.out.println(dateStr);
System.out.println(msgContent);
}

5.下线功能

下线的客户端发送消息给服务端( //客户端 1给服务端下线指令 2关闭客户端的socket 3关闭客户端读取消息的线程)

private static void offline() throws IOException{//客户端 1给服务端下线指令 2关闭客户端的socket 3关闭客户端读取消息的线程String msg ="下线"+"#"+"下线"+"#"+MsgType.MSG_OFFLINE;out.write(msg.getBytes()); }

服务端收到下线消息之后(//服务端 1通知其他人 2关闭下线这个人的socket 3把下线这个人从集合中取出)

else if (msgType == MsgType.MSG_OFFLINE) {Set<String> keySet = hashMap.keySet();for (String key : keySet) {//排除自己,不给自己发if (userName.equals(key)) {continue;}Socket socket = hashMap.get(key);String zfMsg = userName + "-" + "下线了" + "-" + MsgType.MSG_OFFLINE + "-" + System.currentTimeMillis();socket.getOutputStream().write(zfMsg.getBytes());}break;}

客户端线程接收下线提醒

else if (msgType == MsgType.MSG_OFFLINE) {System.out.println(dateStr + sender + "---下线了---");}

6.转发文件

客户端

private static void sendFiles() throws IOException{Scanner scanner = new Scanner(System.in);System.out.println("请输入接收者");String reciver = scanner.nextLine();System.out.println("请输入文件路径");String filePath = scanner.nextLine();File file = new File(filePath);String msg =reciver+"#"+file.getName()+"&"+file.length()+"#"+MsgType.MSG_SENDFILES;//文本字节数据byte[] msgbytes = msg.getBytes();//定义空字节数组byte[] emptyBytes = new byte[1024 * 10 - msgbytes.length];//获取文件的字节数据byte[] fileBytes = InputAndOutputUtil.readFile(filePath);//把三个小的字节数组拼接成一个大的字节数组ByteArrayOutputStream bos = new ByteArrayOutputStream();bos.write(msgbytes);bos.write(emptyBytes);bos.write(fileBytes);//把总的字节数组发给服务端byte[] allBytes = bos.toByteArray();out.write(allBytes);out.write(msg.getBytes());}

服务端接收文件

else if(msgType == MsgType.MSG_SENDFILES){String[] fileInfo = msgContent.split("&");String fileName=fileInfo[0];Long fileLength=Long.parseLong(fileInfo[1]);//2.组拼消息:1.文本字节2,空字节3,文件字节String zfMsg= userName + "-" + msgContent + "-" + MsgType.MSG_SENDFILES + "-" + System.currentTimeMillis();byte[] textBytes = zfMsg.getBytes();byte[] emptyBytes=new byte[1024*10-textBytes.length];//需要读取文件的字节数组ByteArrayOutputStream bos = new ByteArrayOutputStream();byte[] catchBytes=new byte[1024*8];int catchLen=0;while (true){int lens = in.read(catchBytes); //读取通道中的文件字节数据//往内存中写bos.write(catchBytes,0,lens);catchLen+=lens; //统计读取文件的字节数if(catchLen==fileLength){ //当文件数据读取完,结束循环break;}}//取出文件的字节数据byte[] fileBytes = bos.toByteArray();//拼接三个字节数组,转发给接收者bos.reset();//重置此流,清空缓存 ByteArrayOutputStreambos.write(textBytes);bos.write(emptyBytes);bos.write(fileBytes);//取出总的字节数组byte[] allBytes = bos.toByteArray();//把这个总的字节数组转发给接收者hashMap.get(reciver).getOutputStream().write(allBytes);}

客户端线程接收文件(接受者可以选择是否保存收到的文件)

else if (msgType == MsgType.MSG_SENDFILES) {//1.从消息内容中,取出文件名和文件大小String[] fileInfo = msgContent.split("&");String fileName = fileInfo[0];Long fileLength = Long.parseLong(fileInfo[1]);System.out.println(dateStr);System.out.println(sender + ":给你发来一个文件:" + fileName + " 文件大小:" + (fileLength / 1024 / 1024.0) + "MB");System.out.println("你是否接收?y/n");//因为主线程在使用Scanner 那么子线程不能用Scanner 录入数据//可以通过在主线程,修改标记,让子线程选择要不要保存这个文件while (isSave) { //trueif (isClose) {//falsebreak;}}//保存y:isSave=false isClose=false//不保存n:isSave=true isClose=true;//读取文件字节数数据保存到本地//需要读取文件的字节数组if (isClose) {//不保存把通道中发过来的数据读完即可,不往本地存储ByteArrayOutputStream bos = new ByteArrayOutputStream();byte[] catchBytes = new byte[1024 * 8];int catchLen = 0;while (true) {int lens = in.read(catchBytes); //读取通道中的文件字节数据//往内存中写bos.write(catchBytes, 0, lens);catchLen += lens; //统计读取文件的字节数if (catchLen == fileLength) { //当文件数据读取完,结束循环break;}}//读完之后,清理缓存bos.reset();} else {//保存ByteArrayOutputStream bos = new ByteArrayOutputStream();byte[] catchBytes = new byte[1024 * 8];int canLen = 0;while (true) {int lens = in.read(catchBytes); //读取通道中的文件字节数据//往内存中写bos.write(catchBytes, 0, lens);canLen += lens; //统计读取文件的字节数if (canLen == fileLength) { //当文件数据读取完了,结束循环break;}}//取出文件的字节数据byte[] fileBytes = bos.toByteArray();boolean b = InputAndOutputUtil.writeFile("E:\\" + fileName, fileBytes);if (b) {System.out.println("文件保存成功在" + "E:\\" + fileName);} else {System.out.println("文件保存失败");}}//最后把标记再次置为默认值isSave = true;//再定义一个标记isClose = false;}

切换状态(在 创建双列集合存储用户跟通道时创建单列集合存储用户名)

客户端给服务端申请

private static void switchState() throws IOException {
String msg =“切换”+"#"+“切换”+"#"+MsgType.MSG_HIDDEN;
out.write(msg.getBytes());
}

服务端接收申请(如果再次切换到在线状态给当前连接的客户端再次发送上线提醒)

else if(msgType == MsgType.MSG_HIDDEN){if(isHidden){list.remove(userName);}else {list.add(userName);Set<String> keySet = hashMap.keySet();for (String key : list) {//排除自己,不给自己发上线提醒if(userName.equals(key)){continue;}Socket socket = hashMap.get(key);String zfMsg=userName+"-"+" 上线了"+"-"+MsgType.MSG_ONLINE+"-"+System.currentTimeMillis();socket.getOutputStream().write(zfMsg.getBytes());}}isHidden=!isHidden;}

总结

以上是生活随笔为你收集整理的JavaSE聊天室项目的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。