day02-代碼實現01

来源:https://www.cnblogs.com/liyuelian/archive/2022/09/21/16717711.html
-Advertisement-
Play Games

多用戶即時通訊系統02 4.編碼實現01 4.1功能實現-用戶登錄 4.1.1功能說明 因為還沒有學習資料庫,我們人為規定 用戶名/id = 100,密碼為 123456 就可以登錄,其他用戶不能登錄,後面使用HashMap模擬資料庫,這樣就可以多個用戶登錄。 4.1.2思路分析+框架圖 用戶的登錄 ...


多用戶即時通訊系統02

4.編碼實現01

4.1功能實現-用戶登錄

4.1.1功能說明

因為還沒有學習資料庫,我們人為規定 用戶名/id = 100,密碼為 123456 就可以登錄,其他用戶不能登錄,後面使用HashMap模擬資料庫,這樣就可以多個用戶登錄。

image-20220920184736385

4.1.2思路分析+框架圖

image-20220921230655619

用戶的登錄功能的流程:

  1. 用戶進入系統界面,選擇登錄

  2. 輸入登錄信息之後,客戶端與服務端建立連接,把信息發送給服務端

  3. 服務端接收信息,在資料庫中進行校驗,作出判斷

  4. 服務端將判斷返回客戶端

  5. 客戶端接收信息後,進行下一步操作(成功則進入二級菜單,失敗則請求用戶重新輸入)

4.1.3代碼實現

4.1.3.1客戶端代碼

image-20220921233227770 image-20220922000812144

1.User類

用戶輸入登錄信息後,在客戶端發送信息給服務端的過程中,為了方便數據的解析(比如用戶id、用戶密碼等),使用對象來進行數據的傳輸

package qqcommon;

import java.io.Serializable;

/**
 * @author 李
 * @version 1.0
 * 表示一個用戶信息
 */
public class User implements Serializable {//要序列化某個對象,實現介面Serializable
    private static final long serialVersionUID = 1L;//聲明序列化版本號,提高相容性
    private String userId;//用戶id/用戶名
    private String password;//用戶密碼

    public User() {
    }

    public User(String uerId, String password) {
        this.userId = uerId;
        this.password = password;
    }

    public String getUerId() {
        return userId;
    }

    public void setUerId(String uerId) {
        this.userId = uerId;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
2.Message類

表示客戶端和伺服器端通訊時的消息對象,目的同User

package qqcommon;

import java.io.Serializable;

/**
 * @author 李
 * @version 1.0
 * 表示客戶端和伺服器端通訊時的消息對象
 */
public class Message implements Serializable {
    private static final long serialVersionUID = 1L;//聲明序列化版本號,提高相容性
    //因為客戶端之間的通信都要依靠服務端,因此信息必須要寫明接收者和發送者等
    private String sender;//發送者
    private String getter;//接收者
    private String content;//消息內容
    private String sendTime;//發送時間  -因為發送時間也要被序列化,因此這裡也用String類型
    private String mesType;//消息類型[可以在介面中定義消息類型]

    public String getMesType() {
        return mesType;
    }

    public void setMesType(String mesType) {
        this.mesType = mesType;
    }

    public String getSender() {
        return sender;
    }

    public void setSender(String sender) {
        this.sender = sender;
    }

    public String getGetter() {
        return getter;
    }

    public void setGetter(String getter) {
        this.getter = getter;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getSendTime() {
        return sendTime;
    }

    public void setSendTime(String sendTime) {
        this.sendTime = sendTime;
    }
}
3.MessageType介面
package qqcommon;

/**
 * @author 李
 * @version 1.0
 * 表示消息類型
 */
public interface MessageType {
    //在介面中定義類一些常量,不同的常量的表示不同的消息類型
    String MESSAGE_LOGIN_SUCCEED = "1";//表示登錄成功
    String MESSAGE_LOGIN_FAIL = "2";//表示登錄失敗

}
4.QQView類

主程式入口,顯示菜單

package qqclient.view;


import qqclient.service.UserClientService;
import qqclient.utils.Utility;

/**
 * @author 李
 * @version 1.0
 */
public class QQView {
    private boolean loop = true;//控制是否顯示菜單
    private String key = "";//用來接收用戶的鍵盤輸入
    private UserClientService userClientService = new UserClientService();//該對象用於登錄服務/註冊用戶

    public static void main(String[] args) {
        new QQView().mainMenu();
        System.out.println("客戶端退出系統......");
    }

    //顯示主菜單
    public 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 pwd = Utility.readString(50);

                    // 到服務端去驗證用戶是否合法
                    //這裡有很多代碼,我們這裡編寫一個類UserClientService[提供用戶登錄/註冊等功能]
                    if (userClientService.checkUser(userId, pwd)) {//驗證成功
                        System.out.println("=========歡迎(用戶 " + userId + " 登錄成功)=========");
                        //進入到二級菜單
                        while (loop) {
                            System.out.println("\n=========網路通訊系統二級菜單(用戶 " + 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":
                                    System.out.println("顯示線上用戶列表");
                                    break;
                                case "2":
                                    System.out.println("群發消息");
                                    break;
                                case "3":
                                    System.out.println("私聊消息");
                                    break;
                                case "4":
                                    System.out.println("發送文件");
                                    break;
                                case "9":
                                    loop = false;//退出迴圈
                                    break;
                            }
                        }
                    } else {//驗證失敗
                        System.out.println("=========登錄失敗========");
                    }
                    break;
                case "9":
                    loop = false;//退出迴圈
                    break;
            }
        }
    }
}
5.Utility類

