分析:
聊天室需要多個客戶端和一個服務端。
服務端負責轉發消息。
客戶端可以發送消息、接收消息。
消息分類:
群聊消息:發送除自己外所有人
私聊消息:只發送@的人
系統消息:根據情況分只發送個人和其他人
技術方面:
客戶端和服務端收發消息,需要使用IO流,封裝一個IOUtils工具類用來釋放資源。
客戶端需要同時收發消息,需要啟動發送和接收兩個消息,互不干擾
服務端需要接收每個客戶端消息和對多個客戶端發送消息,每連接上一個客戶端需要啟動一個線程,讓后面進來的客戶端不需要等待前面的客戶端退出后才能建立連接。
……
還是上代碼吧。
基礎版:
搭建結構,實現多個客戶端和服務端連接,保證服務端能正常轉發消息。
我們約定:
當服務端在初始化、發送、接收時出現異常時分別輸出:
------1------
------2------
------3------
當客戶端,初始化發送線程、初始化接收線程、發送、接收異常時分別輸出:
======1=====
======2=====
======3=====
======4=====
1、IO工具類
package com.xzlf.chat;
import java.io.Closeable;
import java.io.IOException;
/** * 工具類 * @author xzlf * */
public class IOUtils {
/** * 釋放資源 * @param closeables */
public static void close(Closeable...closeables) {
for (Closeable closeable : closeables) {
if(null != closeable) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
2、服務端
package com.xzlf.chat;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/** * 聊天室:服務器 * @author xzlf * */
public class TMultiChat {
public static void main(String[] args) throws IOException {
System.out.println("======server======");
// 1、指定端口創建服務端
ServerSocket server = new ServerSocket(8888);
while(true) {
// 2、每進來一個客戶端啟動一個線程
Socket socket = server.accept();
System.out.println("一個客戶端建立了連接");
new Thread(new Channel(socket)).start();
}
}
// 一個Channel 代表一個客戶端
static class Channel implements Runnable{
private Socket socket;
private DataInputStream dis;
private DataOutputStream dos;
private boolean isRuning;
public Channel(Socket socket) {
this.socket = socket;
this.isRuning = true;
try {
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());
} catch (IOException e) {
System.out.println("------1------");
this.release();
}
}
// 接收消息
private String receive() {
String msg = "";
try {
msg = dis.readUTF();
} catch (IOException e) {
System.out.println("------3------");
this.release();
}
return msg;
}
// 發送消息
private void send(String msg) {
try {
dos.writeUTF(msg);
} catch (IOException e) {
System.out.println("------2------");
this.release();
}
}
// 釋放資源
private void release() {
this.isRuning = false;
IOUtils.close(dis, dos, socket);
}
@Override
public void run() {
while(isRuning) {
String msg = this.receive();
if (!msg.equals("")) {
this.send(msg);
}
}
}
}
}
3、多線程封裝發送端
package com.xzlf.chat;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream.GetField;
import java.net.Socket;
/** * 利用多線程封裝發送端 * * @author xzlf * */
public class Send implements Runnable{
private Socket socket;
private DataOutputStream dos;
private BufferedReader console;
private boolean isRuning;
public Send(Socket socket) {
this.socket = socket;
this.isRuning = true;
try {
console = new BufferedReader(new InputStreamReader(System.in));
dos = new DataOutputStream(socket.getOutputStream());
} catch (IOException e) {
System.out.println("======1=====");
this.release();
}
}
// 從控制台獲取消息
private String getStrFromConsole() {
try {
return console.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
// 發送消息
public void send(String msg) {
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
e.printStackTrace();
System.out.println("======3=====");
this.release();
}
}
// 釋放資源
private void release() {
this.isRuning = false;
IOUtils.close(dos, console, socket);
}
@Override
public void run() {
while(isRuning) {
String msg = getStrFromConsole();
if (!msg.equals("")) {
this.send(msg);
}
}
}
}
4、多線程封裝接收端
package com.xzlf.chat;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
/** * 使用多線程封裝接收端 * @author xzlf * */
public class Receive implements Runnable {
private Socket socket;
private DataInputStream dis;
private boolean isRuning;
public Receive(Socket socket) {
this.socket = socket;
this.isRuning = true;
try {
dis = new DataInputStream(socket.getInputStream());
} catch (IOException e) {
System.out.println("======2=====");
this.release();
}
}
// 接收消息
public String receive() {
String msg = "";
try {
msg = dis.readUTF();
} catch (IOException e) {
System.out.println(e);
System.out.println("======4=====");
release();
}
return msg;
}
// 釋放資源
private void release() {
this.isRuning = false;
IOUtils.close(dis, socket);
}
@Override
public void run() {
while(isRuning) {
String msg = receive();
if(!msg.equals("")) {
System.out.println(msg);
}
}
}
}
5、客戶端
package com.xzlf.chat;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
/** * 聊天室:客戶端 * @author xzlf * */
public class TMultiClient {
public static void main(String[] args) throws UnknownHostException, IOException {
System.out.println("======client======");
// 1、指定ip + 端口 建立連接
Socket socket = new Socket("localhost", 8888);
// 2、客戶端收發消息
new Thread(new Send(socket)).start();
new Thread(new Receive(socket)).start();
}
}
運行服務端和客戶端:

先每個客戶端只能自己跟自己聊。
實現群聊:
1、加入容器(使用JUC包下的並發容器CopyOnWriteArrayList),並添加給其他用戶發送消息方法
添加容器:
public class Chat {
private static CopyOnWriteArrayList<Channel> all = new CopyOnWriteArrayList<Channel>();
public static void main(String[] args) throws IOException {
System.out.println("======server======");
// 1、指定端口創建服務端
ServerSocket server = new ServerSocket(8888);
while(true) {
// 2、每進來一個客戶端啟動一個線程
Socket socket = server.accept();
Channel c = new Channel(socket);
all.add(c);
System.out.println("一個客戶端建立了連接");
new Thread(c).start();
}
}
添加群發方法
// 群聊:發送消息給其他人
private void sendOthers(String msg, boolean isSys) {
for(Channel other : all) {
if(other == this) {
continue;
}
if (!isSys) {
// 群聊消息
other.send(this.name + "說:" + msg);
}else {
// 系統消息
other.send(msg);
}
}
}
2、在初始化發送端,寫入自己用戶名並在初始化就發送
客戶端啟動時,輸入用戶名
public class Client {
public static void main(String[] args) throws UnknownHostException, IOException {
System.out.println("======client======");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.print("請輸入用戶名:");
String name = br.readLine();
// 1、指定ip + 端口 建立連接
Socket socket = new Socket("localhost", 8888);
// 2、客戶端收發消息
new Thread(new Send(socket, name)).start();
new Thread(new Receive(socket)).start();
}
}
發送線程初始化時立馬發送用戶名:
public class Send implements Runnable{
private Socket socket;
private DataOutputStream dos;
private BufferedReader console;
private boolean isRuning;
private String name;
public Send(Socket socket, String name) {
this.socket = socket;
this.name = name;
try {
this.isRuning = true;
console = new BufferedReader(new InputStreamReader(System.in));
dos = new DataOutputStream(socket.getOutputStream());
// 發送用戶名
this.send(name);
} catch (IOException e) {
System.out.println("======1=====");
this.release();
}
}
服務端的channel類中初始化時立馬接收用戶名並保存
3、服務端(靜態內部類Channel類中)在初始化時立即獲取用戶名並給用戶發送歡迎信息同時給其他用戶發提示信息(系統消息)
static class Channel implements Runnable{
private Socket socket;
private DataInputStream dis;
private DataOutputStream dos;
private boolean isRuning;
private String name;
public Channel(Socket socket) {
this.socket = socket;
this.isRuning = true;
try {
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());
// 獲取用戶名
this.name = receive();
this.send("歡迎你的到來");
this.sendOthers(this.name + "來了xxx聊天室", true);
} catch (IOException e) {
System.out.println("------1------");
this.release();
}
}
4、用戶關閉線程,給其他用戶發送提示信息,提示用戶已離開
// 釋放資源
private void release() {
this.isRuning = false;
IOUtils.close(dis, dos, socket);
// 退出
all.remove(this);
sendOthers(this.name + "離開了聊天室。。。", true);
}
運行測試:

實現私聊:
通過判斷用戶輸入信息是否包含“@xxx:”確定是否為私聊,修改群發方法:
/** * 群聊:獲取自己的信息,發送消息給其他人 * 私聊:約定數據格式: @xxx:msg * @param msg * @param isSys */
private void sendOthers(String msg, boolean isSys) {
if(msg.startsWith("@")) {
// 私聊
int endIndex = msg.indexOf(":");
String targetName = msg.substring(1, endIndex);
String info = msg.substring(endIndex + 1);
for(Channel other : all) {
if(other.name.equals(targetName)) {
other.send(this.name + "悄悄對你說:" + info);
}
}
}else {
// 群聊
for(Channel other : all) {
if(other == this) {
continue;
}
if (!isSys) {
// 群聊消息
other.send(this.name + "說:" + msg);
}else {
// 系統消息
other.send(msg);
}
}
}
}
好了,現在已經實現了私聊。
運行測試一下:

需要完整代碼的可以在下方留言。
