通信網絡編程3.0——JAVA

?主要添加了私聊功能

1服務器類定義與成員變量

public class ChatServer {int port = 6666;// 定義服務器端口號為 6666ServerSocket ss;// 定義一個 ServerSocket 對象用于監聽客戶端連接//List<Socket> clientSockets = new ArrayList<>();// 定義一個列表用于存儲已連接的客戶端 Socket 對象List<Socket> clientSockets = new CopyOnWriteArrayList<>();List<String> clientNames = new ArrayList<>(10);//迭代時會復制整個底層數組,因此在遍歷過程中其他線程對集合的修改不會影響當前遍歷,// 有效避免了 ConcurrentModificationException 異常。
}

  • 端口號:服務器的端口號被定義為 6666,客戶端需要知道這個端口號才能與服務器建立連接。
  • ServerSocket 對象ServerSocket 是 Java 提供的一種用于服務器端編程的類,它負責監聽特定端口上的客戶端連接請求。一旦有客戶端連接,就會創建一個新的 Socket 對象來處理與該客戶端之間的通信。
  • 客戶端套接字列表clientSockets 使用了 CopyOnWriteArrayList 來存儲與客戶端建立連接的 Socket 對象。這種數據結構在遍歷時會復制整個底層數組,因此在遍歷過程中其他線程對集合的修改不會影響當前遍歷,有效避免了 ConcurrentModificationException 異常。
  • 客戶端名稱列表clientNames 用于存儲每個連接客戶端的名稱,方便在消息中區分不同的客戶端。

2初始化服務器