工具類,用於處理各種情況的用戶輸入,並且能夠按照程式員的需求,得到用戶的控制台輸入。

package qqclient.utils;


/**
 * 工具類的作用:
 * 處理各種情況的用戶輸入,並且能夠按照程式員的需求,得到用戶的控制台輸入。
 */

import java.util.Scanner;

/**


 */
public class Utility {
    //靜態屬性。。。
    private static Scanner scanner = new Scanner(System.in);


    /**
     * 功能:讀取鍵盤輸入的一個菜單選項,值:1——5的範圍
     * @return 1——5
     */
    public static char readMenuSelection() {
        char c;
        for (; ; ) {
            String str = readKeyBoard(1, false);//包含一個字元的字元串
            c = str.charAt(0);//將字元串轉換成字元char類型
            if (c != '1' && c != '2' &&
                    c != '3' && c != '4' && c != '5') {
                System.out.print("選擇錯誤,請重新輸入:");
            } else break;
        }
        return c;
    }

    /**
     * 功能:讀取鍵盤輸入的一個字元
     * @return 一個字元
     */
    public static char readChar() {
        String str = readKeyBoard(1, false);//就是一個字元
        return str.charAt(0);
    }

    /**
     * 功能:讀取鍵盤輸入的一個字元,如果直接按回車,則返回指定的預設值;否則返回輸入的那個字元
     * @param defaultValue 指定的預設值
     * @return 預設值或輸入的字元
     */

    public static char readChar(char defaultValue) {
        String str = readKeyBoard(1, true);//要麼是空字元串,要麼是一個字元
        return (str.length() == 0) ? defaultValue : str.charAt(0);
    }

    /**
     * 功能:讀取鍵盤輸入的整型,長度小於2位
     * @return 整數
     */
    public static int readInt() {
        int n;
        for (; ; ) {
            String str = readKeyBoard(10, false);//一個整數,長度<=10位
            try {
                n = Integer.parseInt(str);//將字元串轉換成整數
                break;
            } catch (NumberFormatException e) {
                System.out.print("數字輸入錯誤,請重新輸入:");
            }
        }
        return n;
    }

