IM即時通訊(三) 多客戶端文字傳輸之聊天功能
阿新 • • 發佈:2018-12-28
主要內容:
多執行緒的應用
讀寫分離思想
伺服器轉發
伺服器轉發是什麼呢?
可以假想伺服器是一個大型的資料中心,按照一定的規則,將資訊送到你想送去的地方。
而且,這裡的規則是由開發者自己定義的。
原始碼例項:
客戶端
package me.mxzf;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
/**
*
* @Title: Client
* @Dscription : 客戶端
* @author Deleter
* @date 2017年3月12日 下午2:29:00
* @version 1.0
*/
public class Client {
public static void main(String[] args) throws UnknownHostException,
IOException {
// 建立Socket例項
Socket socket = new Socket(InetAddress.getLocalHost(), 1234);
// 包裝寫
new ThreadWriter(socket).start();
// 包裝讀
new ThreadReader(socket).start();
}
}
客戶端讀執行緒
package me.mxzf;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
/**
*
* @Title: ThreadReader
* @Dscription: 讀執行緒
* @author Deleter
* @date 2017年3月12日 下午2:32:46
* @version 1.0
*/
public class ThreadReader extends Thread {
private Socket socket;
private DataInputStream dis;
private String content;
public ThreadReader(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 用DataInputStream包裝socket的輸入流
dis = new DataInputStream(this.socket.getInputStream());
while (true) {
// 一直阻塞,直到有訊息到來,進入迴圈體
while ((content = dis.readUTF()) != null) {
// 列印接收到的訊息
System.out.println(content);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客戶端寫執行緒
package me.mxzf;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
/**
*
* @Title: ThreadWriter
* @Dscription: 寫執行緒
* @author Deleter
* @date 2017年3月12日 下午2:33:25
* @version 1.0
*/
public class ThreadWriter extends Thread {
private Socket socket;
private DataOutputStream dos;
public ThreadWriter(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 用DataOutputStream包裝socket的輸出流
dos = new DataOutputStream(socket.getOutputStream());
while (true) {
// 等待3後
Thread.sleep(3000);
// 向伺服器傳送"Hello World!"
dos.writeUTF("Client:Hello World!");
// 重新整理緩衝區,防止粘包
dos.flush();
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
伺服器
package me.mxzf;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashSet;
/**
*
* @Title: Server
* @Dscription: 伺服器
* @author Deleter
* @date 2017年3月12日 下午2:28:09
* @version 1.0
*/
public class Server {
// 建立靜態的集合來存放"客戶通道"
public static HashSet<ServerThread> lists = new HashSet<>();
public static void main(String[] args) throws IOException {
// 建立ServerSocket例項
ServerSocket server = new ServerSocket(1234);
// 在迴圈的外部定義變數,節約記憶體空間
Socket socket;
ServerThread serverThread;
while (true) {
// 阻塞直到有客戶端接入
socket = server.accept();
// 包裝Socket
serverThread = new ServerThread(socket, lists);
serverThread.start();
// 將當前接入的"客戶通道"儲存在集合中
lists.add(serverThread);
}
}
/**
* 伺服器轉發
*
* 這種遍歷的方式,效率極低,不介意使用
*
* 但是為了初學者更好的理解,我還是用了這種方式
*/
public void forword(String serverSocketName, String content) {
try {
// 遍歷集合
for (ServerThread serverThread : lists) {
// 如果該ServerThread的toString與即將傳送的目的地名稱一致時
if (serverThread.toString().equals(serverSocketName)) {
// 獲取該ServerThread的輸出流,向其中寫出資訊
serverThread.getDataOutputStream().writeUTF(content);
// 同時,重新整理該緩衝區
serverThread.getDataOutputStream().flush();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
伺服器讀寫執行緒
package me.mxzf;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.HashSet;
/**
*
* @Title: ServerThread
* @Dscription: 伺服器讀寫執行緒
* @author Deleter
* @date 2017年3月12日 下午2:31:57
* @version 1.0
*/
public class ServerThread extends Thread {
private Socket socket;
private HashSet<ServerThread> list;
private DataInputStream dis;
private DataOutputStream dos;
public ServerThread(Socket socket, HashSet<ServerThread> list) {
this.socket = socket;
this.list = list;
}
@Override
public void run() {
try {
// 用DataInputStream包裝socket的輸入流
dis = new DataInputStream(socket.getInputStream());
// 用DataOutputStream包裝socket的輸出流
dos = new DataOutputStream(socket.getOutputStream());
// 定義區域性變數
String content;
while (true) {
// 一直阻塞,直到有訊息到來,進入迴圈體
while ((content = dis.readUTF()) != null) {
// 列印接收到的訊息
System.out.println(content);
// 伺服器反饋訊息
dos.writeUTF("Server:ok");
// 重新整理緩衝區,防止粘包
dos.flush();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/*
* 重寫toString方法
*
* @see java.lang.Thread#toString()
*/
@Override
public String toString() {
return "ServerThread [socket=" + socket + "]";
}
/*
* 獲取輸出流
*/
public DataOutputStream getDataOutputStream() {
return dos;
}
}
注意
1、客戶端先進行寫操作(重要)
2、必須先啟動服務端
擴充套件學習
可以自行擴充套件,新增自定義的資訊格式,或者加入Scanner輸入流等等