1. 程式人生 > >Java——實現聊天室

Java——實現聊天室

學習Java的每一個人都知道,聊天室是每一個程式設計師都要過手的專案,根據要求的不同,聊天室的實現可易可難。

我今天的聊天室程式主要實現的功能是:

1、私聊功能

2、群聊功能

3、檢視成員列表功能

4、退出聊天室功能

5、傳送檔案功能

內容比較簡單,是學完JavaSE的一次知識總結,沒有用到介面等,都是在控制檯上進行輸出的。希望能給大家提供借鑑。程式碼如下,程式在最後打包,希望對於初學者的同學

package org.westos.client;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

import org.westos.client.Config.Config;
import org.westos.util.InputAndOutputUtil;
import org.westos.util.InputUtil;

/**
 * 客戶端
 * @author 虎
 *
 */
public class Client {

	private static Socket s;
	private static InputStream is;
	private static OutputStream os;
	private static Scanner sc;
	private static Config config;
	private static  String clientName;
	public static void main(String[] args) {
		 sc = new Scanner(System.in);
		try {
			 s = new Socket("LocalHost", 9999);
			 os = s.getOutputStream();
			 is = s.getInputStream();
			 //註冊------->不停的去註冊
			 while(true) {
				 System.out.println("請設定您的暱稱");
				 clientName = sc.nextLine();
				 os.write(clientName.getBytes());
				 os.flush();
				 //讀取伺服器的反饋
				 byte[] result = new byte[1024];
				 int len = is.read(result);
				 String str = new String(result, 0, len);
				 if(str.equals("yes")) {
					 System.out.println("註冊成功");
					 break;
				 }
				 else {
					 System.out.println("請重新輸入");
				 }
			 }
			ClientReadThread thread = new  ClientReadThread(is);
			thread.start();
			 boolean isRun = true;
			//開始聊天
			 while(isRun) {
				 System.out.println("請選擇要進行的操作: 1,私聊  2,公聊  3,線上列表 4 ,退出 聊天 5,傳送檔案6,下載檔案");
				 int chioce =InputUtil.inputIntType(new Scanner(System.in));
				 switch (chioce) {
				case 1:
					System.out.println(1);
					privateChat();
					break;
				case 2:	
					System.out.println(2);
					publicChat();
					break;
				case 3:	
					System.out.println(3);
					onlineList();
					break;
				case 4:	
					System.out.println(4);
					exitChat();
					isRun = false;
					break;
				case 5:	
					System.out.println(5);
					sendFile();
					break;
				case 6 :	
					System.out.println(6);
					downloadFile();
					break;

			 }
			 }
		}
		
		 catch (Exception e) {
			e.printStackTrace();
		}
		System.exit(0);
		
	}
	private static void downloadFile() {
		// TODO Auto-generated method stub
		
	}
	private static void sendFile() throws IOException {
		//現主要實現私發
		System.out.println("請輸入接收檔案的使用者姓名:");
		String receiver  = sc.nextLine();
		System.out.println("請選擇您要傳送的檔案:(輸入具體路徑)");
		String filePath = sc.nextLine();
		//構建檔案
		File file = new File(filePath);
		String fileName = file.getName();
		long fileLength = file.length();
		String msg = clientName + ":" + receiver +":" + (fileName + "#" + fileLength) + ":" + Config.MSG_SEBDFILE+":";
		//使用記憶體操作流輸入
		byte[] msgBytes = msg.getBytes();
		//空位元組陣列
		byte[] emptyBytes = new byte[1024*10-msgBytes.length] ;
		//利用工具將檔案轉化為位元組陣列
		byte[] fileBytes = InputAndOutputUtil.readFile(filePath);
		//System.out.println("經過工具後,檔案變化為位元組的大小為:"+fileBytes.length);
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		baos.write(msgBytes);
		baos.write(emptyBytes);
		baos.write(fileBytes);
		byte[] allBytes = baos.toByteArray();
		//System.out.println("客戶端發出的資料總共位元組大小:"+allBytes.length);
		//通過輸出流輸出到伺服器中
		os.write(allBytes);
		os.flush();
	}
	private static void exitChat() throws IOException {
		//退出聊天
		System.out.println("謝謝使用,再見");
		String msg = clientName+ ":" + "null" + ":" + "null" +":"+ Config.MSG_EXIT;
		os.write(msg.getBytes());
		//關閉 
	}
	private static void onlineList() throws IOException {
		System.out.println("******當前線上的好友有******");
		String msg = clientName + ":" + "null" + ":" + "null" + ":" +Config.MSG_ONLINELIST; 
		os.write(msg.getBytes());
	}
	@SuppressWarnings("static-access")
	private static void publicChat() throws IOException {
		System.out.println("----------------歡迎您公聊模式--------------");
		System.out.println("請輸入您要發的訊息:");
		String msg = sc.next();
		//沒有傳送者用null來代替
		//訊息格式: 傳送者:接受者:所發信息:訊息型別
		msg = clientName + ":"+"null"+":" + msg + ":" + config.MSG_PUBLICCHAT;
		os.write(msg.getBytes());
	}
	
