文章目錄
- 前言
-
- 用戶端
- 服務端
- 服務端的源代碼:
- 實作效果
前言
在學TCP的時候就做了一個多使用者登入并得到響應的一個小項目,這個聊天室也是基于TCP通信原理的互動,在服務端對接收的Socket對象封裝成線程類,開啟多線程實作多使用者同時接發資料。
不同的點是:
用戶端
對接收和發送的代碼進行了封裝,封裝成獨立出來的兩個線程類,就可以實作讀寫分離,不再是請求響應式的交流。
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.println("請輸入使用者名");
String name = br.readLine();
//建立連接配接:socket建立用戶端
Socket client = new Socket("localhost",8888);
//用戶端發送資訊
new Thread(new ClientSend(client,name)).start(); //導入發送線程類
//擷取消息
new Thread(new ClientReceive(client)).start(); //導入接收線程類
}
}
上面導入name變量之後在建立線程體時會先把name導入,這樣在服務端就可以接收到這個線程對象的name屬性,友善後面群發消息可以調用說話的用戶端的名字。
下面就是用戶端的接收和發送的兩個線程類。
接收:
public class ClientReceive implements Runnable {
DataInputStream dis ;
Socket client;
boolean isRunning = true;
public ClientReceive(Socket client){
this.client = client;
try {
dis = new DataInputStream(client.getInputStream());
} catch (IOException e) {
release();
}
}
public void run() {
while(isRunning) {
//接收消息
String datas;
try {
datas = dis.readUTF();
System.out.println(datas);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//釋放資源
private void release() {
this.isRunning = false;
try {
dis.close();
client.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
發送類:
public class ClientSend implements Runnable {
BufferedReader console;
DataOutputStream dos;
Socket client;
boolean isRunning = true;
String name;
public ClientSend(Socket client,String name) {
this.name = name;
this.client = client;
console = new BufferedReader(new InputStreamReader(System.in));
try {
dos = new DataOutputStream(client.getOutputStream());
//發送名稱
dos.writeUTF(name);
dos.flush();
} catch (IOException e) {
release();
}
}
public void run() { //線程體
while(isRunning) {
//用戶端發送資訊
String msg;
try {
msg = console.readLine();
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//釋放資源
private void release() {
this.isRunning = false;
try {
dos.close();
client.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
服務端
1.伺服器是請求響應式的,是以不需要用到上面建立的接收發送線程類。
2.對Socket進行封裝成線程類,在類内加入輸入輸出流和發送接收消息的兩個類,然後在run線程體中執行。
static class Channel implements Runnable{
private DataInputStream dis = null;
private DataOutputStream dos= null;
private Socket client;
private boolean isRunning = true;
private String name;
public Channel(Socket client) throws IOException {
this.client = client;
try {
dis = new DataInputStream(client.getInputStream());
dos = new DataOutputStream(client.getOutputStream());
this.name = receive();
this.send("歡迎來到聊天室");
this.sendOther(this.name+"來到了聊天室",true);
} catch (IOException e) {
release();
}
}
//接收消息
private String receive(){
String msg ="";
try {
msg = dis.readUTF();
} catch (IOException e) {
release();
}
return msg;
}
//發送消息
private void send(String msg) {
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
release();
}
}
//線程體
public void run() {
while(isRunning) {
String msg = receive();
//send(msg); 自己不用發給自己了
sendOther(msg,false); //發給其他人
}
}
}
3.要想把一個用戶端發送的資料發送給其他流的用戶端,需要先建立一個容器,存放每一個建立立的socket。
static private CopyOnWriteArrayList all = new CopyOnWriteArrayList();
//比較安全,發生并發時資料不會混亂
ServerSocket socket = new ServerSocket(8888);
//阻塞式的連接配接
while(true) {
Socket client = socket.accept();
System.out.println("用戶端建立了連接配接");
Channel c = new Channel(client);
all.add(c); //管理所有的成員
//利用多線程實作多個用戶端同時運作
new Thread(c).start();
}
然後線上程對象中加入sendOther的關鍵方法。這裡面還實作了私聊的功能。
//發送消息給其他人,實作群聊
//私聊約定資料格式:@xxx:msg
private void sendOther(String msg,boolean isSys) {
if(msg.startsWith("@")) { //判斷是不是私聊的語句
String name = msg.substring(1,msg.indexOf(":"));
msg = msg.substring(msg.indexOf(":")+1); //分離名稱和資料
for(Channel other:all) {
if(other.name.equals(name)) { //找到目标
other.send(this.name+"悄悄地對您說:"+msg);
}
}
}else { //不是私聊那就是群聊了
for(Channel other:all) {
if(other == this) { //自己不發送給自己
continue;
}
if(!isSys)
other.send(this.name+"對所有人說:"+msg); //群聊消息
else other.send(msg); //系統消息
}
}
}
服務端的源代碼:
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 線上聊天室:服務端
* 目标 加入容器實作群聊 私聊在sendOther裡面
*
* @author Little Black
*
*/
public class Chat {
static private CopyOnWriteArrayList<Channel> all = new CopyOnWriteArrayList<Channel>();//比較安全,發生并發時資料不會混亂
public static void main(String[] args) throws IOException {
System.out.println("------server------");
//指定端口
ServerSocket socket = new ServerSocket(8888);
//阻塞式的連接配接
while(true) {
Socket client = socket.accept();
System.out.println("用戶端建立了連接配接");
Channel c = new Channel(client);
all.add(c); //管理所有的成員
//利用多線程實作多個用戶端同時運作
new Thread(c).start();
}
}
//一個客戶代表一個channel
static class Channel implements Runnable{
private DataInputStream dis = null;
private DataOutputStream dos= null;
private Socket client;
private boolean isRunning = true;
private String name;
public Channel(Socket client) throws IOException {
this.client = client;
try {
dis = new DataInputStream(client.getInputStream());
dos = new DataOutputStream(client.getOutputStream());
this.name = receive();
this.send("歡迎來到聊天室");
this.sendOther(this.name+"來到了聊天室",true);
} catch (IOException e) {
release();
}
}
//接收消息
private String receive(){
String msg ="";
try {
msg = dis.readUTF();
} catch (IOException e) {
release();
}
return msg;
}
//發送消息
private void send(String msg) {
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
release();
}
}
//發送消息給其他人,實作群聊
//私聊約定資料格式:@xxx:msg
private void sendOther(String msg,boolean isSys) {
if(msg.startsWith("@")) { //判斷是不是私聊的語句
String name = msg.substring(1,msg.indexOf(":"));
msg = msg.substring(msg.indexOf(":")+1); //分離名稱和資料
for(Channel other:all) {
if(other.name.equals(name)) { //找到目标
other.send(this.name+"悄悄地對您說:"+msg);
}
}
}else {
for(Channel other:all) {
if(other == this) { //自己不發送給自己
continue;
}
if(!isSys)
other.send(this.name+"對所有人說:"+msg); //群聊消息
else other.send(msg); //系統消息
}
}
}
//釋放資源
private void release() {
try {
dos.close();
dis.close();
isRunning = false;
all.remove(this);
sendOther(this.name+"離開了聊天室", true) ;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//線程體
public void run() {
while(isRunning) {
String msg = receive();
//send(msg); 自己不用發給自己了
sendOther(msg,false); //發給其他人
}
}
}
}