    /**
     * 功能:讀取鍵盤輸入的 整數或預設值,如果直接回車,則返回預設值,否則返回輸入的整數
     * @param defaultValue 指定的預設值
     * @return 整數或預設值
     */
    public static int readInt(int defaultValue) {
        int n;
        for (; ; ) {
            String str = readKeyBoard(10, true);
            if (str.equals("")) {
                return defaultValue;
            }

            //異常處理...
            try {
                n = Integer.parseInt(str);
                break;
            } catch (NumberFormatException e) {
                System.out.print("數字輸入錯誤,請重新輸入:");
            }
        }
        return n;
    }

    /**
     * 功能:讀取鍵盤輸入的指定長度的字元串
     * @param limit 限制的長度
     * @return 指定長度的字元串
     */

    public static String readString(int limit) {
        return readKeyBoard(limit, false);
    }

    /**
     * 功能:讀取鍵盤輸入的指定長度的字元串或預設值,如果直接回車,返回預設值,否則返回字元串
     * @param limit 限制的長度
     * @param defaultValue 指定的預設值
     * @return 指定長度的字元串
     */

    public static String readString(int limit, String defaultValue) {
        String str = readKeyBoard(limit, true);
        return str.equals("") ? defaultValue : str;
    }


    /**
     * 功能:讀取鍵盤輸入的確認選項,Y或N
     * 將小的功能,封裝到一個方法中.
     * @return Y或N
     */
    public static char readConfirmSelection() {
        System.out.println("請輸入你的選擇(Y/N): 請小心選擇");
        char c;
        for (; ; ) {//無限迴圈
            //在這裡,將接受到字元,轉成了大寫字母
            //y => Y n=>N
            String str = readKeyBoard(1, false).toUpperCase();
            c = str.charAt(0);
            if (c == 'Y' || c == 'N') {
                break;
            } else {
                System.out.print("選擇錯誤,請重新輸入:");
            }
        }
        return c;
    }

    /**
     * 功能: 讀取一個字元串
     * @param limit 讀取的長度
     * @param blankReturn 如果為true ,表示 可以讀空字元串。
     *                   如果為false表示 不能讀空字元串。
     *
     * 如果輸入為空,或者輸入大於limit的長度,就會提示重新輸入。
     * @return
     */
    private static String readKeyBoard(int limit, boolean blankReturn) {

        //定義了字元串
        String line = "";

        //scanner.hasNextLine() 判斷有沒有下一行
        while (scanner.hasNextLine()) {
            line = scanner.nextLine();//讀取這一行

            //如果line.length=0, 即用戶沒有輸入任何內容,直接回車
            if (line.length() == 0) {
                if (blankReturn) return line;//如果blankReturn=true,可以返回空串
                else continue; //如果blankReturn=false,不接受空串,必須輸入內容
            }

            //如果用戶輸入的內容大於了 limit,就提示重寫輸入
            //如果用戶如的內容 >0 <= limit ,我就接受
            if (line.length() < 1 || line.length() > limit) {
                System.out.print("輸入長度(不能大於" + limit + ")錯誤,請重新輸入:");
                continue;
            }
            break;
        }

        return line;
    }
}
6.UserClientService類

該類完成用戶登錄驗證和用戶註冊等功能

package qqclient.service;

import qqcommon.Message;
import qqcommon.MessageType;
import qqcommon.User;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;

/**
 * @author 李
 * @version 1.0
 * 該類完成用戶登錄驗證和用戶註冊等功能
 */
public class UserClientService {
    //因為我們可能在其他地方使用User信息,因此做成成員屬性
    private User u = new User();
    //因為可能在其他地方使用Socket,因此也做成成員屬性
    private Socket socket;