	@SuppressWarnings("static-access")
	private static void privateChat() throws IOException {
		System.out.println("----------------歡迎您進入私聊模式--------------");
		//訊息個數:接受者+傳送訊息+訊息型別
		System.out.println("請輸入接受者的姓名:");
		String receiver = sc.nextLine();
		System.out.println("請輸入您要給他(她)話:");
		String msg = sc.next();
		//訊息格式: 傳送者:接受者:所發信息:訊息型別
		msg = clientName + ":" + receiver +":"+ msg +":"+ config.MSG_PRIVATECHAT ;
		os.write(msg.getBytes());
	}
}


package org.westos.client;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import org.westos.client.Config.Config;
import org.westos.util.InputAndOutputUtil;

/**
 * 客戶端讀取輸入流的執行緒
 * @author 虎
 *
 */
public class ClientReadThread extends Thread{

	private InputStream is;
	public ClientReadThread(InputStream is) {
		super();
		this.is = is;
	}

	
	@Override
	public void run() {

			try {
				while(true) {
				byte[] bytes = new byte[1024*10];
				int len = is.read(bytes);
				String infomation = new String(bytes, 0, len);
				String[] info = infomation.split(":");
				String clientName = info[0];
				String receiver = info[1];
				String msg = info[2];
				int infoType =Integer.parseInt(info[3]);
				//私聊模式
				//System.out.println(infomation);
				if(infoType == (Config.MSG_PRIVATECHAT)) {
					System.out.println(clientName+"@你說:"+msg);
				}
				else if(infoType == (Config.MSG_PUBLICCHAT)) {
					System.out.println(clientName+"對大家說:"+msg);
				}
				else if (infoType == Config.MSG_ONLINE) {
					System.out.println(clientName+msg);
				}
				else if(infoType == (Config.MSG_ONLINELIST)) {
					System.out.println(msg);
				}
				else if(infoType == Config.MSG_EXIT) {
					System.out.println(msg);
				}
				else if(infoType == Config.MSG_SEBDFILE) {
					String[] file = msg.split("#");
					String fileName = file[0];
					long fileLength = Long.parseLong(file[1]);
					System.out.println(clientName+"給您發來一個檔案:"+ fileName + "檔案大小為:"+fileLength);
					byte[] memoryBytes = new byte[1024];
					int memoryLength = 0;
					//int len1 = infomation.getBytes().length;
					//不斷的去讀取
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
					int len1 = infomation.getBytes().length;
					//System.out.println("讀取的訊息的長度:"+len1);
					while (true) {
						int len2= is.read(memoryBytes);
						baos.write(memoryBytes, 0, len2);
						//實際長度
						memoryLength += len2;
						//判斷截止標記
						//System.out.println(memoryLength);
						//System.out.println(fileLength);  31879
						if (fileLength == memoryLength) {
							break;
						}
						//System.out.println("ha2");
					}
					//System.out.println("最終的長度:"+memoryLength);
					byte[] fileBytes = baos.toByteArray();
					//呼叫工具
					boolean flag = InputAndOutputUtil.writeFile("D:/"+fileName, fileBytes);
					//針對不同情況進行判斷
					//System.out.println("ha3");
					if (flag) {
						System.out.println("檔案接受成功!儲存在D:/"+fileName+"中,請前往檢視");
						break;
					}
					else {
						System.out.println("檔案接受失敗!");
					}
				}
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		
	}

}
package org.westos.server;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;

/**
 * 
 * @author 代虎
 *
 */
public class Server {

	private static ServerSocket ss;
	private static Socket s;
	private static InputStream is;
	private static OutputStream os;
	static HashMap<String, Socket> map = new HashMap<String, Socket>() ;
	public static void main(String[] args) {
		int num = 1; 
		
			try {
				ss = new ServerSocket(9999);
				System.out.println("伺服器已啟動...");
				while (true) {
				s = ss.accept();
				is = s.getInputStream();
				os = s.getOutputStream();
				System.out.println("第"+ (num++)+"個客戶端接入");
				new SaveClientThread(s, map).start();;
				
				}
				} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
}

package org.westos.server;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.HashMap;

import org.westos.server.config.Config;

/**
 * 存取客戶的資訊
 * @author 代虎
 *
 */
public class SaveClientThread extends Thread{

	private Socket s;
	private InputStream is;
	private OutputStream os;
	private String clientName;
	private Config config;
	private HashMap<String, Socket> map = new HashMap<String,Socket>();
	
	public SaveClientThread(Socket s, HashMap<String, Socket> map) {
		this.s = s;
		this.map = map;
	}

	@Override
	public void run() {
		while(true) {
			byte[] name = new byte[1024];
			try {
				is = s.getInputStream();
				os = s.getOutputStream();
				int len = is.read(name);
				clientName = new String(name, 0, len);
				if(map.containsKey(clientName)) {
					os.write("no".getBytes());
				}
				else {
					map.put(clientName, s);
					os.write("yes".getBytes());
					System.out.println("當前線上好友:");
					for(String client:map.keySet()) {
						System.out.println(client);
					}
					break;
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
			//給客戶端反饋上線的訊息
			for(String userName:map.keySet()) {
				//本人上線不需要踢提醒自己
				if(userName.equals(clientName)) {
					continue;
				}
				else {
					Socket socket = map.get(userName);
					String msg = clientName+":"+ userName +":"+ "上線了" +":"+ Config.MSG_ONLINE;
					try {
						socket.getOutputStream().write(msg.getBytes());
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
				//System.out.println("傳送成功");
			}
		new ServerTransmitThread(s, map).start();;
		
	}
}
package org.westos.server;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.HashMap;

import org.westos.server.config.Config;

public class ServerTransmitThread extends Thread{
	private OutputStream os;
	private InputStream is;
	private Socket s;
	private  HashMap<String, Socket> map = new HashMap<String, Socket>() ;
	
	public ServerTransmitThread(Socket s, HashMap<String, Socket> map) {
		super();
		this.s = s;
		this.map = map;
	}
	@SuppressWarnings("unused")
	@Override
	public void run() {
		try {
			is = s.getInputStream();
			while(true) {
				//先定義50kb
				byte[] bytes = new byte[1024*10];
				int len= is.read(bytes);
				String infomation = new String(bytes, 0, len);
				String[] info = infomation.split(":");
				String sendName = info[0];
				String receiver = info[1];
				String context = info[2];
				int infoType =Integer.parseInt(info[3]);
				//公聊
				if(infoType == (Config.MSG_PRIVATECHAT)) {
					Socket socket = map.get(receiver);
					socket.getOutputStream().write(infomation.getBytes());
				}
				//私聊
				else if(infoType == (Config.MSG_PUBLICCHAT)) {
					for(String str:map.keySet()) {
						Socket socket = map.get(str);
						socket.getOutputStream().write(infomation.getBytes());
					}
				}
				//線上列表
				else if(infoType == Config.MSG_ONLINELIST) {
					Socket socket = map.get(sendName);
					int num = 1;
					StringBuffer sb = new StringBuffer();
					for(String str:map.keySet()) {
						sb.append(num++).append("、").append(str).append("\n");
					}
					String msg  = sendName +":"+ receiver+":" + (sb.toString())+":" + Config.MSG_ONLINELIST;
					Socket socket2 = map.get(sendName);
					socket2.getOutputStream().write(msg.getBytes());
				}
				//退出聊天室
				else if (infoType == Config.MSG_EXIT) {
					//在map中移除該使用者
					map.remove(sendName);
					String msg = sendName+"下線了";
					for(String str:map.keySet()) {
						msg = "null"+ ":"+ str + ":" + msg + ":" + infoType;
						Socket socket = map.get(str);
						socket.getOutputStream().write(msg.getBytes());
					}
				}
				//傳送檔案
				else if (infoType == Config.MSG_SEBDFILE) {
					//System.out.println("傳送到伺服器端檔案總共位元組大小"+infomation.getBytes().length);
					Socket socket = map.get(receiver);
					String[] file = context.split("#");
					String fileName = file[0];
					long fileLength = Long.parseLong(file[1]);
					String msg = sendName + ":" + receiver +":" + context+ ":" + Config.MSG_SEBDFILE+":";
					byte[] msgBytes = msg.getBytes();
					byte[] emptyBytes = new byte[1024*10-msgBytes.length] ;
					//讀取檔案
					byte[] memoryBytes = new byte[1024];
					int memoryLength = 0;
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
					//System.out.println("讀取開始了嗎?");
					//不斷的去讀取
					while (true) {
						//System.out.println("1讀取開始了");
						int len2= is.read(memoryBytes);
						//System.out.println("2到這了嗎");
						baos.write(memoryBytes, 0, len2);
						//baos.flush();
						//實際長度
						memoryLength += len2;
						//System.out.println("3到這了嗎");
						//判斷截止標記
						//System.out.println("1:"+memoryLength);
						//System.out.println("2:"+len2);
						if (fileLength == memoryLength) {
							break;
						}
						//System.out.println("3");
					}
					byte[] fileBytes = baos.toByteArray();
					baos.reset();
					//重置,繼續使用該輸出流物件寫資料到記憶體中
					//System.out.println("4");
					baos.write(msgBytes);
					baos.write(emptyBytes);
					baos.write(fileBytes);
					//記憶體中已經有這些資料,需要拼接成一個大的字元陣列發過去
					byte[] allBytes = baos.toByteArray();
					//System.out.println("傳送的長度:"+allBytes.length);
					socket.getOutputStream().write(allBytes);
				}
			}
		} catch (IOException e) {
		//	e.printStackTrace();
		}
	}

}

工具包:
package org.westos.server.config;
/**
 * 常量(相當於客戶端與伺服器之間的人協議一樣)
 * @author 代虎
 *
 */
public class Config {

	public static final int MSG_PRIVATECHAT = 100;//私聊
	public static final int MSG_PUBLICCHAT = 200;//公聊
	public static final int MSG_ONLINE = 300;//上線
	public static final int MSG_ONLINELIST = 400;//線上列表
	public static final int MSG_EXIT = 500; //退出
	public static final int MSG_SEBDFILE= 600;//傳送檔案
	public static final int MSG_DOWNLOAD= 700;//下載檔案
	
}

package org.westos.util;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class InputAndOutputUtil {
	public static byte[] readFile(String path) {
		File file = new File(path);
		// 陣列用來儲存讀取的資料 相當於水池
		byte datas[] = null;
		if (!file.exists()) {
			datas = null;
		} else {
			try {
				// 位元組陣列輸出流 用來往記憶體中寫位元組陣列 可以用來拼接位元組陣列
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				// 建立檔案輸入流
				FileInputStream fis = new FileInputStream(file);
				// 用來儲存每次讀的資料 相當水瓢(每次讀1024位元組 但是不一定每次能讀這麼多 實際讀取的長度用len儲存)
				byte data[] = new byte[1024 * 1024];
				// 用來儲存每次讀取的位元組大小
				int len = 0;
				// 不斷的讀取 直到資料讀完
				while ((len = fis.read(data)) > 0) {
					// 把每次讀入的資料 存放在位元組陣列流的記憶體中
					baos.write(data, 0, len);
				}
				// 把位元組陣列流中的資料轉為位元組陣列
				datas = baos.toByteArray();
				baos.flush();
				baos.close();
				// 關閉流
				fis.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return datas;
	}

	public static boolean writeFile(String path, byte datas[]) {
		try {
			File file = new File(path);
			FileOutputStream fos = new FileOutputStream(file);
			fos.write(datas);
			// 傾倒關閉
			fos.flush();
			fos.close();
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}
}

package org.westos.util;

import java.util.Scanner;

public class InputUtil {
	
	//一直要我們錄入整數為止
	public static int inputIntType(Scanner sc) {
		int choose = 0;
		while (true) {
			try {
				//錄入使用者輸入的整數
				choose = sc.nextInt();
				break;
			} catch (Exception e) {
				sc = new Scanner(System.in);
				System.out.println("輸入的型別不正確,請重新輸入:");
			}
		}
		return choose;
	}
}