2.2 網絡多線程(私聊、群發、發送文件、推送新聞、離線留言)

文章目錄

  • 一、私聊
    • 1.1 分析
    • 1.2 客戶端
      • 1.2.1 MessageClientService 私聊類
      • 1.2.2 ClientConnectServerThread 線程類
    • 1.3 服務端
      • 1.3.1 ServerConnectClientThread 線程類
    • 1.4功能演示
  • 二、群發消息
    • 2.1 分析
    • 2.2 客戶端
      • 2.2.1 MessageClientService類
      • 2.2.2 ClientConnectServerThread 線程類
    • 2.3 服務端
      • 2.3.1 ServerConnectClientThread 線程類
    • 2.4 測試
  • 三、發送文件
    • 3.0 消息類擴展
    • 3.1 分析
    • 3.2 客戶端
      • 3.2.1 FileClientService 文件傳輸類
      • 3.2.2 ClientConnectServerThread 線程類接收文件
    • 3.3 服務端
      • 3.3.1 ServerConnectClientThread 線程類
    • 3.4 測試結果
  • 四、服務端推送新聞
    • 4.1 分析
    • 4.2 客戶端
      • 4.2.1 ClientConnectServerThread 線程類
    • 4.3 服務端
      • 4.3.1 SendNewsAllService推送消息
      • 4.3.2 QQServer啟動線程
    • 4.4 測試
  • 五、離線消息
  • 六、代碼總結
    • 6.1 公共類代碼
      • 6.1.1 消息類
      • 6.1.2 消息類型類
      • 6.1.3 客戶類
      • 6.1.4 控制臺讀取類
    • 6.2 客戶端代碼
      • 6.2.1 QQView 客戶端頁面
      • 6.2.2 ClientConnectServerThread線程類
      • 6.2.3 ManagerClientConnectServerThread線程管理類
      • 6.2.4 MessageClientService發送消息類
      • 6.2.5 FileClientService發送文件類
      • 6.2.6 UserClientService 用戶登錄驗證類
    • 6.3 服務端代碼
      • 6.3.1 后臺啟動
      • 6.3.2 服務器
      • 6.3.3 ServerConnectClientThread線程類
      • 6.3.4 ManagerServerConnectServerThread管理線程類
      • 6.3.5 SendNewsAllService 推送新聞類

一、私聊

1.1 分析

客戶端A和客戶端B私聊的時候,其實服務端在中間做了一個轉發

流程

  • 客戶端A —> 服務端 —> 客戶端B

  • 客戶端B —> 服務端 —> 客戶端A

服務端可以讀取到客戶端A發送給客戶端B的消息,服務端再從管理線程的集合中獲取接收者客戶端B的線程,也就能獲取到其socket,此線程在服務端就會將消息發送給客戶端B,也就是服務器只需要做個轉發即可

image-20231208224106294

1.2 客戶端

1.2.1 MessageClientService 私聊類