    //根據用戶輸入的 userId 和 pwd,到伺服器去驗證該用戶是否合法
    public boolean checkUser(String userId, String pwd) {
        boolean b = false;
        //創建User對象
        u.setUerId(userId);
        u.setPassword(pwd);

        try {
            //連接伺服器,發送u對象
            socket = new Socket(InetAddress.getByName("192.168.1.6"), 9999);//指定服務端的ip和埠
            //獲取ObjectOutputStream對象(對象輸出流)
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(u);//向服務端發送User對象,伺服器會進行驗證

            //socket.shutdownOutput();

            //伺服器驗證後,客戶端讀取從服務端回送的Message對象
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            Message ms = (Message) ois.readObject();//強轉為Message類型

            /**取出服務端返回的Message對象中的getMesType屬性
             * 如果為MESSAGE_LOGIN_SUCCEED則說明登錄成功,
             * 否則登錄失敗
             * */
            if (ms.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCEED)) {//登錄成功
                //創建一個伺服器保持通信的線程
                // -->創建一個類 ClientConnectServerThread,
                // 把socket傳到該線程裡面,然後把線程放到一個集合裡面去管理
                ClientConnectServerThread clientConnectServerThread = new ClientConnectServerThread(socket);
                //啟動客戶端的線程
                clientConnectServerThread.start();
                //這裡為了後面客戶端的擴展,我們將線程放入到集合裡面
                ManageClientConnectServerThread.addClientConnectServerThread(userId, clientConnectServerThread);
                b = true;
            } else {//登錄失敗
                //如果登錄失敗,就不啟動和伺服器通訊的線程,直接關閉socket
                socket.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return b;
    }
}
7.ClientConnectServerThread類

客戶端與服務端通過socket連接,考慮到一個客戶端會有多個socket的情況(服務端同此),將socket放線上程內

package qqclient.service;

import qqcommon.Message;

import java.io.ObjectInputStream;
import java.net.Socket;

/**
 * @author 李
 * @version 1.0
 */
public class ClientConnectServerThread extends Thread {
    //該線程需要持有socket
    private Socket socket;

    //構造器可以接收一個Socket對象
    public ClientConnectServerThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        //因為Thread需要在後臺和伺服器通信,因此我們使用while迴圈
        while (true) {
            try {
                System.out.println("客戶端線程,等待讀取從服務端發送的消息");
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());

                //如果伺服器沒有發送Message對象,線程會阻塞在這裡
                Message message = (Message) ois.readObject();
                //註意,後面我們需要使用message

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //為了更方便地得到socket,提供get方法
    public Socket getSocket() {
        return socket;
    }

    public void setSocket(Socket socket) {
        this.socket = socket;
    }
}
8.ManageClientConnectServerThread類

將線程都放入集合中,便於管理

package qqclient.service;

import java.util.HashMap;

/**
 * @author 李
 * @version 1.0
 * 該類管理客戶端連接到伺服器端的線程的類
 */
public class ManageClientConnectServerThread {
    //把多個線程放入到HashMap集合,key就是用戶id,value就是線程
    private static HashMap<String, ClientConnectServerThread> hm = new HashMap<>();

    //將某個線程加入到集合
    public static void addClientConnectServerThread(String userId, ClientConnectServerThread clientConnectServerThread) {
        hm.put(userId, clientConnectServerThread);
    }

    //通過userId可以得到一個對應的線程
    public static ClientConnectServerThread getClientConnectServerThread(String userId) {
        return hm.get(userId);
    }
}
4.1.3.2服務端代碼

image-20220921234743331 image-20220922000858338

服務端的User、Message、MessageType和客戶端一致,不再贅述

1.QQFrame
package qqframe;

import qqserver.server.QQServer;

/**
 * @author 李
 * @version 1.0
 * 該類創建QQServer,啟動後臺的服務
 */
public class QQFrame {
    public static void main(String[] args) {
        new QQServer();
    }
}
2.QQServer
package qqserver.server;

import qqcommon.Message;
import qqcommon.MessageType;
import qqcommon.User;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author 李
 * @version 1.0
 * 這是服務端,在監聽埠9999,等待有客戶端連接,並保持通信
 */
public class QQServer {

    private ServerSocket ss = null;
    //創建一個集合,存放多個用戶數據,如果是在集合裡面的用戶登錄,就認為是合法的(模擬資料庫)
    //這裡也可以使用 ConcurrentHashMap,可以處理併發的集合,沒有線程安全問題
    // HashMap 沒有處理線程安全,因此在多線程的情況下是不安全的
    // ConcurrentHashMap 處理的線程安全,即線程同步處理,在多線程的情況下是安全的
    private static ConcurrentHashMap<String, User> validUsers = new ConcurrentHashMap<>();

