JavaSE聊天室项目
功能
1.用戶名登錄注冊(cè)
2.上下線提醒
3.在線列表
4.私聊
5.公聊
6.發(fā)送文字文件
7.聊天記錄的保存 查詢 刪除
8.下線
項(xiàng)目步驟
效果圖UI→→→項(xiàng)目文檔→→→接口文檔→→→數(shù)據(jù)庫(kù)的表→→→分組開(kāi)發(fā)
GIT/SVN→→→測(cè)試→→→出Bug→→→調(diào)試→→→正式上線→→→版本更新維護(hù)
統(tǒng)一開(kāi)發(fā)環(huán)境
Java JDK 1.8 IDEA2018.2.8 win10
客戶端 用戶
分包(分別管理不同類)
chatroom.ui
chatroom.utils
chatroom.config(創(chuàng)建一個(gè)數(shù)據(jù)類型的借口,方便服務(wù)器進(jìn)行判斷)
服務(wù)端 服務(wù)器
分包(分別管理不同類)
chatroom.ui
chatroom.utils
chatroom.config(創(chuàng)建一個(gè)數(shù)據(jù)類型的借口,方便服務(wù)器進(jìn)行判斷)
第一步(提示用戶注冊(cè)并判斷沒(méi)有重復(fù)注冊(cè)之后開(kāi)啟客戶端線程彈出選擇目錄)
客戶端提示用戶輸入用戶名注冊(cè)
while (true) {System.out.println("1.注冊(cè) 2.登錄");//登錄注冊(cè),需要保存賬戶名和密碼當(dāng)選擇登錄輸入用戶名和密碼 System.out.println("請(qǐng)輸入用戶名");//對(duì)用戶名校驗(yàn)String userName = scanner.nextLine();//把用戶名寫給服務(wù)端out.write(userName.getBytes());//讀取服務(wù)端,是否注冊(cè)成功byte[] bytes = new byte[1024];int len = in.read(bytes);String fk = new String(bytes, 0, len);if ("yes".equals(fk)) {System.out.println("用戶名注冊(cè)成功");break;} else {System.out.println("用戶名已注冊(cè),請(qǐng)重新注冊(cè)");}}服務(wù)器開(kāi)啟通道讀取是否已經(jīng)存在該用戶
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);//順便給單列集合存儲(chǔ)用戶名list.add(userName);//給客戶一個(gè)反饋out.write("yes".getBytes());break;}else {out.write("no".getBytes());}}當(dāng)注冊(cè)成功之后開(kāi)啟一個(gè)客戶端線程并為用戶提供選擇目錄
所有功能都是客戶端發(fā)送消息給服務(wù)端統(tǒng)一格式是由"#"分割成三部分方便服務(wù)端讀取
服務(wù)端讀取消息之后又再次拼接消息給客戶端線程,由"-"拼接,統(tǒng)一由四部分組成方便客戶端線程讀取
難點(diǎn)發(fā)送文件,切換轉(zhuǎn)態(tài)
發(fā)送文件可以將消息分為三個(gè)部分1文件名和文件路徑2空格3所要發(fā)送的文件接收時(shí)也可由此方式截取文件
切換狀態(tài),在創(chuàng)建雙列集合存儲(chǔ)用戶名跟通道時(shí)創(chuàng)建單列集合存儲(chǔ)用戶名,在切換時(shí)只需要操作單列集合
boolean flag = true;while (flag) {System.out.println("請(qǐng)選擇1.私聊 2.群聊 3.在線列表 4.下線 5.發(fā)送文件 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://發(fā)送文件sendFiles();break;case 6:switchState();break;case 7://查詢聊天記錄break;}}ct.stop();//強(qiáng)制關(guān)閉線程sk.close();//關(guān)閉客戶端socket目錄選擇實(shí)現(xiàn)功能
1.客戶端向服務(wù)端轉(zhuǎn)發(fā)消息形式統(tǒng)一
2.“如果有需要可以是具體值”+"#"+“否則也可以是空值”+"#"+MsgType.MSG_HIDDEN
;3.主要目的是服務(wù)端收到字節(jié)數(shù)據(jù)之后方便截取
1私聊功能
客戶端發(fā)出消息給服務(wù)端
private static void privateChat(OutputStream out, Scanner scanner) throws IOException {while (true) {//發(fā)消息 格式 接受者#消息內(nèi)容System.out.println("你處于私聊模式,請(qǐng)輸入消息 接收者:消息內(nèi)容 退出輸入-q");String msg = scanner.nextLine();if ("-q".equals(msg)) {break;}msg=msg+"#"+ MsgType.MSG_PRIVATE;out.write(msg.getBytes());}服務(wù)端接收消息并且轉(zhuǎn)發(fā)給客戶端開(kāi)啟的線程
服務(wù)端轉(zhuǎn)發(fā)消息的格式統(tǒng)一由“-”分割為四部分,目的同理是方便接收消息的客戶端線程讀取
if (msgType == MsgType.MSG_PRIVATE) {//私聊//轉(zhuǎn)發(fā) 發(fā)送者-消息內(nèi)容-消息類型-時(shí)間String zfMsg = userName + "-" + msgContent + "-" + msgType + "-" + System.currentTimeMillis();//取出接收者管道,把組拼好的消息發(fā)送hashMap.get(reciver).getOutputStream().write(zfMsg.getBytes());客戶端開(kāi)啟線程接收消息
if (msgType == MsgType.MSG_PRIVATE) {System.out.println(dateStr);System.out.println(sender + "對(duì)你說(shuō):" + "---" + msgContent);2.群發(fā)消息功能
客戶端發(fā)出消息給服務(wù)端
private static void publicChat() throws IOException {while (true) {//發(fā)消息 格式 接受者#消息內(nèi)容System.out.println("你處于群聊模式,請(qǐng)輸入消息 消息內(nèi)容 退出輸入-q");String msg = scanner.nextLine();if ("-q".equals(msg)) {break;}msg="群消息"+"#"+msg+"#"+ MsgType.MSG_GROUP;out.write(msg.getBytes());} }服務(wù)端接收消息并且轉(zhuǎn)發(fā)給客戶端開(kāi)啟的線程(與私聊不同之處就是將消息發(fā)個(gè)每個(gè)客戶端,以為每個(gè)客戶端儲(chǔ)存在集合中。所以遍歷集合,每個(gè)key作為接收者接收消息即可)
else if (msgType == MsgType.MSG_GROUP) {//群聊 遍歷所有人Set<String> keySet = hashMap.keySet();for (String key : keySet) {//排除自己,不給自己發(fā)if (userName.equals(key)) {continue;}Socket socket = hashMap.get(key);//服務(wù)器轉(zhuǎn)發(fā)格式:發(fā)送者-消息內(nèi)容-消息類型-時(shí)間String zfMsg = userName + "-" + msgContent + "-" + MsgType.MSG_GROUP + "-" + System.currentTimeMillis();socket.getOutputStream().write(zfMsg.getBytes());}客戶端開(kāi)啟線程接收消息
else if (msgType == MsgType.MSG_GROUP) {System.out.println(dateStr);System.out.println(sender + "對(duì)大家說(shuō):" + "---" + msgContent);3.上線提醒功能
服務(wù)端轉(zhuǎn)發(fā)給客戶端開(kāi)啟的線程(與群發(fā)消息相似,只需遍歷集合將消息轉(zhuǎn)發(fā)個(gè)每個(gè)元素即可,兩者都不需要給本身發(fā)消息)
Set<String> keySet = hashMap.keySet();for (String key : keySet) {//排除自己,不給自己發(fā)上線提醒if(userName.equals(key)){continue;}Socket socket = hashMap.get(key);String zfMsg=userName+"-"+"上線了"+"-"+MsgType.MSG_ONLINE+"-"+System.currentTimeMillis();socket.getOutputStream().write(zfMsg.getBytes());}客戶端開(kāi)啟線程接收消息
else if (msgType == MsgType.MSG_ONLINE) {System.out.println(dateStr + sender + "---上線了---");}4.在線列表
客戶端發(fā)出消息給服務(wù)端(請(qǐng)求者的客戶端直接將消息轉(zhuǎn)發(fā)給服務(wù)端)
private static void whoOnline() throws IOException{//獲取在線列表String msg ="當(dāng)前在線"+"#"+"在線"+"#"+MsgType.MSG_WHOONLINE;out.write(msg.getBytes()); }服務(wù)端遍歷當(dāng)前連接的客戶端并且轉(zhuǎn)發(fā)給請(qǐng)求者
else if (msgType == MsgType.MSG_WHOONLINE) {//獲取在線列表 把集合中的人拼接好發(fā)送給請(qǐng)求者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();//發(fā)給請(qǐng)求者h(yuǎn)ashMap.get(userName).getOutputStream().write(zfMsg.getBytes());}請(qǐng)求者接收服務(wù)端轉(zhuǎn)發(fā)過(guò)來(lái)的消息并且打印
else if (msgType == MsgType.MSG_WHOONLINE) {
System.out.println(dateStr);
System.out.println(msgContent);
}
5.下線功能
下線的客戶端發(fā)送消息給服務(wù)端( //客戶端 1給服務(wù)端下線指令 2關(guān)閉客戶端的socket 3關(guān)閉客戶端讀取消息的線程)
private static void offline() throws IOException{//客戶端 1給服務(wù)端下線指令 2關(guān)閉客戶端的socket 3關(guān)閉客戶端讀取消息的線程String msg ="下線"+"#"+"下線"+"#"+MsgType.MSG_OFFLINE;out.write(msg.getBytes()); }服務(wù)端收到下線消息之后(//服務(wù)端 1通知其他人 2關(guān)閉下線這個(gè)人的socket 3把下線這個(gè)人從集合中取出)
else if (msgType == MsgType.MSG_OFFLINE) {Set<String> keySet = hashMap.keySet();for (String key : keySet) {//排除自己,不給自己發(fā)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.轉(zhuǎn)發(fā)文件
客戶端
private static void sendFiles() throws IOException{Scanner scanner = new Scanner(System.in);System.out.println("請(qǐng)輸入接收者");String reciver = scanner.nextLine();System.out.println("請(qǐng)輸入文件路徑");String filePath = scanner.nextLine();File file = new File(filePath);String msg =reciver+"#"+file.getName()+"&"+file.length()+"#"+MsgType.MSG_SENDFILES;//文本字節(jié)數(shù)據(jù)byte[] msgbytes = msg.getBytes();//定義空字節(jié)數(shù)組byte[] emptyBytes = new byte[1024 * 10 - msgbytes.length];//獲取文件的字節(jié)數(shù)據(jù)byte[] fileBytes = InputAndOutputUtil.readFile(filePath);//把三個(gè)小的字節(jié)數(shù)組拼接成一個(gè)大的字節(jié)數(shù)組ByteArrayOutputStream bos = new ByteArrayOutputStream();bos.write(msgbytes);bos.write(emptyBytes);bos.write(fileBytes);//把總的字節(jié)數(shù)組發(fā)給服務(wù)端byte[] allBytes = bos.toByteArray();out.write(allBytes);out.write(msg.getBytes());}服務(wù)端接收文件
else if(msgType == MsgType.MSG_SENDFILES){String[] fileInfo = msgContent.split("&");String fileName=fileInfo[0];Long fileLength=Long.parseLong(fileInfo[1]);//2.組拼消息:1.文本字節(jié)2,空字節(jié)3,文件字節(jié)String zfMsg= userName + "-" + msgContent + "-" + MsgType.MSG_SENDFILES + "-" + System.currentTimeMillis();byte[] textBytes = zfMsg.getBytes();byte[] emptyBytes=new byte[1024*10-textBytes.length];//需要讀取文件的字節(jié)數(shù)組ByteArrayOutputStream bos = new ByteArrayOutputStream();byte[] catchBytes=new byte[1024*8];int catchLen=0;while (true){int lens = in.read(catchBytes); //讀取通道中的文件字節(jié)數(shù)據(jù)//往內(nèi)存中寫bos.write(catchBytes,0,lens);catchLen+=lens; //統(tǒng)計(jì)讀取文件的字節(jié)數(shù)if(catchLen==fileLength){ //當(dāng)文件數(shù)據(jù)讀取完,結(jié)束循環(huán)break;}}//取出文件的字節(jié)數(shù)據(jù)byte[] fileBytes = bos.toByteArray();//拼接三個(gè)字節(jié)數(shù)組,轉(zhuǎn)發(fā)給接收者bos.reset();//重置此流,清空緩存 ByteArrayOutputStreambos.write(textBytes);bos.write(emptyBytes);bos.write(fileBytes);//取出總的字節(jié)數(shù)組byte[] allBytes = bos.toByteArray();//把這個(gè)總的字節(jié)數(shù)組轉(zhuǎn)發(fā)給接收者h(yuǎn)ashMap.get(reciver).getOutputStream().write(allBytes);}客戶端線程接收文件(接受者可以選擇是否保存收到的文件)
else if (msgType == MsgType.MSG_SENDFILES) {//1.從消息內(nèi)容中,取出文件名和文件大小String[] fileInfo = msgContent.split("&");String fileName = fileInfo[0];Long fileLength = Long.parseLong(fileInfo[1]);System.out.println(dateStr);System.out.println(sender + ":給你發(fā)來(lái)一個(gè)文件:" + fileName + " 文件大小:" + (fileLength / 1024 / 1024.0) + "MB");System.out.println("你是否接收?y/n");//因?yàn)橹骶€程在使用Scanner 那么子線程不能用Scanner 錄入數(shù)據(jù)//可以通過(guò)在主線程,修改標(biāo)記,讓子線程選擇要不要保存這個(gè)文件while (isSave) { //trueif (isClose) {//falsebreak;}}//保存y:isSave=false isClose=false//不保存n:isSave=true isClose=true;//讀取文件字節(jié)數(shù)數(shù)據(jù)保存到本地//需要讀取文件的字節(jié)數(shù)組if (isClose) {//不保存把通道中發(fā)過(guò)來(lái)的數(shù)據(jù)讀完即可,不往本地存儲(chǔ)ByteArrayOutputStream bos = new ByteArrayOutputStream();byte[] catchBytes = new byte[1024 * 8];int catchLen = 0;while (true) {int lens = in.read(catchBytes); //讀取通道中的文件字節(jié)數(shù)據(jù)//往內(nèi)存中寫bos.write(catchBytes, 0, lens);catchLen += lens; //統(tǒng)計(jì)讀取文件的字節(jié)數(shù)if (catchLen == fileLength) { //當(dāng)文件數(shù)據(jù)讀取完,結(jié)束循環(huán)break;}}//讀完之后,清理緩存bos.reset();} else {//保存ByteArrayOutputStream bos = new ByteArrayOutputStream();byte[] catchBytes = new byte[1024 * 8];int canLen = 0;while (true) {int lens = in.read(catchBytes); //讀取通道中的文件字節(jié)數(shù)據(jù)//往內(nèi)存中寫bos.write(catchBytes, 0, lens);canLen += lens; //統(tǒng)計(jì)讀取文件的字節(jié)數(shù)if (canLen == fileLength) { //當(dāng)文件數(shù)據(jù)讀取完了,結(jié)束循環(huán)break;}}//取出文件的字節(jié)數(shù)據(jù)byte[] fileBytes = bos.toByteArray();boolean b = InputAndOutputUtil.writeFile("E:\\" + fileName, fileBytes);if (b) {System.out.println("文件保存成功在" + "E:\\" + fileName);} else {System.out.println("文件保存失敗");}}//最后把標(biāo)記再次置為默認(rèn)值isSave = true;//再定義一個(gè)標(biāo)記isClose = false;}切換狀態(tài)(在 創(chuàng)建雙列集合存儲(chǔ)用戶跟通道時(shí)創(chuàng)建單列集合存儲(chǔ)用戶名)
客戶端給服務(wù)端申請(qǐng)
private static void switchState() throws IOException {
String msg =“切換”+"#"+“切換”+"#"+MsgType.MSG_HIDDEN;
out.write(msg.getBytes());
}
服務(wù)端接收申請(qǐng)(如果再次切換到在線狀態(tài)給當(dāng)前連接的客戶端再次發(fā)送上線提醒)
else if(msgType == MsgType.MSG_HIDDEN){if(isHidden){list.remove(userName);}else {list.add(userName);Set<String> keySet = hashMap.keySet();for (String key : list) {//排除自己,不給自己發(fā)上線提醒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;}總結(jié)
以上是生活随笔為你收集整理的JavaSE聊天室项目的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 打开模拟器上app的文件位置方法
- 下一篇: 2022年P气瓶充装考题及答案