网络编程--聊天室
網絡編程-聊天室
網絡編程是指編寫運行在多個設備的計算機程序,這些設備通過網絡連接起來
主要使用:
java.net包:JavaSE的API包含有類和接口,它們提供低層次的通信細節。
java.net包中提供了兩種常見的網絡協議的支持:
TCP:TCP是傳輸控制協議的縮寫,它保障了兩個應用程序之間的可靠通信。通常用于互聯網協議,被稱TCP/IP。
UDP:UDP是用戶數據報協議的縮寫,一個無連接的協議。提供了應用程序之間要發送的數據的數據報。
Socket編程:套接字使用TCP提供兩臺計算機之間的通信機制。
客戶端程序創建一個套接字,并嘗試連接服務器的套接字。
當連接建立時,服務器會創建一個 Socket 對象。客戶端和服務器現在可以通過對 Socket 對象的寫入和讀取來進行通信。
因此:
1.服務器實例化一個 ServerSocket 對象,表示通過服務器上的端口通信。
2.服務器調用 ServerSocket 類的 accept() 方法,該方法將一直等待,直到客戶端連接到服務器上給定的端口。
3.服務器正在等待時,一個客戶端實例化一個 Socket 對象,指定服務器名稱和端口號來請求連接。
4.Socket 類的構造函數試圖將客戶端連接到指定的服務器和端口號。如果通信被建立,則在客戶端創建一個 Socket 對象能夠與服務器進行通信。
5.在服務器端,accept() 方法返回服務器上一個新的 socket 引用,該 socket 連接到客戶端的 socket。
6.連接建立后,通過使用 I/O 流在進行通信,每一個socket都有一個輸出流和一個輸入流,客戶端的輸出流連接到服務器端的輸入流,而客戶端的輸入流連接到服務器端的輸出流。
聊天室功能
聊天室需要一個服務器支持,多個客戶端連接服務端,服務端的作用就是接受不同的客戶端數據,并轉發到其他客戶端。
客戶端可發發送數據給服務器端,同時客戶端也需要接收服務器端返回的數據。客戶端的發送數據和接收數據是兩個獨立的通道,互不影響。
客戶端的輸出與輸入要獨立,可以使用多線程來實現。
服務端要為每一個客戶端建立一個通道,服務端也使用多線程來實現。
服務端需要創建一個通道的列表,統一管理客戶端的通道,為了實現自己發的消息,其他人都可以看到。
在客戶端程序里為每一個客戶端設置一個名稱,約定以@name#開頭的格式為私聊,就可以實現私聊的功能。
當程序中發生異常時,線程就停止執行。
總體:
每個客戶端在連接到服務器端時,要通過控制臺輸入自己的名稱,然后開始發送消息到服務端,服務端在接收到客戶端的連接時,首先輸出誰進入了聊天室,然后把客戶端發來的消息轉發給其他客戶端,實現群聊的功能,如果客戶端按照約定以@name#開頭的格式輸入消息,服務端需要解析到客戶端要私聊的對象,把消息單獨發送給要私聊的客戶端。
1.處理IO異常
import java.io.Closeable;/*** 釋放資源,由于代碼里會處理很多 IO 異常,當程序中發生異常時,線程就停止執行,并且關閉掉對應的資源*/public class Util {public static void closeAll(Closeable... io) {for (Closeable temp : io) {try {if (null != temp) {temp.close();}} catch (Exception e) {e.printStackTrace();}}} }2.服務器端多線程,維護一個客戶端的通道列表,服務端實現既能接受客戶端的數據,又能轉發給對應的客戶端
import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; import java.util.ArrayList; import java.util.List;/*** 實現服務器端的多線程,維護一個客戶端的通道列表,服務器端既能接收客戶端的數據,又能把數據轉發給對應的客戶端*/public class ChatChannel implements Runnable{public static List<ChatChannel> all = new ArrayList<ChatChannel>();// 通道列表private DataInputStream dis; // 輸入流private DataOutputStream dos;// 輸出流private String name;// 客戶端名稱private boolean isRunning = true;public ChatChannel(Socket client) {try {dis = new DataInputStream(client.getInputStream());dos = new DataOutputStream(client.getOutputStream());this.name = dis.readUTF();System.out.println(this.name + "進入了聊天室");this.send(this.name + ",您好!歡迎您進入聊天室");sendOthers(this.name + "進入了聊天室", true); // 系統消息} catch (IOException e) {e.printStackTrace();Util.closeAll(dis, dos);isRunning = false;}}/*** 讀取數據*/private String receive() {String msg = "";try {msg = dis.readUTF();} catch (IOException e) {e.printStackTrace();Util.closeAll(dis);isRunning = false;all.remove(this); // 移除自身}return msg;}/*** 發送數據*/private void send(String msg) {if (msg != null && !"".equals(msg)) {try {dos.writeUTF(msg);dos.flush();} catch (IOException e) {e.printStackTrace();Util.closeAll(dos);isRunning = false;all.remove(this); // 移除自身}}}/*** @param msg 消息內容* @param sysMsg 是否是系統消息*/private void sendOthers(String msg, boolean sysMsg) {// 加入私聊的判斷,約定@name#格式為私聊if (msg.startsWith("@") && msg.indexOf("#") > -1) { // 私聊// 獲取nameString name = msg.substring(1, msg.indexOf("#"));String content = msg.substring(msg.indexOf("#") + 1);for (ChatChannel other : all) {if (name.equals(other.name)) {other.send(this.name + "悄悄地對您說:" + content);}}} else {for (ChatChannel other : all) {if (other == this) {continue;}if (sysMsg) {other.send("系統信息:" + msg);} else {// 發送其他客戶端other.send(this.name + "對所有人說:" + msg);}}}}@Overridepublic void run() {while (isRunning) {sendOthers(receive(), false); // 用戶消息}} }3.創建服務端類Server,使用多線程和通道容器
import java.io.IOException; import java.net.ServerSocket; import java.net.Socket;/*** 創建服務端Server類,使用多線程和通道容器*/public class Server {public static void main(String[] args) throws IOException {ServerSocket server = new ServerSocket(8888);while (true) {Socket client = server.accept();ChatChannel channel = new ChatChannel(client);ChatChannel.all.add(channel);// 統一管理客戶端的通道new Thread(channel).start(); // 啟動一條通道}} }4.客戶端發送消息線程類Send,設定自己的名字,并發送給服務端
import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket;/*** 客戶端的消息發送線程類 Send,每個客戶端要設定自己的名字,同時接收控制臺輸入的數據并發送給服務端。*/public class Send implements Runnable{// 控制臺輸入private BufferedReader console;// 輸出流private DataOutputStream dos;// 客戶端名稱private String name;// 控制線程private boolean isRunning = true;public Send(Socket client, String name) {try {console = new BufferedReader(new InputStreamReader(System.in));dos = new DataOutputStream(client.getOutputStream());this.name = name;send(this.name); // 把自己的名字發給服務端} catch (IOException e) {e.printStackTrace();isRunning = false;Util.closeAll(dos, console);}}/*** 從控制臺接收數據并發送數據*/public void send(String msg) {try {if (msg != null && !"".equals(msg)) {dos.writeUTF(msg);dos.flush(); // 強制刷新}} catch (IOException e) {e.printStackTrace();isRunning = false;Util.closeAll(dos, console);}}// 從控制臺接收數據private String getMsgFromConsole() {try {return console.readLine();} catch (IOException e) {e.printStackTrace();}return "";}@Overridepublic void run() {while (isRunning) {send(getMsgFromConsole());}} }5.客戶端接收消息Receive,用于獨立接受服務端返回的數據
mport java.io.DataInputStream; import java.io.IOException; import java.net.Socket;/*** 客戶端的消息接收線程類 Receive,用于獨立接收服務端返回的數據。*/public class Receive implements Runnable{// 輸入流private DataInputStream dis;// 線程標識private boolean isRunning = true;public Receive(Socket client) {try {dis = new DataInputStream(client.getInputStream());} catch (IOException e) {e.printStackTrace();isRunning = false;Util.closeAll(dis);}}/*** 接收數據*/public String receive() {String msg = "";try {msg = dis.readUTF();} catch (IOException e) {e.printStackTrace();isRunning = false;Util.closeAll(dis);}return msg;}@Overridepublic void run() {while (isRunning) {System.out.println(receive());}} }6.創建客戶端類Client,發送和接收數據分布使用獨立的多線程
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket;/*** 創建客戶端類 Client,發送數據和接收數據分布使用獨立的多線程處理。*/public class Client {public static void main(String[] args) throws IOException {System.out.println("請輸入您的名稱:");BufferedReader br = new BufferedReader(new InputStreamReader(System.in));String name = br.readLine();if ("".equals(name)) {return;}Socket client = new Socket("localhost", 8888);new Thread(new Send(client, name)).start(); // 發送一條通道new Thread(new Receive(client)).start(); // 接收一條通道} }最后,先啟動Server,然后可以啟動多個客戶端Client
客戶端啟動后,創建名字:
客戶端公開發言
客戶端私聊
總結
- 上一篇: Prim算法简易教程(~简单易懂,附最详
- 下一篇: kriging克里金插值以及前端渲染jS