    static {//在靜態代碼塊,初始化 validUsers
        validUsers.put("100", new User("100", "123456"));
        validUsers.put("200", new User("200", "123456"));
        validUsers.put("300", new User("300", "123456"));
        validUsers.put("至尊寶", new User("至尊寶", "123456"));
        validUsers.put("紫霞仙子", new User("紫霞仙子", "123456"));
    }

    //驗證用戶是否有效的方法
    public boolean checkUser(String userId, String password) {
        User user = validUsers.get(userId);//在HashMap(模擬資料庫)裡面找key=userId對應的value=User對象
        //過關的驗證方式
        if (user == null) {//如果User為空(即Value為空)就說明 userId對應的key不存在
            return false;
        }
        if (!user.getPassword().equals(password)) {//如果userId正確,但是密碼錯誤
            return false;
        }
        return true;//如果userId和密碼都正確
    }

    public QQServer() {

        //註意:埠可以寫在配置文件裡面
        System.out.println("服務端在9999埠監聽...");
        try {
            ss = new ServerSocket(9999);

            while (true) {//迴圈監聽,當和某個客戶端建立連接後,會繼續監聽,因此使用while
                Socket socket = ss.accept();//如果沒有客戶端連接,就會阻塞在這裡,直到有新的客戶端來連接

                //得到socket關聯的對象輸入流
                ObjectInputStream ois =
                        new ObjectInputStream(socket.getInputStream());
                User u = (User) ois.readObject();//讀取客戶端發送的User對象

                /***
                 * 下麵這裡其實是要到資料庫區驗證User的信息,但是因為還沒學資料庫,先用規定的數據進行校驗
                 * HashMap模擬資料庫,可以多個用戶登錄
                 */
                //創建一個Message對象,用來回覆客戶端
                Message message = new Message();
                //得到socket關聯的對象輸出流
                ObjectOutputStream oos =
                        new ObjectOutputStream(socket.getOutputStream());
                //驗證
                if (checkUser(u.getUserId(), u.getPassword())) {//登錄通過
                    message.setMesType(MessageType.MESSAGE_LOGIN_SUCCEED);
                    //將Message對象回覆給客戶端
                    oos.writeObject(message);
                    //創建一個線程,和客戶端保持通信,該線程需要持有socket對象
                    ServerConnectClientThread serverConnectClientThread =
                            new ServerConnectClientThread(socket, u.getUserId());
                    //啟動該線程
                    serverConnectClientThread.start();
                    //把該線程對象放入到一個集合中,進行管理
                    ManageClientThreads.addClientThread(u.getUserId(), serverConnectClientThread);

                } else {//登錄失敗
                    System.out.println("用戶 id=" + u.getUserId() + " pwd=" + u.getPassword() + " 驗證失敗");
                    message.setMesType(MessageType.MESSAGE_LOGIN_FAIL);
                    oos.writeObject(message);
                    //關閉socket
                    socket.close();
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {

            //如果伺服器退出了while迴圈,說明伺服器不再監聽,因此關閉ServerSock
            try {
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
3.ServerConnectClientThread

線程類,與客戶端的線程類同理

package qqserver.server;

import qqcommon.Message;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;

/**
 * @author 李
 * @version 1.0
 * 該類的一個對象和某個客戶端保持通信
 */
public class ServerConnectClientThread extends Thread {
    private Socket socket;
    private String userId;//連接到服務端的用戶id


    public ServerConnectClientThread(Socket socket, String userId) {
        this.socket = socket;
        this.userId = userId;
    }

    @Override
    public void run() {//這裡線程處於run的狀態,可以發送/接收消息

        while (true) {
            try {
                System.out.println("服務端和客戶端" + userId + "保持通信,讀取數據...");
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                Message message = (Message) ois.readObject();
                //後面會使用Message
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
4.ManageClientThreads

使用集合來存放線程,便於管理

package qqserver.server;

import java.util.HashMap;

/**
 * @author 李
 * @version 1.0
 * 該類用於管理和客戶端通信的線程
 */
public class ManageClientThreads {
    private static HashMap<String, ServerConnectClientThread> hm = new HashMap<>();

    //添加線程對象到 hm集合中
    public static void addClientThread(String userId, ServerConnectClientThread serverConnectClientThread) {
        hm.put(userId, serverConnectClientThread);
    }

    //根據userId返回ServerConnectClientThread線程
    public static ServerConnectClientThread getServerConnectClientThread(String userId) {
        return hm.get(userId);
    }
}

運行截圖:

  1. 先運行服務端:
image-20220921235610095
  1. 運行客戶端,並輸入信息:
image-20220921235756369

此時服務端:

image-20220922000032580

可以看到服務端成功地從客戶端獲取用戶登錄信息,匹配相應用戶後返回了信息,客戶端成功獲取到了服務端返回的信息,併進入了二級菜單。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.HTTP和HTTPS的基本概念 http:是一個客戶端和服務端請求和應答的標準(TCP),用於從www伺服器傳輸超文本到本地瀏覽器的超文本傳輸協議。 https:是以安全為目標的HTTP通道,即HTTP下加入SSL層進行加密。其作用是:建立一個信息安全通道,確保數據的傳輸,確保網站的真實性。 補 ...
  • 原因 項目每次打包後都需要改動項目版本號,這個改動每次都需要在package.json中修改version,比較麻煩,到底有沒有一種打包後版本號自加的辦法。 方案 版本號自加其實可以使用fs修改文件來實現的。 具體思路是:在執行打包命令npm run build時,同時執行一段js代碼,該代碼通過調 ...
  • 在我們基於UniApp的H5項目中,需要生成一些二維碼進行展示,另外也需要讓用戶可以掃碼進行一定的快捷操作,本篇隨筆介紹一下二維碼的生成處理和基於H5的掃碼進行操作。二維碼的生成,使用了JS文件weapp-qrcode.js進行處理,而二維碼掃碼則是基於一個第三方組件的方式進行支持的,最後通過統一入... ...
  • 一.起步 1.1 配置uni-app開發環境 什麼是uni-app,就是基於vue的一個開發框架,可以將我們寫的一套代碼,同時發佈到ios、安卓、小程式等多個平臺 ==官方推薦使用Hbuilderx來寫uni-app項目== 下載之後可以將預設改為vscode 進入hbuilder插件市場下載scs ...
  • 蒼穹之邊,浩瀚之摯,眰恦之美; 悟心悟性,善始善終,惟善惟道! —— 朝槿《朝槿兮年說》 寫在開頭 在併發編程領域,有兩大核心問題:一個是互斥,即同一時刻只允許一個線程訪問共用資源;另一個是同步,即線程之間如何通信、協作。主要原因是,對於多線程實現實現併發,一直以來,多線程都存在2個問題: 線程之間 ...
  • 蒼穹之邊,浩瀚之摯,眰恦之美; 悟心悟性,善始善終,惟善惟道! —— 朝槿《朝槿兮年說》 寫在開頭 在併發編程領域,有兩大核心問題:一個是互斥,即同一時刻只允許一個線程訪問共用資源;另一個是同步,即線程之間如何通信、協作。主要原因是,對於多線程實現實現併發,一直以來,多線程都存在2個問題: 線程之間 ...
  • 一、前言 Caffeine是一個高性能的 Java 緩存庫,底層數據存儲採用ConcurrentHashMap 優點:因為Caffeine面向JDK8,在jdk8中ConcurrentHashMap增加了紅黑樹,在hash衝突嚴重時也能有良好的讀性能。多線程環境中,不同的key可以併發寫,相同的ke ...
  • 簡述 類型:創建型 目標:通過拷貝快速創建相同或相似對象。 接下來我們看一個需要改進的案例。 優化案例 話不多說,先來看一個創建相同或相似對象的傳統寫法。 原版v0 public class Department { private String name; private String count ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...