public void initServer() {// 初始化服務器的方法try {ss = new ServerSocket(port);// 創建 ServerSocket 對象并綁定到指定端口System.out.println("服務器啟動,等待客戶端連接...");} catch (IOException e) {throw new RuntimeException(e);}}
  • ?在服務器初始化方法 initServer 中,通過調用 ServerSocket 的構造函數并傳入端口號,創建了一個 ServerSocket 對象,綁定了服務器到指定端口,使服務器能夠監聽該端口上的客戶端連接請求。一旦初始化成功,就會打印出 "服務器啟動,等待客戶端連接..." 的提示信息,告知服務器已正常啟動并處于監聽狀態。

3監聽客戶端連接

public void listenerConnection() {// 監聽客戶端連接的方法,返回連接的 Socket 對象new Thread(()->{while(true){try {Socket socket = ss.accept();// 調用 accept() 方法等待客戶端連接clientNames.add("Hello");//clientSockets.add(socket);synchronized (clientSockets) {// 同步操作確保線程安全clientSockets.add(socket);// 將連接的客戶端 Socket 對象添加到列表中}System.out.println("客戶端已連接:" + socket.getInetAddress().getHostAddress());// 輸出客戶端連接成功提示信息及客戶端 IP 地址} catch (IOException e) {throw new RuntimeException(e);}}}).start();}
  • listenerConnection 方法創建了一個新線程,用于不斷地監聽客戶端的連接請求。在循環中,調用 ss.accept() 方法阻塞等待客戶端的連接。一旦有客戶端連接,就會創建一個新的 Socket 對象,并將其添加到 clientSockets 列表中。同時,向 clientNames 列表中添加一個默認客戶端名稱 "Hello",后續可以根據實際情況更新該名稱。每當有新的客戶端連接成功,就會打印出客戶端的 IP 地址,表明該客戶端已成功連接到服務器。

4讀取客戶端消息

public void readMsg(List<Socket> clientSockets, JTextArea msgShow) {// 讀取客戶端消息的方法//System.out.println("clientSockets size: " + clientSockets.size()); // 檢查列表大小synchronized (clientSockets) {// 對客戶端列表進行同步操作Thread tt = new Thread(() -> {// 創建一個線程用于讀取并處理客戶端消息//System.out.println("開始讀取客戶端發送的消息");while (true) {// 無限循環持續讀取消息InputStream is;// 定義輸入流對象用于讀取客戶端消息Socket socket = null;try {Thread.sleep(50);} catch (InterruptedException e) {throw new RuntimeException(e);}for (Socket cSocket : clientSockets) {// 遍歷每個客戶端 Socket//System.out.println("循環每個socket");socket = cSocket;if(socket == null){continue;}try {is = socket.getInputStream();// 獲取客戶端 Socket 對象的輸入流} catch (IOException e) {throw new RuntimeException(e);}try {int idLen = is.read();// 讀取消息中發送方名稱長度的字節if(idLen == 0){continue;}byte[] id = new byte[idLen];// 根據讀取的長度創建字節數組存儲發送方名稱is.read(id);// 讀取發送方名稱字節數組int si = clientSockets.indexOf(socket);clientNames.set(si,new String(id));int msgLen = is.read();// 讀取消息內容長度的字節if(msgLen == 0){continue;}byte[] msg = new byte[msgLen];// 根據讀取的長度創建字節數組存儲消息內容is.read(msg);// 讀取消息內容字節數組String clientmsg = new String(msg);//先判斷@有沒有int start = clientmsg.indexOf('@');if(start != -1){int end = clientmsg.indexOf(' ');String friendID = clientmsg.substring(start+1,end);String message = clientmsg.substring(end);System.out.println(new String(id) + "發送給" + friendID + " 的一條私聊消息:" + message);// 將字節數組轉換為字符串并輸出消息內容msgShow.append(new String(id) + "發送給" + friendID + " 的一條私聊消息:" + message + "\n");for (Socket clientSocket : clientSockets) {// 遍歷所有已連接的客戶端 Socket 對象if (clientSocket == socket) {// 如果是當前發送消息的客戶端continue;}int s = clientSockets.indexOf(clientSocket);String name = clientNames.get(s);if(name.equals(friendID)){OutputStream os = null;// 定義輸出流對象用于向其他客戶端發送消息os = clientSocket.getOutputStream();// 獲取客戶端 Socket 對象的輸出流os.write(id.length);// 發送發送方名稱長度os.write(id);// 發送發送方名稱字節數組message += "(這是一條私聊消息)";os.write(message.getBytes().length);// 發送消息內容長度os.write(message.getBytes());// 發送消息內容字節數組os.flush();// 刷新輸出流確保數據發送完成break;}}}else {System.out.println(new String(id) + "發送的消息:" + new String(msg));// 將字節數組轉換為字符串并輸出消息內容msgShow.append(new String(id) + "說:" + new String(msg) + "\n");// 轉發信息給所有其他客戶端for (Socket clientSocket : clientSockets) {// 遍歷所有已連接的客戶端 Socket 對象if (clientSocket == socket) {// 如果是當前發送消息的客戶端continue;}OutputStream os = null;// 定義輸出流對象用于向其他客戶端發送消息os = clientSocket.getOutputStream();// 獲取客戶端 Socket 對象的輸出流os.write(id.length);// 發送發送方名稱長度os.write(id);// 發送發送方名稱字節數組os.write(msg.length);// 發送消息內容長度os.write(msg);// 發送消息內容字節數組os.flush();// 刷新輸出流確保數據發送完成}}} catch (IOException e) {throw new RuntimeException(e);}}}});tt.start();}}
  • readMsg 方法的核心功能是讀取客戶端發送的消息,并根據消息類型(普通消息或私聊消息)進行相應的處理和轉發。
  • 首先,創建一個新線程 tt,在無限循環中依次檢查 clientSockets 列表中的每個 Socket 對象。對于每個客戶端 Socket,通過 socket.getInputStream() 獲取輸入流,從而讀取客戶端發送的數據。
  • 客戶端發送的數據格式預先約定為:首先是一個字節表示發送方名稱的長度,然后是發送方名稱對應的字節數組;接下來是一個字節表示消息內容長度,最后是消息內容對應的字節數組。按照這種格式,先讀取發送方名稱長度 idLen,根據該長度創建字節數組 id 讀取發送方名稱,再讀取消息內容長度 msgLen,創建字節數組 msg 讀取消息內容。
  • 隨后,將讀取到的發送方名稱更新到對應的 clientNames 列表位置。接下來對消息內容進行判斷,如果消息內容中包含 "@" 符號,則認為這是一條私聊消息。通過解析消息內容,獲取私聊目標的名稱 friendID 和實際消息內容 message,然后在 clientSockets 中查找對應的私聊目標客戶端 Socket,并將私聊消息發送給該目標客戶端。
  • 如果消息內容中不包含 "@" 符號,則認為這是一條普通消息,直接將消息轉發給除發送方外的所有其他客戶端。
  • 無論是普通消息還是私聊消息,都通過輸出流 OutputStream 將消息發送給目標客戶端。發送時,同樣按照約定的數據格式,先發送發送方名稱的長度和名稱字節數組,再發送消息內容的長度和內容字節數組,并調用 os.flush() 刷新輸出流確保數據發送完成。

5服務器啟動

public void start() {// 啟動服務器的方法initServer();// 調用初始化服務器的方法//new Thread(()->{//startSend();// 啟動服務端從控制臺向所有客戶端發送消息的線程//}).start();ChatUI ui = new ChatUI("服務端", clientSockets);ui.setVisible(true); // 確保 UI 可見listenerConnection();// 調用監聽客戶端連接的方法readMsg(clientSockets,ui.msgShow);// 調用讀取消息的方法}
  • start 方法中,首先調用 initServer 方法初始化服務器,然后創建一個 ChatUI 對象作為服務器端的用戶界面(假設存在 ChatUI 類,用于展示聊天內容等信息),使用戶界面可見。接著調用 listenerConnection 方法啟動監聽客戶端連接的線程,最后調用 readMsg 方法啟動讀取消息的線程,并將讀取到的消息顯示在用戶界面中。

6主方法

public static void main(String[] args) {ChatServer server = new ChatServer();// 創建 ChatServer 對象server.start();// 調用啟動服務器的方法}
  • main 方法作為程序的入口,創建了一個 ChatServer 對象,并調用其 start 方法啟動服務器。?

7其他模塊代碼不變

public class Client {Socket socket;// 定義 Socket 對象用于與服務器建立連接String ip;// 定義服務器 IP 地址int port;// 定義服務器端口號InputStream in;// 定義輸入流對象用于讀取服務器發送的消息OutputStream out;// 定義輸出流對象用于向服務器發送消息public Client(String ip, int port) {// 構造方法,初始化客戶端 IP 地址和端口號this.ip = ip;this.port = port;}public void connectServer(String userName) {// 連接服務器的方法try {socket = new Socket(ip, port);// 創建 Socket 對象連接到指定 IPin = socket.getInputStream();// 獲取 Socket 對象的輸入流用于讀取消息out = socket.getOutputStream();// 獲取 Socket 對象的輸出流用于發送消息try {out.write(userName.length());out.write(userName.getBytes());out.flush();} catch (IOException e) {throw new RuntimeException(e);}System.out.println("連接服務器成功");} catch (IOException e) {throw new RuntimeException(e);}}public void readMsg(JTextArea msgShow) {// 讀取服務器發送的消息的方法new Thread(() -> {// 創建一個線程用于讀取并處理服務器消息try {System.out.println("開始讀取消息");while (true) { // 無限循環持續讀取消息int senderNameLength = in.read();// 讀取發送方名稱長度的字節byte[] senderNameBytes = new byte[senderNameLength];// 根據讀取的長度創建字節數組存儲發送方名稱in.read(senderNameBytes);// 讀取發送方名稱字節數組int msgLength = in.read();// 讀取消息內容長度的字節byte[] msgBytes = new byte[msgLength];// 根據讀取的長度創建字節數組存儲消息內容in.read(msgBytes);// 讀取消息內容字節數組System.out.println(new String(senderNameBytes) + "發送的消息:" + new String(msgBytes));// 將字節數組轉換為字符串并輸出消息內容msgShow.append(new String(senderNameBytes) +"說:" + new String(msgBytes) + "\n");}} catch (IOException e) {throw new RuntimeException(e);}}).start();}/*public void startSend() {// 從控制臺向服務器發送消息的方法new Thread(() -> {// 創建一個線程用于讀取控制臺輸入并發送消息try {BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));// 創建緩沖讀取器讀取控制臺輸入System.out.println("請輸入用戶名:");String userName = reader.readLine();// 讀取一行控制臺輸入作為用戶名System.out.println("請輸入消息(按回車發送):");while (true) {// 無限循環持續讀取并發送消息String msg = reader.readLine();// 讀取一行控制臺輸入作為消息內容if (msg != null && !msg.isEmpty()) {// 如果輸入的消息不為空out.write(userName.getBytes().length);// 發送用戶名長度out.write(userName.getBytes());// 發送用戶名字節數組out.write(msg.getBytes().length);// 發送消息內容長度out.write(msg.getBytes());// 發送消息內容字節數組out.flush();// 刷新輸出流確保數據發送完成}}} catch (IOException e) {throw new RuntimeException(e);}}).start();}*/public void startClient() {// 啟動客戶端的方法String userName = JOptionPane.showInputDialog("請輸入用戶名:");connectServer(userName);// 調用連接服務器的方法ChatUI ui = new ChatUI(userName, out);readMsg(ui.msgShow);// 調用讀取消息的方法//startSend();// 調用發送消息的方法try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}new Thread() {public void run() {while (true) {try {out.write(0);out.flush();Thread.sleep(500);} catch (IOException e) {throw new RuntimeException(e);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}.start();}public static void main(String[] args) {Client client = new Client("127.0.0.1", 6666);// 創建 Client 對象,連接到本地主機的 6666 端口client.startClient();// 調用啟動客戶端的方法}
}
public class ChatUI extends JFrame {public JTextArea msgShow = new JTextArea();// 顯示消息的文本區域public ChatUI(String title, List<Socket> clientSockets) {// 服務器端構造方法super(title);// 設置窗口標題setSize(500, 500);// 設置窗口大小setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 設置關閉操作JScrollPane scrollPane = new JScrollPane(msgShow);// 創建滾動面板包括消息顯示區域scrollPane.setPreferredSize(new Dimension(0, 350));add(scrollPane, BorderLayout.NORTH);// 添加到窗口北部// 創建消息輸入面板及組件JPanel msgInput = new JPanel();JTextArea msg = new JTextArea();JScrollPane scrollPane1 = new JScrollPane(msg);scrollPane1.setPreferredSize(new Dimension(480, 80));msgInput.add(scrollPane1);JButton send = new JButton("發送");msgInput.add(send);msgInput.setPreferredSize(new Dimension(0, 120));add(msgInput, BorderLayout.SOUTH);// 添加到窗口南部setVisible(true);ChatListener cl = new ChatListener();// 創建事件監聽器send.addActionListener(cl);// 為發送按鈕添加監聽器cl.showMsg = msgShow;// 傳遞消息顯示組件cl.msgInput = msg;cl.userName = title;cl.clientSockets = clientSockets;}public ChatUI(String title, OutputStream out) {// 客戶端構造方法super(title);setSize(500, 500);setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);JScrollPane scrollPane = new JScrollPane(msgShow);scrollPane.setPreferredSize(new Dimension(0, 350));add(scrollPane, BorderLayout.NORTH);JPanel msgInput = new JPanel();JTextArea msg = new JTextArea();JScrollPane scrollPane1 = new JScrollPane(msg);scrollPane1.setPreferredSize(new Dimension(480, 80));msgInput.add(scrollPane1);JButton send = new JButton("發送");msgInput.add(send);msgInput.setPreferredSize(new Dimension(0, 120));add(msgInput, BorderLayout.SOUTH);setVisible(true);clientListener cl = new clientListener();send.addActionListener(cl);cl.showMsg = msgShow;cl.msgInput = msg;cl.userName = title;cl.out = out;}
}
public class ChatListener implements ActionListener {public List<Socket> clientSockets;// 客戶端 Socket 列表JTextArea showMsg;// 消息顯示區域JTextArea msgInput;// 消息輸入區域String userName;// 用戶名OutputStream out;// 輸出流public void actionPerformed(ActionEvent e) {// 處理發送按鈕點擊事件String text = msgInput.getText();// 獲取輸入的消息文本showMsg.append(userName + ": " + text + "\n");// 在顯示區域追加消息for (Socket cSocket : clientSockets) {// 遍歷所有客戶端Socket socket = cSocket;try {out = socket.getOutputStream();// 獲取客戶端輸出流out.write(userName.getBytes().length);// 發送用戶名長度out.write(userName.getBytes());// 發送用戶名out.write(text.getBytes().length);// 發送消息內容長度out.write(text.getBytes());// 發送消息內容out.flush();// 刷新輸出流} catch (IOException ex) {throw new RuntimeException(ex);}}}}
public class clientListener implements ActionListener {JTextArea showMsg;// 消息顯示區域JTextArea msgInput;// 消息輸入區域String userName;// 用戶名OutputStream out;// 輸出流public void actionPerformed(ActionEvent e) {// 處理發送按鈕點擊String text = msgInput.getText();// 獲取輸入消息showMsg.append(userName + ": " + text + "\n");// 顯示消息try {out.write(userName.getBytes().length);// 發送用戶名長度out.write(userName.getBytes());// 發送用戶名out.write(text.getBytes().length);// 發送消息長度out.write(text.getBytes());// 發送消息內容out.flush();// 刷新輸出流//msgInput.setText(""); // 清空輸入框} catch (IOException ex) {throw new RuntimeException(ex);}}
}

8運行效果

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/88121.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/88121.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/88121.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

RediSearch `FT.CREATE` 完全參數指南 HASH/JSON 雙寫實戰

1、索引與 Schema 速概 索引 (index) —— 倒排、前綴、向量、Geo … 元數據集合Schema —— 索引藍圖&#xff1a;定義字段、類型、權重、排序及存儲策略FT.CREATE —— 創建索引命令&#xff0c;分「索引級參數」和「字段級參數」兩層 2 、FT.CREATE 語法模板 FT.CREATE &…

QT學習教程(三十七)

系統繁忙時的響應&#xff08;Staying Responsive During Intensive Processing&#xff09; 當我們調用QApplication::exec()時&#xff0c;Qt 就開始了事件循環。啟動時&#xff0c;Qt 發出顯示和繪制事件&#xff0c;把控件顯示出來。然后&#xff0c;事件循環就開始了&…

hot100 -- 17.技巧

1.多數元素 問題&#xff1a; 給定一個大小為 n 的數組 nums &#xff0c;返回其中的多數元素。多數元素是指在數組中出現次數 大于 ? n/2 ? 的元素。 你可以假設數組是非空的&#xff0c;并且給定的數組總是存在多數元素。 方法1&#xff1a; 哈希表 實時判斷&#xff…

算法第39天| 打家劫舍 1、2、3

198. 打家劫舍 題目 思路與解法 class Solution { public:int rob(vector<int>& nums) {// dp數組含義&#xff1a;// 考慮下標i&#xff08;包括i&#xff09;以內的房屋&#xff0c;最多可以偷竊的金額為dp[i]if (nums.size() 0) return 0;if (nums.size() 1)…

車載CAN總線數據采集與故障診斷裝置設計與實現

車載CAN總線數據采集與故障診斷裝置設計與實現 鏈接:1.6W字 [下載]摘要1.1 研究背景1.2 研究意義(1)技術提升:推動CAN總線診斷的智能化與實時性(2)經濟價值:降低診斷成本與維修時間(3)安全與標準化:促進車聯網數據安全體系建設社會效益1.3 國內外研究現狀1.3.1 國外研…

布瑞琳BRANEW:高端洗護領航者,鑄就品質生活新典范

近日,布瑞琳BRANEW,這一中國高端洗護行業的領軍品牌,再次憑借其卓越的服務品質、創新的經營模式以及對行業標準的深度推動,成為市場矚目的焦點。作為北京2022年冬奧會和殘奧會的商業服務保障單位,布瑞琳不僅展現了其無與倫比的服務能力,更在國際舞臺上彰顯了品牌的非凡影響力。…

AWS服務器擴充硬盤

1、在控制臺上將需要擴充的硬盤增加空間 將硬盤大小由原來的50G升級到200G 2、登錄所掛載的服務器 1&#xff09;查看硬盤分區情況 adminip-172-31-121-13:~$ sudo lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS nvme0n1 259:0 0 200G 0 disk ├─nv…

嵌入式自學第四十二天

PWM:脈沖寬度調制&#xff0c;調節電壓為方波。關鍵參數&#xff1a;占空比、周期。 UART&#xff1a;通用異步收發器。 參與通信的設備&#xff1a;主機host 通信的本質&#xff1a;數據的傳遞。 通信方式&#xff1a; 單工&#xff1a;只能單向傳遞 半雙工&#xff1a;雙向…

人工智能如何重塑教育體系:個性化學習的新時代

&#x1f4dd;個人主頁&#x1f339;&#xff1a;慌ZHANG-CSDN博客 &#x1f339;&#x1f339;期待您的關注 &#x1f339;&#x1f339; 一、引言&#xff1a;教育的“智能革命”正在發生 教育作為人類社會發展的基石&#xff0c;始終緊隨技術進步不斷演化。從印刷術帶來知識…

【云原生】基礎篇

?一、云原生 1.1 本質與核心技術體系? 云原生&#xff08;Cloud Native&#xff09;是以容器化、微服務、聲明式API和動態編排為核心的架構范式&#xff0c;旨在最大化利用云的彈性、可觀測性和自動化能力。其技術棧分層如下&#xff1a; ?1.2、云原生核心技術棧? ?層級…

實時反欺詐:基于 Spring Boot 與 Flink 構建信用卡風控系統

在金融科技飛速發展的今天&#xff0c;信用卡欺詐手段日益高明和快速。傳統的基于批處理的事后分析模式已難以應對實時性要求極高的欺詐場景。本文將詳細介紹如何利用 Spring Boot 和 Apache Flink 這對強大的組合&#xff0c;構建一個高性能、可擴展的實時信用卡反欺詐系統。 …

通過apache共享文件

有時候&#xff0c;vmware虛擬機的vmware tools總是安裝失敗&#xff0c;這樣就不能在虛擬機和主機之間共享文件。此時可以利用apache通過文件上傳和下載共享文件。 通過下面的php文件&#xff0c;虛擬機作為客戶端訪問此php&#xff0c;可以在虛擬機和主機之間共享文件。當然…

Maven生命周期,測試

測試 Junit入門 單元測試 單元測試&#xff1a;就是針對最小的功能單元(方法)&#xff0c;編寫測試代碼對其正確性進行測試。 JUnit&#xff1a;最流行的Java測試框架之一&#xff0c;提供了一些功能&#xff0c;方便程序進行單元測試&#xff08;第三方公司提供&#xff09…

H5調試工具vconsole和Eruda對比

VConsole與Eruda對比分析 VConsole和Eruda是兩款主流的移動端JavaScript調試工具&#xff0c;它們在功能定位、使用場景和技術實現上有諸多差異。以下從多個維度進行對比&#xff0c;幫助你選擇更適合的工具&#xff1a; 一、核心功能對比 功能維度VConsoleEruda基礎日志輸出…

Java經典編程題

題目 1&#xff1a;斐波那契數列 題目要求&#xff1a;編寫一個方法&#xff0c;輸入正整數n&#xff0c;輸出斐波那契數列的第n項。斐波那契數列的定義是&#xff1a;F(0)0&#xff0c;F(1)1, 當n > 1時&#xff0c;F(n)F(n - 1)F(n - 2)。 答案&#xff1a; public cla…

BUG調試案例五十:“低級”設計BUG案例篇(持續更新中.........)

引言 回頭看這些年硬件路,總有一些“低級Bug”一次次地在給我上課。它們不是復雜的架構設計,不是玄妙的信號完整性問題,而是最基礎、最應該避免、卻又最容易忽略的小細節。 每一次Bug的背后,都是教訓,有的甚至讓整個項目差點“翻車”。寫下這篇文章記錄那些“看似簡單實…

DeepSeek中的提示庫及其用法示例

《DEEPSEEK原生應用與智能體開發實踐 圖書》【摘要 書評 試讀】- 京東圖書 為了深入探索DeepSeek提示詞樣例的豐富內涵&#xff0c;充分挖掘其背后潛藏的無限可能&#xff0c;同時致力于為用戶打造更為卓越、便捷且高效的使用體驗&#xff0c;DeepSeek官網的API文檔匠心獨運地…

Node.js特訓專欄-實戰進階:7.Express模板引擎選型與使用

&#x1f525; 歡迎來到 Node.js 實戰專欄&#xff01;在這里&#xff0c;每一行代碼都是解鎖高性能應用的鑰匙&#xff0c;讓我們一起開啟 Node.js 的奇妙開發之旅&#xff01; Node.js 特訓專欄主頁 專欄內容規劃詳情 Express模板引擎選型與使用全解析&#xff1a;打造動態We…

uniapp評價組件

組件目錄 components/Evaluation.vue <template><view class"evaluation-container"><!-- 綜合評價 --><view class"evaluation-item" tap"parentTap"><text class"label label-1">綜合評價</text&…

SQL Server2022版詳細安裝教程(Windows)

一&#xff0c;下載SQL Server 可以瀏覽器自己搜索一下 2、安裝 安裝前需要先將防火墻和帶殺毒軟件的先退出關閉掉&#xff08;防止安裝不成功&#xff09; 2.1、選擇自定義安裝 2.2、更改位置進行安裝 2.3、等待安裝 3、進行安裝配置 當安裝好后會彈出一個這樣的頁面 3.1、…