/*** 該類提供和消息相關的服務方法*/
public class MessageClientService {/*** @param content  內容* @param senderId 發送用戶id* @param getterId 接收用戶id*/public void sendMessageToOne(String content, String senderId, String getterId) {//封裝消息Message message = new Message();message.setContent(content);message.setSender(senderId);message.setGetter(getterId);message.setSendTime(new Date().toString());message.setMesType(MessageType.MESSAGE_COMM_MES.getCode());//普通消息System.out.println("用戶"+senderId+"和用戶"+getterId+"說:"+content);//獲取senderId對應的socketClientConnectServerThread clientConnectServerThread = ManagerClientConnectServerThread.getClientConnectServerThread(senderId);Socket socket = clientConnectServerThread.getSocket();//輸出消息try {ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(message);oos.flush();} catch (IOException e) {e.printStackTrace();}}
}

1.2.2 ClientConnectServerThread 線程類

@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ClientConnectServerThread extends Thread {//該線程需要持有Socket屬性private Socket socket;/***因為Thread需要在后臺跟我們的服務器進行通信(保持一個聯系),因此我們使用while循環來控制*/@Overridepublic void run() {while(true){//一直讀取從服務器端回收的消息System.out.println("客戶端線程,等待讀取從服務端發送的消息....");try {ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());//如果服務器端沒有發送消息過來,這個地方會堵塞,此線程會一直等待//這就是一個堵塞式網絡編程,效率是相對比較低的Message message = (Message)ois.readObject();//判斷message的類型,然后做響應的業務處理if (message.getMesType().equals(MessageType.MESSAGE_RETTURN_ONLINE_FRIEND.getCode())){//獲取在線用戶,取出在線列表信息并顯示String[] onlineUsers = message.getContent().split(" ");System.out.println("當前在線用戶列表如下");for (int i=0;i<onlineUsers.length;i++){System.out.println("用戶:"+onlineUsers[i]);}}else if (MessageType.MESSAGE_COMM_MES.getCode().equals(message.getMesType())) {//轉發給指定客戶端,假如說客戶不在線的話,可以保存到數據庫,這樣就可以實現離線留言System.out.println("用戶"+message.getGetter()+"收到來自用戶"+message.getSender()+"的消息:"+message.getContent());}else{System.out.println("其他類型的message,暫時不處理");}} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}}}
}

1.3 服務端

1.3.1 ServerConnectClientThread 線程類

/*** 該類對應的對象和某個客戶端保持通信*/
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ServerConnectClientThread extends Thread {/*** 可以區分此socket是和哪個用戶進行關聯的*/private String userId;//連接到服務端的這個用戶idprivate Socket socket;/*** 線程處于run狀態,可以發送或者接收客戶端的消息*/@Overridepublic void run() {//不斷的從socket中讀數據和寫數據while (true) {System.out.println("服務端和客戶端保持通信,讀取數據.... userId:" + userId);ObjectInputStream ois = null;try {ois = new ObjectInputStream(socket.getInputStream());//讀取數據Message message = (Message) ois.readObject();//根據Message的類型,判斷客戶端想要執行什么操作if (MessageType.MESSAGE_GET_ONLINE_FRIEND.getCode().equals(message.getMesType())) {System.out.println("用戶" + userId + "獲取在線用戶");//拉取在線用戶(客戶端要拉取在線用戶列表)Socket socket = ManagerServerConnectServerThread.getClientThread(userId).getSocket();ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());//構建Message發送給服務端Message returnMessage = new Message();returnMessage.setMesType(MessageType.MESSAGE_RETTURN_ONLINE_FRIEND.getCode());returnMessage.setContent(ManagerServerConnectServerThread.getOnlineUser());//說明要發送給誰returnMessage.setGetter(message.getSender());//返回給客戶端oos.writeObject(returnMessage);oos.flush();} else if (MessageType.MESSAGE_CLIENT_EXIT.getCode().equals(message.getMesType())) {//說明客戶端想要退出,服務端要將socket關閉并退出線程就可以了//將客戶端對應的線程從集合中刪除ManagerServerConnectServerThread.remove(userId);//關閉socketsocket.close();System.out.println("用戶" + userId + "退出系統");//退出循環return;} else if (MessageType.MESSAGE_COMM_MES.getCode().equals(message.getMesType())) {//轉發給指定客戶端,假如說客戶不在線的話,可以保存到數據庫,這樣就可以實現離線留言Socket socket = ManagerServerConnectServerThread.getClientThread(message.getGetter()).getSocket();ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(message);oos.flush();} else {System.out.println("其他類型暫時不處理");}} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}//如果服務器端沒有發送消息過來,這個地方會堵塞,此線程會一直等待//讀取客戶端發送的User對象}}
}

1.4功能演示

客戶端A

image-20231208233050369

客戶端B

image-20231208233107188

服務端

image-20231208233117506

二、群發消息

將消息發送給所有的在線用戶

2.1 分析

客戶端A群發消息后,服務端會遍歷線程集合,將消息發送給除了客戶端A以外的所有客戶端,完成群發功能

2.2 客戶端

2.2.1 MessageClientService類

/*** 群發消息* @param userId 發送消息的用戶id* @param content 需要發送的內容*/
public void sendMessageToOnlineUser(String userId, String content) {Message message = new Message();message.setContent(content);message.setSender(userId);message.setSendTime(new Date().toString());message.setMesType(MessageType.MESSAGE_TO_ALL_EXIT.getCode());//普通消息System.out.println("用戶"+userId+"群發消息說:"+content);ClientConnectServerThread clientConnectServerThread = ManagerClientConnectServerThread.getClientConnectServerThread(userId);Socket socket = clientConnectServerThread.getSocket();//輸出消息try {ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(message);oos.flush();} catch (IOException e) {e.printStackTrace();}
}

2.2.2 ClientConnectServerThread 線程類

else if (MessageType.MESSAGE_TO_ALL_EXIT.getCode().equals(message.getMesType())) {//群發消息System.out.println("\n用戶"+message.getGetter()+"收到來自用戶"+message.getSender()+"的群發消息:"+message.getContent());}

2.3 服務端

2.3.1 ServerConnectClientThread 線程類

else if (MessageType.MESSAGE_TO_ALL_EXIT.getCode().equals(message.getMesType())) {//群發消息//遍歷線程集合取出所有線程對應的socket發送消息即可HashMap<String, ServerConnectClientThread> hm = ManagerServerConnectServerThread.getHm();Iterator<String> iterator = hm.keySet().iterator();while (iterator.hasNext()) {//取出在線人的idString onlineId = iterator.next();if (!onlineId.equals(message.getSender())) {ObjectOutputStream oos = new ObjectOutputStream(hm.get(onlineId).getSocket().getOutputStream());oos.writeObject(message);oos.flush();}}}

2.4 測試

客戶端A

客戶端B

image-20231209001512979

服務端

image-20231209001526468

三、發送文件

3.0 消息類擴展

@Data
public class Message implements Serializable {private static final long serialVersionUID = -3567747187962510012L;/*** 消息類型:發送文件、純文本、視頻聊天....*/private String mesType;/**發送者*/private String sender;/*** 接收者*/private String getter;/*** 消息內容*/private String content;/*** 發送時間*/private String sendTime;/*** 擴展好文件香菇那的成員變量*///字節數組存儲文件字節private byte[] fileBytes;//文件大小的長度初始化為0private int fileLen = 0;//文件的目的地是哪個位置private String dest;//傳輸的是哪個文件(原文件路徑)private String src;}

3.1 分析

image-20231209003348777

3.2 客戶端

3.2.1 FileClientService 文件傳輸類

向服務器發送文件

/*** 該類完成文件的傳輸*/
public class FileClientService {public void sendFileToOne(String src, String dest, String sender, String getter) {//讀取src文件Message message = new Message();message.setMesType(MessageType.MESSAGE_FILE_MES.getCode());message.setSender(sender);message.setGetter(getter);message.setSrc(src);message.setDest(dest);//需要將文件從客戶端讀取FileInputStream fileInputStream = null;byte[] fileBytes = new byte[(int) new File(src).length()];// 二進制流try {//讀取文件fileInputStream = new FileInputStream(src);//將src文件讀入到程序的字節數組中fileInputStream.read(fileBytes);//將文件對應的字節數粗設置到messagemessage.setFileBytes(fileBytes);} catch (Exception e) {e.printStackTrace();}finally {if (fileInputStream!=null){try {fileInputStream.close();} catch (IOException e) {e.printStackTrace();}}}//提示信息System.out.println("用戶" + sender + "向用戶" + getter + "發送文件" + src + "并存儲到對方電腦目錄" + dest);//向服務端發送Messagetry {ObjectOutputStream oos = new ObjectOutputStream(ManagerClientConnectServerThread.getClientConnectServerThread(sender).getSocket().getOutputStream());oos.writeObject(message);oos.flush();} catch (Exception e) {e.printStackTrace();}System.out.println("發送文件完畢");}}

3.2.2 ClientConnectServerThread 線程類接收文件

 else if (MessageType.MESSAGE_FILE_MES.getCode().equals(message.getMesType())) {System.out.println("用戶" + message.getGetter() + "收到用戶" + message.getSender() + "發送的文件" + message.getSrc() + "并存儲到我方電腦目錄" + message.getDest());FileOutputStream fileOutputStream = new FileOutputStream(message.getDest());fileOutputStream.write(message.getFileBytes());fileOutputStream.flush();fileOutputStream.close();System.out.println("保存文件成功");
}

3.3 服務端

3.3.1 ServerConnectClientThread 線程類

服務端起到一個轉發的作用而已

else if (MessageType.MESSAGE_FILE_MES.getCode().equals(message.getMesType())){System.out.println("用戶" + message.getSender() + "向用戶" + message.getGetter() + "發送文件" + message.getSrc() + "并存儲到對方電腦目錄" + message.getDest());//發送文件Socket socket = ManagerServerConnectServerThread.getClientThread(message.getGetter()).getSocket();ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(message);oos.flush();}

3.4 測試結果

客戶端A

image-20231209205819824

客戶端B

這個地方的用戶名錯了,就不截取第二次了

image-20231209205836682

服務端

image-20231209205852008

四、服務端推送新聞

4.1 分析

服務端推送新聞本質就是群發消息

在服務器啟動一條獨立的線程,專門負責發送推送新聞

image-20231209213009521

4.2 客戶端

4.2.1 ClientConnectServerThread 線程類

這個方法我們之前使用過

else if (MessageType.MESSAGE_TO_ALL_EXIT.getCode().equals(message.getMesType())) {//群發消息System.out.println("\n用戶收到來自用戶" + message.getSender() + "的群發消息:" + message.getContent());}

4.3 服務端

4.3.1 SendNewsAllService推送消息

/*** 發送新聞*/
public class SendNewsAllService implements Runnable {@Overridepublic void run() {//多次推送新聞,使用while循環while (true) {System.out.println("請輸入服務器要推送的信息/消息【輸入exit表示退出】");String content = Utility.readString(500);if ("exit".equals(content)) {break;}//構建消息類型Message message = new Message();message.setSender("服務器");message.setMesType(MessageType.MESSAGE_TO_ALL_EXIT.getCode());message.setContent(content);message.setSendTime(new Date().toString());System.out.println("服務器推送消息給所有人 說:" + content);//遍歷當前所有的通信線程得到socketHashMap<String, ServerConnectClientThread> hm = ManagerServerConnectServerThread.getHm();Iterator<String> iterator = hm.keySet().iterator();while (iterator.hasNext()) {String next = iterator.next();ServerConnectClientThread serverConnectClientThread = hm.get(next);try {//給每個用戶發送消息ObjectOutputStream objectOutputStream = new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());objectOutputStream.writeObject(message);objectOutputStream.flush();} catch (IOException e) {e.printStackTrace();}}}}
}

4.3.2 QQServer啟動線程

/*** 這是服務器,在監聽9999,等待客戶端的連接,并保持通信*/
@Data
public class QQServer {//創建一個集合存放多個用戶,如果是此用戶登錄,便認為是合法的//也可以使用ConcurrentHashMap,可以在并發的環境下處理(沒有線程安全問題)//HashMap是沒有處理線程安全的,因此在多線程情況下是不安全的private static HashMap<String, User> validUser = new HashMap<>();private ServerSocket serverSocket = null;/*** 進行類加載的時候會執行下面這個代碼*/static {validUser.put("100", new User("100", "123456"));validUser.put("200", new User("200", "123456"));validUser.put("300", new User("300", "123456"));validUser.put("至尊寶", new User("至尊寶", "123456"));validUser.put("紫霞仙子", new User("紫霞仙子", "123456"));validUser.put("菩提老祖", new User("菩提老祖", "123456"));}/*** 這是一個循環監聽的過程* 并不是客戶端A發送完信息服務器接收到后此服務器就關閉,而是一直監聽,因為還有可能其他客戶端發送過來信息*/public QQServer() {System.out.println("服務端在9999端口監聽....");//啟動推送新聞的線程new Thread(new SendNewsAllService()).start();ObjectInputStream ois = null;ObjectOutputStream oos = null;try {this.serverSocket = new ServerSocket(9999);//監聽是一直進行,當和某個客戶端連接后,會繼續監聽,因此使用while循環while (true) {//沒有客戶端連接9999端口時,程序會堵塞,等待連接Socket socket = serverSocket.accept();ois = new ObjectInputStream(socket.getInputStream());//如果服務器端沒有發送消息過來,這個地方會堵塞,此線程會一直等待//讀取客戶端發送的User對象User user = (User) ois.readObject();//創建Message對象,準備恢復客戶端Message message = new Message();oos = new ObjectOutputStream(socket.getOutputStream());//驗證用戶是否合法User userValid = validUser.get(user.getUserId());if (userValid != null && userValid.getUserId().equals(user.getUserId()) && userValid.getPasswd().equals(user.getPasswd())) {//合法用戶message.setMesType(MessageType.find(1));//給客戶端進行回復
//                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(message);oos.flush();//創建一個線程,和客戶端保持通信。//該線程需要持有Socket對象ServerConnectClientThread serverConnectClientThread = new ServerConnectClientThread(user.getUserId(), socket);serverConnectClientThread.start();//把該線程對象放入到一個集合中ManagerServerConnectServerThread.addClientThread(user.getUserId(), serverConnectClientThread);} else {//登錄失敗message.setMesType(MessageType.find(2));oos.writeObject(message);oos.flush();socket.close();}}} catch (IOException | ClassNotFoundException e) {e.printStackTrace();} finally {
//          如果服務端退出了while循環,說明服務器端不再監聽了,因此需要關閉資源if (serverSocket != null) {try {serverSocket.close();} catch (IOException e) {e.printStackTrace();}}if (ois != null) {try {ois.close();} catch (IOException e) {e.printStackTrace();}}if (oos != null) {try {oos.close();} catch (IOException e) {e.printStackTrace();}}}}
}

4.4 測試

服務端

image-20231209223129728

客戶端

image-20231209223147358

五、離線消息

客戶端A給離線客戶端B發送消息

我們可以在服務端創建一個集合,集合(HashMap就行)存放離線Message

對于集合的Key接收者的id,value是一個ArrayList,此ArrayList存放Message,因為客戶端A可以給離線用戶客戶端B發送多條消息

當客戶端B登錄之后,服務端會首先到此HashMap集合中讀取看看有沒有離線消息,如果有的話從服務端發送到客戶端B即可

image-20231209225316565

六、代碼總結

6.1 公共類代碼

6.1.1 消息類

@Data
public class Message implements Serializable {private static final long serialVersionUID = -3567747187962510012L;/*** 消息類型:發送文件、純文本、視頻聊天....*/private String mesType;/**發送者*/private String sender;/*** 接收者*/private String getter;/*** 消息內容*/private String content;/*** 發送時間*/private String sendTime;/*** 擴展好文件香菇那的成員變量*///字節數組存儲文件字節private byte[] fileBytes;//文件大小的長度初始化為0private int fileLen = 0;//文件的目的地是哪個位置private String dest;//傳輸的是哪個文件(原文件路徑)private String src;}

6.1.2 消息類型類

/*** 消息類型* 不同行亮的值表示不同的消息類型*/
@Getter
public enum MessageType {/*** 登錄成功*/MESSAGE_LOGIN_SUCCEED("1"),/*** 登錄失敗*/MESSAGE_LOGIN_FAIL("2"),/*** 普通信息對象*/MESSAGE_COMM_MES("3"),/*** 獲取在線用戶* 要求服務器返回在線用戶列表*/MESSAGE_GET_ONLINE_FRIEND("4"),/*** 服務器返回在線用戶列表*/MESSAGE_RETTURN_ONLINE_FRIEND("5"),/*** 客戶端請求退出*/MESSAGE_CLIENT_EXIT("6"),/*** 群發消息*/MESSAGE_TO_ALL_EXIT("7"),/*** 發送文件*/MESSAGE_FILE_MES("8"),;private final String code;MessageType(String code) {this.code = code;}public static String find(Integer code) {for (MessageType value : MessageType.values()) {if (code.toString().equals(value.getCode())) {return value.getCode();}}return null;}
}

6.1.3 客戶類

/*** 客戶信息*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {private static final long serialVersionUID = 4300366482842276408L;private String userId; //用戶idprivate String passwd; //用戶密碼
}

6.1.4 控制臺讀取類

public class Utility {private static Scanner scanner;static {scanner = new Scanner(System.in);}public Utility() {}public static char readMenuSelection() {while (true) {String str = readKeyBoard(1, false);char c = str.charAt(0);if (c == '1' || c == '2' || c == '3' || c == '4' || c == '5') {return c;}System.out.print("選擇錯誤,請重新輸入:");}}public static char readChar() {String str = readKeyBoard(1, false);return str.charAt(0);}public static char readChar(char defaultValue) {String str = readKeyBoard(1, true);return str.length() == 0 ? defaultValue : str.charAt(0);}public static int readInt() {while (true) {String str = readKeyBoard(2, false);try {int n = Integer.parseInt(str);return n;} catch (NumberFormatException var3) {System.out.println("數字輸入錯誤,請重新輸入:");}}}public static int readInt(int defaultValue) {while (true) {String str = readKeyBoard(2, true);if (str.equals("")) {return defaultValue;}try {int n = Integer.parseInt(str);return n;} catch (NumberFormatException var4) {System.out.print("數字輸入錯誤,請重新輸入:");}}}private static String readKeyBoard(int limit, boolean blankReturn) {String line = "";while (scanner.hasNextLine()) {line = scanner.nextLine();if (line.length() == 0) {if (blankReturn) {return line;}} else {if (line.length() >= 1 && line.length() <= limit) {break;}System.out.println("輸入長度(不大于" + limit + ")錯誤,請重新輸入:");}}return line;}public static String readString(int limit) {return readKeyBoard(limit, false);}public static char readConfirmSelection(){while (true){String str=readKeyBoard(1,false).toUpperCase();char c=str.charAt(0);if(c=='Y'||c=='N'){return c;}System.out.print("選擇錯誤,請重新輸入:");}}}

6.2 客戶端代碼

6.2.1 QQView 客戶端頁面

/*** 菜單界面*/
public class QQView {/*** 控制是否顯示菜單*/private boolean loop = true;/*** 接收用戶的鍵盤輸入*/private String key = "";/*** 完成用戶登錄驗證和用戶注冊等功能*/public UserClientService userClientService = new UserClientService();public MessageClientService messageClientService = new MessageClientService();private FileClientService fileClientService = new FileClientService();public static void main(String[] args) {QQView qqView = new QQView();qqView.mainMenu();System.out.println("退出客戶端系統");}/*** 顯示主菜單*/private void mainMenu() {while (loop) {System.out.println("***********歡迎登錄網絡通信系統*************");System.out.println("\t\t 1 登錄系統");System.out.println("\t\t 9 退出系統");System.out.print("請輸入你的選擇:");key = Utility.readString(1);//根據用戶的輸入來處理不同的邏輯switch (key) {case "1":System.out.print("請輸入用戶號");String userId = Utility.readString(50);System.out.print("請輸入密  碼");String password = Utility.readString(50);//TODO 到服務端驗證用戶是否合法if (userClientService.checkUser(userId,password)) {//進入二級菜單System.out.println(String.format("網絡通信系統二級菜單(用戶%s)", userId));while (loop) {System.out.println(String.format("\n========網絡通信系統二級菜單(用戶%s)===========", userId));System.out.println("\t\t 1.顯示在線用戶列表");System.out.println("\t\t 2.群發消息");System.out.println("\t\t 3.私聊消息");System.out.println("\t\t 4.發送文件");System.out.println("\t\t 9.退出系統");System.out.print("請輸入你的選擇:");key = Utility.readString(1);switch (key) {case "1"://獲取在線用戶列表userClientService.onlineFriendList();break;case "2"://群發消息System.out.print("請輸入想說的話:");String content = Utility.readString(100);messageClientService.sendMessageToOnlineUser(userId,content);break;case "3"://私發消息System.out.print("請輸入想聊天的在線用戶號:");//用戶號最長為50String getterId = Utility.readString(50);System.out.print("請輸入想說的話:");String contentToAll = Utility.readString(100);messageClientService.sendMessageToOne(contentToAll,userId,getterId);break;case "4":System.out.println("正在發送文件....");System.out.print("請輸入文件接收者:");String getter = Utility.readString(50);System.out.print("\n請輸入想要發送文件的路徑:");String src = Utility.readString(50);System.out.print("\n請輸入想要將文件存儲在對方哪里:");String dest = Utility.readString(50);fileClientService.sendFileToOne(src,dest,userId,getter);break;case "9":loop = false;//調用方法,給服務器發送一個退出系統的MessageSystem.out.println("退出系統");userClientService.logout();break;}}}else {System.out.println("登錄服務器失敗,用戶名或密碼存在問題");}break;case "9":loop = false;System.out.println("退出系統");}}}
}

6.2.2 ClientConnectServerThread線程類

@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ClientConnectServerThread extends Thread {//該線程需要持有Socket屬性private Socket socket;/*** 因為Thread需要在后臺跟我們的服務器進行通信(保持一個聯系),因此我們使用while循環來控制*/@Overridepublic void run() {while (true) {//一直讀取從服務器端回收的消息System.out.println("客戶端線程,等待讀取從服務端發送的消息....");try {ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());//如果服務器端沒有發送消息過來,這個地方會堵塞,此線程會一直等待//這就是一個堵塞式網絡編程,效率是相對比較低的Message message = (Message) ois.readObject();//判斷message的類型,然后做響應的業務處理if (message.getMesType().equals(MessageType.MESSAGE_RETTURN_ONLINE_FRIEND.getCode())) {//獲取在線用戶,取出在線列表信息并顯示String[] onlineUsers = message.getContent().split(" ");System.out.println("當前在線用戶列表如下");for (int i = 0; i < onlineUsers.length; i++) {System.out.println("用戶:" + onlineUsers[i]);}} else if (MessageType.MESSAGE_COMM_MES.getCode().equals(message.getMesType())) {//轉發給指定客戶端,假如說客戶不在線的話,可以保存到數據庫,這樣就可以實現離線留言System.out.println("\n用戶" + message.getGetter() + "收到來自用戶" + message.getSender() + "的消息:" + message.getContent());} else if (MessageType.MESSAGE_TO_ALL_EXIT.getCode().equals(message.getMesType())) {//群發消息System.out.println("\n用戶收到來自用戶" + message.getSender() + "的群發消息:" + message.getContent());} else if (MessageType.MESSAGE_FILE_MES.getCode().equals(message.getMesType())) {System.out.println("用戶" + message.getGetter() + "收到用戶" + message.getSender() + "發送的文件" + message.getSrc() + "并存儲到我方電腦目錄" + message.getDest());FileOutputStream fileOutputStream = new FileOutputStream(message.getDest());fileOutputStream.write(message.getFileBytes());fileOutputStream.flush();fileOutputStream.close();System.out.println("保存文件成功");} else {System.out.println("其他類型的message,暫時不處理");}} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}}}
}

6.2.3 ManagerClientConnectServerThread線程管理類

/*** 管理客戶端連接到服務端線程的一個類*/
public class ManagerClientConnectServerThread {//把多個線程放入一個HashMap中進行管理,key是用戶id,value是客戶端與服務端通信的線程private static HashMap<String, ClientConnectServerThread> hm = new HashMap<>();//將某個線程加入到集合中public static void addClientConnectServerThread(String userId, ClientConnectServerThread clientConnectServerThread) {hm.put(userId, clientConnectServerThread);}public static ClientConnectServerThread getClientConnectServerThread(String userId) {return hm.get(userId);}
}

6.2.4 MessageClientService發送消息類

/*** 該類提供和消息相關的服務方法*/
public class MessageClientService {/*** @param content  內容* @param senderId 發送用戶id* @param getterId 接收用戶id*/public void sendMessageToOne(String content, String senderId, String getterId) {//封裝消息Message message = new Message();message.setContent(content);message.setSender(senderId);message.setGetter(getterId);message.setSendTime(new Date().toString());message.setMesType(MessageType.MESSAGE_COMM_MES.getCode());//普通消息System.out.println("用戶"+senderId+"和用戶"+getterId+"說:"+content);//獲取senderId對應的socketClientConnectServerThread clientConnectServerThread = ManagerClientConnectServerThread.getClientConnectServerThread(senderId);Socket socket = clientConnectServerThread.getSocket();//輸出消息try {ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(message);oos.flush();} catch (IOException e) {e.printStackTrace();}}/*** 群發消息* @param userId 發送消息的用戶id* @param content 需要發送的內容*/public void sendMessageToOnlineUser(String userId, String content) {Message message = new Message();message.setContent(content);message.setSender(userId);message.setSendTime(new Date().toString());message.setMesType(MessageType.MESSAGE_TO_ALL_EXIT.getCode());//普通消息System.out.println("用戶"+userId+"群發消息說:"+content);ClientConnectServerThread clientConnectServerThread = ManagerClientConnectServerThread.getClientConnectServerThread(userId);Socket socket = clientConnectServerThread.getSocket();//輸出消息try {ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(message);oos.flush();} catch (IOException e) {e.printStackTrace();}}
}

6.2.5 FileClientService發送文件類

/*** 該類完成文件的傳輸*/
public class FileClientService {public void sendFileToOne(String src, String dest, String sender, String getter) {//讀取src文件Message message = new Message();message.setMesType(MessageType.MESSAGE_FILE_MES.getCode());message.setSender(sender);message.setGetter(getter);message.setSrc(src);message.setDest(dest);//需要將文件從客戶端讀取FileInputStream fileInputStream = null;byte[] fileBytes = new byte[(int) new File(src).length()];// 二進制流try {//讀取文件fileInputStream = new FileInputStream(src);//將src文件讀入到程序的字節數組中fileInputStream.read(fileBytes);//將文件對應的字節數粗設置到messagemessage.setFileBytes(fileBytes);} catch (Exception e) {e.printStackTrace();}finally {if (fileInputStream!=null){try {fileInputStream.close();} catch (IOException e) {e.printStackTrace();}}}//提示信息System.out.println("用戶" + sender + "向用戶" + getter + "發送文件" + src + "并存儲到對方電腦目錄" + dest);//向服務端發送Messagetry {ObjectOutputStream oos = new ObjectOutputStream(ManagerClientConnectServerThread.getClientConnectServerThread(sender).getSocket().getOutputStream());oos.writeObject(message);oos.flush();} catch (Exception e) {e.printStackTrace();}System.out.println("發送文件完畢");}}

6.2.6 UserClientService 用戶登錄驗證類

/*** 完成用戶登錄驗證和用戶注冊等功能*/
@Data
public class UserClientService {//其他地方也會使用user信息,所以將其作為一個屬性private User user = new User();private Socket socket = null;/***根據userId和pwd到服務器驗證該用戶是否合法*/public boolean checkUser(String userId, String pwd) {//臨時變量b,用戶是否合法的標志boolean b = false;//TODO 創建User對象user.setUserId(userId);user.setPasswd(pwd);try {//TODO 連接到服務端,發送User對象socket = new Socket(InetAddress.getByName("127.0.0.1"), 9999);//得到ObjectOutputStream對象流(序列化流,也是字節流中一種)ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(user);oos.flush();//TODO 讀取從服務器回復的Message對象ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());Message msg = (Message) ois.readObject();if (MessageType.find(1).equals(msg.getMesType())) {//登錄成功//一旦登錄成功,我們需要啟動一個線程維護或者持有此socket,保持此線程可以跟我們服務器端一直進行通信//不啟動線程的話此Socket不好維護。如果我們有數據發送或者接收,我們可以從這個線程里面進行拉取//為什么將Socket放入一個線程中管理?// 1.如果不創建這個線程的話,一個客戶端會有多個socket,socket管理起來就比較麻煩// 2.需要socket不斷的從數據通道中讀寫數據,所以也必須做成一個線程ClientConnectServerThread ccst = new ClientConnectServerThread(socket);//啟動客戶端的線程ccst.start();//為了后面客戶端的擴展,我們將線程放入到集合中管理ManagerClientConnectServerThread.addClientConnectServerThread(userId, ccst);b = true;} else {//登錄失敗//我們是有Socket的,但是沒有線程,即登錄失敗,不能啟動和服務器通信的線程//關閉socketsocket.close();}} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}return b;}/*** 向服務器端請求在線用戶列表*/public void onlineFriendList(){//發送一個message,并且消息的類型是MESSAGE_GET_ONLINE_FRIENDMessage message = new Message();message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND.getCode());message.setSender(user.getUserId());//發送給服務器//得到當前線程的Socket對應的ObjectOutputStream//clientConnectServerThread線程一直在運行過程中,監聽從服務器傳輸過來的消息ClientConnectServerThread clientConnectServerThread = ManagerClientConnectServerThread.getClientConnectServerThread(user.getUserId());try {ObjectOutputStream oos = new ObjectOutputStream(clientConnectServerThread.getSocket().getOutputStream());oos.writeObject(message);oos.flush();} catch (IOException e) {e.printStackTrace();}}/*** 編寫方法退出客戶端,并給服務端發送一個退出系統的Message對象*/public void logout(){Message message = new Message();message.setMesType(MessageType.MESSAGE_CLIENT_EXIT.getCode());// 要退出這個用戶message.setSender(user.getUserId());ClientConnectServerThread clientConnectServerThread = ManagerClientConnectServerThread.getClientConnectServerThread(user.getUserId());try {ObjectOutputStream oos = new ObjectOutputStream(clientConnectServerThread.getSocket().getOutputStream());oos.writeObject(message);oos.flush();System.exit(0);} catch (IOException e) {e.printStackTrace();}}/****/
}

6.3 服務端代碼

6.3.1 后臺啟動

/*** 此類創建一個QQServer對象,啟動后臺的服務*/
public class QQFrame {public static void main(String[] args) {//創建QQServer對象,會啟動QQServer構造器QQServer qqServer = new QQServer();}
}

6.3.2 服務器

/*** 這是服務器,在監聽9999,等待客戶端的連接,并保持通信*/
@Data
public class QQServer {//創建一個集合存放多個用戶,如果是此用戶登錄,便認為是合法的//也可以使用ConcurrentHashMap,可以在并發的環境下處理(沒有線程安全問題)//HashMap是沒有處理線程安全的,因此在多線程情況下是不安全的private static HashMap<String, User> validUser = new HashMap<>();private ServerSocket serverSocket = null;/*** 進行類加載的時候會執行下面這個代碼*/static {validUser.put("100", new User("100", "123456"));validUser.put("200", new User("200", "123456"));validUser.put("300", new User("300", "123456"));validUser.put("至尊寶", new User("至尊寶", "123456"));validUser.put("紫霞仙子", new User("紫霞仙子", "123456"));validUser.put("菩提老祖", new User("菩提老祖", "123456"));}/*** 這是一個循環監聽的過程* 并不是客戶端A發送完信息服務器接收到后此服務器就關閉,而是一直監聽,因為還有可能其他客戶端發送過來信息*/public QQServer() {System.out.println("服務端在9999端口監聽....");//啟動推送新聞的線程new Thread(new SendNewsAllService()).start();ObjectInputStream ois = null;ObjectOutputStream oos = null;try {this.serverSocket = new ServerSocket(9999);//監聽是一直進行,當和某個客戶端連接后,會繼續監聽,因此使用while循環while (true) {//沒有客戶端連接9999端口時,程序會堵塞,等待連接Socket socket = serverSocket.accept();ois = new ObjectInputStream(socket.getInputStream());//如果服務器端沒有發送消息過來,這個地方會堵塞,此線程會一直等待//讀取客戶端發送的User對象User user = (User) ois.readObject();//創建Message對象,準備恢復客戶端Message message = new Message();oos = new ObjectOutputStream(socket.getOutputStream());//驗證用戶是否合法User userValid = validUser.get(user.getUserId());if (userValid != null && userValid.getUserId().equals(user.getUserId()) && userValid.getPasswd().equals(user.getPasswd())) {//合法用戶message.setMesType(MessageType.find(1));//給客戶端進行回復
//                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(message);oos.flush();//創建一個線程,和客戶端保持通信。//該線程需要持有Socket對象ServerConnectClientThread serverConnectClientThread = new ServerConnectClientThread(user.getUserId(), socket);serverConnectClientThread.start();//把該線程對象放入到一個集合中ManagerServerConnectServerThread.addClientThread(user.getUserId(), serverConnectClientThread);} else {//登錄失敗message.setMesType(MessageType.find(2));oos.writeObject(message);oos.flush();socket.close();}}} catch (IOException | ClassNotFoundException e) {e.printStackTrace();} finally {
//          如果服務端退出了while循環,說明服務器端不再監聽了,因此需要關閉資源if (serverSocket != null) {try {serverSocket.close();} catch (IOException e) {e.printStackTrace();}}if (ois != null) {try {ois.close();} catch (IOException e) {e.printStackTrace();}}if (oos != null) {try {oos.close();} catch (IOException e) {e.printStackTrace();}}}}
}

6.3.3 ServerConnectClientThread線程類

/*** 該類對應的對象和某個客戶端保持通信*/
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ServerConnectClientThread extends Thread {/*** 可以區分此socket是和哪個用戶進行關聯的*/private String userId;//連接到服務端的這個用戶idprivate Socket socket;/*** 線程處于run狀態,可以發送或者接收客戶端的消息*/@Overridepublic void run() {//不斷的從socket中讀數據和寫數據while (true) {System.out.println("服務端和客戶端保持通信,讀取數據.... userId:" + userId);ObjectInputStream ois = null;try {ois = new ObjectInputStream(socket.getInputStream());//讀取數據Message message = (Message) ois.readObject();//根據Message的類型,判斷客戶端想要執行什么操作if (MessageType.MESSAGE_GET_ONLINE_FRIEND.getCode().equals(message.getMesType())) {System.out.println("用戶" + userId + "獲取在線用戶");//拉取在線用戶(客戶端要拉取在線用戶列表)Socket socket = ManagerServerConnectServerThread.getClientThread(userId).getSocket();ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());//構建Message發送給服務端Message returnMessage = new Message();returnMessage.setMesType(MessageType.MESSAGE_RETTURN_ONLINE_FRIEND.getCode());returnMessage.setContent(ManagerServerConnectServerThread.getOnlineUser());//說明要發送給誰returnMessage.setGetter(message.getSender());//返回給客戶端oos.writeObject(returnMessage);oos.flush();} else if (MessageType.MESSAGE_CLIENT_EXIT.getCode().equals(message.getMesType())) {//說明客戶端想要退出,服務端要將socket關閉并退出線程就可以了//將客戶端對應的線程從集合中刪除ManagerServerConnectServerThread.remove(userId);//關閉socketsocket.close();System.out.println("用戶" + userId + "退出系統");//退出循環return;} else if (MessageType.MESSAGE_COMM_MES.getCode().equals(message.getMesType())) {//轉發給指定客戶端,假如說客戶不在線的話,可以保存到數據庫,這樣就可以實現離線留言Socket socket = ManagerServerConnectServerThread.getClientThread(message.getGetter()).getSocket();ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(message);oos.flush();} else if (MessageType.MESSAGE_TO_ALL_EXIT.getCode().equals(message.getMesType())) {//群發消息//遍歷線程集合取出所有線程對應的socket發送消息即可HashMap<String, ServerConnectClientThread> hm = ManagerServerConnectServerThread.getHm();Iterator<String> iterator = hm.keySet().iterator();while (iterator.hasNext()) {//取出在線人的idString onlineId = iterator.next();if (!onlineId.equals(message.getSender())) {ObjectOutputStream oos = new ObjectOutputStream(hm.get(onlineId).getSocket().getOutputStream());oos.writeObject(message);oos.flush();}}}else if (MessageType.MESSAGE_FILE_MES.getCode().equals(message.getMesType())){System.out.println("用戶" + message.getSender() + "向用戶" + message.getGetter() + "發送文件" + message.getSrc() + "并存儲到對方電腦目錄" + message.getDest());//發送文件Socket socket = ManagerServerConnectServerThread.getClientThread(message.getGetter()).getSocket();ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(message);oos.flush();}else {System.out.println("其他類型暫時不處理");}} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}//如果服務器端沒有發送消息過來,這個地方會堵塞,此線程會一直等待//讀取客戶端發送的User對象}}
}

6.3.4 ManagerServerConnectServerThread管理線程類

/*** 該類用于管理和客戶端通信的線程*/
@Data
public class ManagerServerConnectServerThread {private static HashMap<String, ServerConnectClientThread> hm = new HashMap<>();public static HashMap<String, ServerConnectClientThread> getHm() {return hm;}/*** 添加線程對象到hm集合*/public static void addClientThread(String userId, ServerConnectClientThread clientConnectServerThread) {hm.put(userId, clientConnectServerThread);}/*** 從集合中獲取對應線程對象*/public static ServerConnectClientThread getClientThread(String userId) {return hm.get(userId);}/*** 獲取在線用戶*/public static String getOnlineUser() {//集合遍歷,遍歷hashMap的keyIterator<String> iterator = hm.keySet().iterator();String onlineUserList = "";while (iterator.hasNext()) {onlineUserList += iterator.next().toString() + " ";}return onlineUserList;}/*** 從集合中刪除掉某個線程對象*/public static void remove(String userId) {hm.remove(userId);}}

6.3.5 SendNewsAllService 推送新聞類

/*** 發送新聞*/
public class SendNewsAllService implements Runnable {@Overridepublic void run() {//多次推送新聞,使用while循環while (true) {System.out.println("請輸入服務器要推送的信息/消息【輸入exit表示退出】");String content = Utility.readString(500);if ("exit".equals(content)) {break;}//構建消息類型Message message = new Message();message.setSender("服務器");message.setMesType(MessageType.MESSAGE_TO_ALL_EXIT.getCode());message.setContent(content);message.setSendTime(new Date().toString());System.out.println("服務器推送消息給所有人 說:" + content);//遍歷當前所有的通信線程得到socketHashMap<String, ServerConnectClientThread> hm = ManagerServerConnectServerThread.getHm();Iterator<String> iterator = hm.keySet().iterator();while (iterator.hasNext()) {String next = iterator.next();ServerConnectClientThread serverConnectClientThread = hm.get(next);try {//給每個用戶發送消息ObjectOutputStream objectOutputStream = new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());objectOutputStream.writeObject(message);objectOutputStream.flush();} catch (IOException e) {e.printStackTrace();}}}}
}

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

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

相關文章

1.1.1.多線程的發展--對cpu性能的壓榨史

一.壓榨歷史 1.單進程人工切換。紙帶機。只能解決簡單的數學問題。 2.單道批處理。多進程批處理。多個任務批量執行。解決手動操作時需要人工切換作業導致的系統利用率低的問題 3.多進程并行處理。把程序寫在不同的內存位置來回切換。當一個作業在等待I/O處理時&#xff0c;…

C語言-函數STRCPY

strcpy char *strcpy(char *restrict dst, const char *restrict src);把src的字符串拷貝到dst restrict表明src和dst不重疊&#xff08;C99&#xff09; 返回dst 為了能鏈起代碼來 復制一個字符串 char dst (char)malloc(strlen(src) 1); strcpy(dst, src);

從單向鏈表中刪除指定值的節點

輸入一個單向鏈表和一個節點的值&#xff0c;從單向鏈表中刪除等于該值的節點&#xff0c;刪除后如果鏈表中無節點則返回空指針。 鏈表的值不能重復。構造過程&#xff0c;例如輸入一行數據為:6 2 1 2 3 2 5 1 4 5 7 2 2則第一個參數6表示輸入總共6個節點&#xff0c;第二個參數…

通過仿真理解完整的陣列信號噪聲模型

概要 噪聲對無線電設備的信號接收會造成影響,是通信、雷達、導航、遙感等工程應用領域中的關鍵考慮因素。通常認為陣列合成能夠提升信噪比,但忽略了這一論斷的前提,即不同通道引入的噪聲互不相關。但實際應用中,接收的噪聲不僅僅包含信道引入的不相關噪聲,還包含從外界環…

1-6、編程語言排行榜

語雀原文鏈接 https://www.tiobe.com/tiobe-index/

IntelliJ IDEA創建一個Maven項目

在IDEA中創建Maven項目&#xff0c;前提是已經安裝配置好Maven環境 。 本文主要使用的是IntelliJ IDEA 2022.2.1 (Community Edition) 1.創建一個新project:File>Project 2.修改Maven配置&#xff1a;File>Settings>搜索maven 創建好的工程如下&#xff1a; src/main…

使用NanoPi NEO4進行rtsp拉流

使用系統&#xff1a;FriendlyDesktop系統 使用python進行編程&#xff0c;分別使用opencv與ffmpeg進行功能實現&#xff0c;折騰了挺長時間&#xff0c;代碼很簡單&#xff0c;主要是環境搭建。主要是python、opencv-python、ffmpeg-python、numpy之間的版本兼容&#xff0c;…

Chart 8 內核優化

文章目錄 前言8.1 內核融合和拆分8.2 編譯選項8.3 Conformant&#xff08;規范&#xff09; vs. fast vs. native math functions8.4 Loop unrolling8.5 避免分支發散8.6 Handle image boundaries8.7 Avoid the use of size_t8.8 通用 vs. 具名內存地址空間8.9 Subgroup8.10 Us…

七個常用<python裝飾器>---足夠改進代碼質量 (保姆詳解)

前言: 寫代碼嘛&#xff0c;關鍵是得讓它既好用又好看&#xff0c;這不&#xff0c;Python裝飾器就擺在那兒。咱們程序員有時也得有那么點藝術家的腔調&#xff1a;講求效率&#xff0c;追求代碼的簡潔優雅&#xff0c;偶爾還得裝裝X&#xff0c;不是嗎&#xff1f; 翻開人家…

SpringSecurity6 | 自定義認證規則

?作者簡介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;熱愛Java后端開發者&#xff0c;一個想要與大家共同進步的男人&#x1f609;&#x1f609; &#x1f34e;個人主頁&#xff1a;Leo的博客 &#x1f49e;當前專欄&#xff1a; Java從入門到精通 ?特色專欄&#xf…

移相干涉技術1-多種干涉條紋仿真模擬生成(原理轉載+代碼實現 包括模擬生成干涉條紋圖)

過去的干涉測量技術是通過人的肉眼或者相機拍攝&#xff0c;來直觀判斷干涉圖中條紋特征進而完成測量&#xff0c;該方法的不穩定因素&#xff08;比如人的主觀意志&#xff09;很多&#xff0c;其精度誤差在/10左右38]&#xff1b;現代干涉測量技術通過將電子技術、計算機技術…

智能優化算法應用:基于廚師算法無線傳感器網絡(WSN)覆蓋優化 - 附代碼

智能優化算法應用&#xff1a;基于廚師算法無線傳感器網絡(WSN)覆蓋優化 - 附代碼 文章目錄 智能優化算法應用&#xff1a;基于廚師算法無線傳感器網絡(WSN)覆蓋優化 - 附代碼1.無線傳感網絡節點模型2.覆蓋數學模型及分析3.廚師算法4.實驗參數設定5.算法結果6.參考文獻7.MATLAB…

代碼隨想錄-刷題第二十一天

530.二叉搜索樹的最小絕對差 題目鏈接&#xff1a;530. 二叉搜索樹的最小絕對差 思路&#xff1a;二叉搜索樹的中序遍歷是有序的。根據二叉搜索樹的這個特性來解題。 class Solution {// 同樣利用二叉樹中序遍歷是一個有序數組。private List<Integer> list new Arra…

一加 12 Pop-up快閃活動來襲,十城聯動火爆開啟

12 月 9 日&#xff0c;一加 12 Pop-up 快閃活動在北京、深圳、上海、廣州等十城聯動開啟&#xff0c;各地加油歡聚快閃現場&#xff0c;搶先體驗與購買一加 12。作為一加十年超越之作&#xff0c;一加 12 全球首發擁有醫療級護眼方案和行業第一 4500nit 峰值亮度的 2K 東方屏、…

C++新經典模板與泛型編程:策略類模板

策略類模板 在前面的博文中&#xff0c;策略類SumPolicy和MinPolicy都是普通的類&#xff0c;其中包含的是一個靜態成員函數模板algorithm()&#xff0c;該函數模板包含兩個類型模板參數。其實&#xff0c;也可以把SumPolicy和MinPolicy類寫成類模板—直接把algorithm()中的兩…

【Linux】無法使用 ifconfig 查看系統網絡接口信息,報錯 command not found: ifconfig

問題描述 ifconfig是一個用于配置和顯示系統網絡接口信息的命令行工具。它通常用于Unix、Linux和其他類Unix系統中。 通過ifconfig命令&#xff0c;你可以查看和修改系統中網絡接口的配置信息&#xff0c;包括IP地址、子網掩碼、MAC地址、MTU&#xff08;最大傳輸單元&#x…

javacv踩坑記錄

前一陣學習opencv&#xff0c;發現在做人臉識別的時候遇到一些類庫不存在的情況&#xff0c;查找后發現是由于拓展包沒有安裝完全&#xff08;僅安裝了基礎版&#xff09;。由于網絡的問題&#xff08;初步猜測&#xff09;&#xff0c;始終無法安裝好拓展包。 于是另辟蹊徑&am…

MongoDb數據庫

一、命令交互 1.1 數據庫命令 1.顯示所有數據庫&#xff1a; show dbs 2.切換到指定數據庫&#xff0c;如果沒有則自動創建數據庫 use databaseName 3.顯示當前所在數據庫 db 4.刪除當前數據庫 use 庫名 db.dropDatabase() 1.2 集合命令 1.創建集合 db.createColl…

[文檔級關系抽取|ACL論文]文檔級關系抽取中語言理解的基礎模型

Did the Models Understand Documents? Benchmarking Models for Language Understanding in Document-Level Relation Extraction School of Computer Science, Fudan University | ACL 2023.06 | 原文鏈接 Background 過去的工作大多數都是從單個句子中收獲更多的關系&am…

MongoDB中的$type操作符和limit與skip方法

本文主要介紹MongoDB中的$type操作符和limit與skip方法。 目錄 MongoDB的$type操作符MongoDB的limit方法MongoDB的skip方法 MongoDB的$type操作符 MongoDB中的$type操作符用于檢查一個字段的類型是否與指定的類型相匹配。它可以用于查詢和投影操作。 $type操作符可以與以下數…