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(創建一個數據類型的借口,方便服務器進行判斷)
服務端 服務器
分包(分別管理不同類)
chatroom.ui
chatroom.utils
chatroom.config(創建一個數據類型的借口,方便服務器進行判斷)
第一步(提示用戶注冊并判斷沒有重復注冊之后開啟客戶端線程彈出選擇目錄)
客戶端提示用戶輸入用戶名注冊
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聊天室项目的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 打开模拟器上app的文件位置方法
- 下一篇: Java绘制太极阴阳图