image.png
image.png
image.png
image.png
服務(wù)端
public class Server
{
private static final int SERVER_PORT = 30000;
// 使用CrazyitMap對象來保存每個客戶名字和對應(yīng)輸出流之間的對應(yīng)關(guān)系。
public static CrazyitMap<String , PrintStream> clients
= new CrazyitMap<>();
public void init()
{
try(
// 建立監(jiān)聽的ServerSocket
ServerSocket ss = new ServerSocket(SERVER_PORT))
{
// 采用死循環(huán)來不斷接受來自客戶端的請求
while(true)
{
Socket socket = ss.accept();
new ServerThread(socket).start();
}
}
// 如果拋出異常
catch (IOException ex)
{
System.out.println("服務(wù)器啟動失敗,是否端口"
+ SERVER_PORT + "已被占用?");
}
}
public static void main(String[] args)
{
Server server = new Server();
server.init();
}
}
服務(wù)端實(shí)現(xiàn)
public class ServerThread extends Thread
{
private Socket socket;
BufferedReader br = null;
PrintStream ps = null;
// 定義一個構(gòu)造器,用于接收一個Socket來創(chuàng)建ServerThread線程
public ServerThread(Socket socket)
{
this.socket = socket;
}
@Override
public void run()
{
try
{
// 獲取該Socket對應(yīng)的輸入流
br = new BufferedReader(new InputStreamReader(socket
.getInputStream()));
// 獲取該Socket對應(yīng)的輸出流
ps = new PrintStream(socket.getOutputStream());
String line = null;
while((line = br.readLine())!= null)
{
// 如果讀到的行以CrazyitProtocol.USER_ROUND開始,并以其結(jié)束,
// 可以確定讀到的是用戶登錄的用戶名
if (line.startsWith(CrazyitProtocol.USER_ROUND)
&& line.endsWith(CrazyitProtocol.USER_ROUND))
{
// 得到真實(shí)消息
String userName = getRealMsg(line);
// 如果用戶名重復(fù)
if (Server.clients.map.containsKey(userName))
{
System.out.println("重復(fù)");
ps.println(CrazyitProtocol.NAME_REP);
}
else
{
System.out.println("成功");
ps.println(CrazyitProtocol.LOGIN_SUCCESS);
Server.clients.put(userName , ps);
}
}
// 如果讀到的行以CrazyitProtocol.PRIVATE_ROUND開始,并以其結(jié)束,
// 可以確定是私聊信息,私聊信息只向特定的輸出流發(fā)送
else if (line.startsWith(CrazyitProtocol.PRIVATE_ROUND)
&& line.endsWith(CrazyitProtocol.PRIVATE_ROUND))
{
// 得到真實(shí)消息
String userAndMsg = getRealMsg(line);
// 以SPLIT_SIGN分割字符串,前半是私聊用戶,后半是聊天信息
String user = userAndMsg.split(CrazyitProtocol.SPLIT_SIGN)[0];
String msg = userAndMsg.split(CrazyitProtocol.SPLIT_SIGN)[1];
// 獲取私聊用戶對應(yīng)的輸出流,并發(fā)送私聊信息
Server.clients.map.get(user).println(Server.clients
.getKeyByValue(ps) + "悄悄地對你說:" + msg);
}
// 公聊要向每個Socket發(fā)送
else
{
// 得到真實(shí)消息
String msg = getRealMsg(line);
// 遍歷clients中的每個輸出流
for (PrintStream clientPs : Server.clients.valueSet())
{
clientPs.println(Server.clients.getKeyByValue(ps)
+ "說:" + msg);
}
}
}
}
// 捕捉到異常后,表明該Socket對應(yīng)的客戶端已經(jīng)出現(xiàn)了問題
// 所以程序?qū)⑵鋵?yīng)的輸出流從Map中刪除
catch (IOException e)
{
Server.clients.removeByValue(ps);
System.out.println("剩余在線人數(shù)" + Server.clients.map.size());
// 關(guān)閉網(wǎng)絡(luò)、IO資源
try
{
if (br != null)
{
br.close();
}
if (ps != null)
{
ps.close();
}
if (socket != null)
{
socket.close();
}
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
}
// 將讀到的內(nèi)容去掉前后的協(xié)議字符,恢復(fù)成真實(shí)數(shù)據(jù)
private String getRealMsg(String line)
{
return line.substring(CrazyitProtocol.PROTOCOL_LEN
, line.length() - CrazyitProtocol.PROTOCOL_LEN);
}
}
客戶端
public class Client
{
private static final int SERVER_PORT = 30000;
private Socket socket;
private PrintStream ps;
private BufferedReader brServer;
private BufferedReader keyIn;
public void init()
{
try
{
// 初始化代表鍵盤的輸入流
keyIn = new BufferedReader(
new InputStreamReader(System.in));
// 連接到服務(wù)器
socket = new Socket("127.0.0.1", SERVER_PORT);
// 獲取該Socket對應(yīng)的輸入流和輸出流
ps = new PrintStream(socket.getOutputStream());
brServer = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
String tip = "";
// 采用循環(huán)不斷地彈出對話框要求輸入用戶名
while(true)
{
String userName = JOptionPane.showInputDialog(tip
+ "輸入用戶名"); //①
// 將用戶輸入的用戶名的前后增加協(xié)議字符串后發(fā)送
ps.println(CrazyitProtocol.USER_ROUND + userName
+ CrazyitProtocol.USER_ROUND);
// 讀取服務(wù)器的響應(yīng)
String result = brServer.readLine();
// 如果用戶重復(fù),開始下次循環(huán)
if (result.equals(CrazyitProtocol.NAME_REP))
{
tip = "用戶名重復(fù)!請重新";
continue;
}
// 如果服務(wù)器返回登錄成功,結(jié)束循環(huán)
if (result.equals(CrazyitProtocol.LOGIN_SUCCESS))
{
break;
}
}
}
// 捕捉到異常,關(guān)閉網(wǎng)絡(luò)資源,并退出該程序
catch (UnknownHostException ex)
{
System.out.println("找不到遠(yuǎn)程服務(wù)器,請確定服務(wù)器已經(jīng)啟動!");
closeRs();
System.exit(1);
}
catch (IOException ex)
{
System.out.println("網(wǎng)絡(luò)異常!請重新登錄!");
closeRs();
System.exit(1);
}
// 以該Socket對應(yīng)的輸入流啟動ClientThread線程
new ClientThread(brServer).start();
}
// 定義一個讀取鍵盤輸出,并向網(wǎng)絡(luò)發(fā)送的方法
private void readAndSend()
{
try
{
// 不斷讀取鍵盤輸入
String line = null;
while((line = keyIn.readLine()) != null)
{
// 如果發(fā)送的信息中有冒號,且以//開頭,則認(rèn)為想發(fā)送私聊信息
if (line.indexOf(":") > 0 && line.startsWith("http://"))
{
line = line.substring(2);
ps.println(CrazyitProtocol.PRIVATE_ROUND +
line.split(":")[0] + CrazyitProtocol.SPLIT_SIGN
+ line.split(":")[1] + CrazyitProtocol.PRIVATE_ROUND);
}
else
{
ps.println(CrazyitProtocol.MSG_ROUND + line
+ CrazyitProtocol.MSG_ROUND);
}
}
}
// 捕捉到異常,關(guān)閉網(wǎng)絡(luò)資源,并退出該程序
catch (IOException ex)
{
System.out.println("網(wǎng)絡(luò)通信異常!請重新登錄!");
closeRs();
System.exit(1);
}
}
// 關(guān)閉Socket、輸入流、輸出流的方法
private void closeRs()
{
try
{
if (keyIn != null)
{
ps.close();
}
if (brServer != null)
{
ps.close();
}
if (ps != null)
{
ps.close();
}
if (socket != null)
{
keyIn.close();
}
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
public static void main(String[] args)
{
Client client = new Client();
client.init();
client.readAndSend();
}
}
客戶端實(shí)現(xiàn)
public class Client
{
private static final int SERVER_PORT = 30000;
private Socket socket;
private PrintStream ps;
private BufferedReader brServer;
private BufferedReader keyIn;
public void init()
{
try
{
// 初始化代表鍵盤的輸入流
keyIn = new BufferedReader(
new InputStreamReader(System.in));
// 連接到服務(wù)器
socket = new Socket("127.0.0.1", SERVER_PORT);
// 獲取該Socket對應(yīng)的輸入流和輸出流
ps = new PrintStream(socket.getOutputStream());
brServer = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
String tip = "";
// 采用循環(huán)不斷地彈出對話框要求輸入用戶名
while(true)
{
String userName = JOptionPane.showInputDialog(tip
+ "輸入用戶名"); //①
// 將用戶輸入的用戶名的前后增加協(xié)議字符串后發(fā)送
ps.println(CrazyitProtocol.USER_ROUND + userName
+ CrazyitProtocol.USER_ROUND);
// 讀取服務(wù)器的響應(yīng)
String result = brServer.readLine();
// 如果用戶重復(fù),開始下次循環(huán)
if (result.equals(CrazyitProtocol.NAME_REP))
{
tip = "用戶名重復(fù)!請重新";
continue;
}
// 如果服務(wù)器返回登錄成功,結(jié)束循環(huán)
if (result.equals(CrazyitProtocol.LOGIN_SUCCESS))
{
break;
}
}
}
// 捕捉到異常,關(guān)閉網(wǎng)絡(luò)資源,并退出該程序
catch (UnknownHostException ex)
{
System.out.println("找不到遠(yuǎn)程服務(wù)器,請確定服務(wù)器已經(jīng)啟動!");
closeRs();
System.exit(1);
}
catch (IOException ex)
{
System.out.println("網(wǎng)絡(luò)異常!請重新登錄!");
closeRs();
System.exit(1);
}
// 以該Socket對應(yīng)的輸入流啟動ClientThread線程
new ClientThread(brServer).start();
}
// 定義一個讀取鍵盤輸出,并向網(wǎng)絡(luò)發(fā)送的方法
private void readAndSend()
{
try
{
// 不斷讀取鍵盤輸入
String line = null;
while((line = keyIn.readLine()) != null)
{
// 如果發(fā)送的信息中有冒號,且以//開頭,則認(rèn)為想發(fā)送私聊信息
if (line.indexOf(":") > 0 && line.startsWith("http://"))
{
line = line.substring(2);
ps.println(CrazyitProtocol.PRIVATE_ROUND +
line.split(":")[0] + CrazyitProtocol.SPLIT_SIGN
+ line.split(":")[1] + CrazyitProtocol.PRIVATE_ROUND);
}
else
{
ps.println(CrazyitProtocol.MSG_ROUND + line
+ CrazyitProtocol.MSG_ROUND);
}
}
}
// 捕捉到異常,關(guān)閉網(wǎng)絡(luò)資源,并退出該程序
catch (IOException ex)
{
System.out.println("網(wǎng)絡(luò)通信異常!請重新登錄!");
closeRs();
System.exit(1);
}
}
// 關(guān)閉Socket、輸入流、輸出流的方法
private void closeRs()
{
try
{
if (keyIn != null)
{
ps.close();
}
if (brServer != null)
{
ps.close();
}
if (ps != null)
{
ps.close();
}
if (socket != null)
{
keyIn.close();
}
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
public static void main(String[] args)
{
Client client = new Client();
client.init();
client.readAndSend();
}
}
服務(wù)端封裝的 Map,為聊天室提供存儲用戶的功能
// 通過組合HashMap對象來實(shí)現(xiàn)CrazyitMap,CrazyitMap要求value也不可重復(fù)
public class CrazyitMap<K,V>
{
// 創(chuàng)建一個線程安全的HashMap
public Map<K ,V> map = Collections.synchronizedMap(new HashMap<K,V>());
// 根據(jù)value來刪除指定項(xiàng)
public synchronized void removeByValue(Object value)
{
for (Object key : map.keySet())
{
if (map.get(key) == value)
{
map.remove(key);
break;
}
}
}
// 獲取所有value組成的Set集合
public synchronized Set<V> valueSet()
{
Set<V> result = new HashSet<V>();
// 將map中所有value添加到result集合中
map.forEach((key , value) -> result.add(value));
return result;
}
// 根據(jù)value查找key。
public synchronized K getKeyByValue(V val)
{
// 遍歷所有key組成的集合
for (K key : map.keySet())
{
// 如果指定key對應(yīng)的value與被搜索的value相同,則返回對應(yīng)的key
if (map.get(key) == val || map.get(key).equals(val))
{
return key;
}
}
return null;
}
// 實(shí)現(xiàn)put()方法,該方法不允許value重復(fù)
public synchronized V put(K key,V value)
{
// 遍歷所有value組成的集合
for (V val : valueSet() )
{
// 如果某個value與試圖放入集合的value相同
// 則拋出一個RuntimeException異常
if (val.equals(value)
&& val.hashCode()== value.hashCode())
{
throw new RuntimeException("MyMap實(shí)例中不允許有重復(fù)value!");
}
}
return map.put(key , value);
}
}
協(xié)議字符
public interface CrazyitProtocol
{
// 定義協(xié)議字符串的長度
int PROTOCOL_LEN = 2;
// 下面是一些協(xié)議字符串,服務(wù)器和客戶端交換的信息
// 都應(yīng)該在前、后添加這種特殊字符串。
String MSG_ROUND = "§γ";
String USER_ROUND = "∏∑";
String LOGIN_SUCCESS = "1";
String NAME_REP = "-1";
String PRIVATE_ROUND = "★【";
String SPLIT_SIGN = "※";
}