多線程網絡實戰之仿qq群聊的服務器和客戶端

目錄

一、前言

二、設計需求

1.服務器需求?

2.客戶端需求

三、服務端設計

1.項目準備

?2.初始化網絡庫

3.SOCKET創建服務器套接字

4.?bind 綁定套接字

?5. listen監聽套接字

?6. accept接受客戶端連接

7.建立套接字數組

8. 建立多線程與客戶端通信

9. 處理線程函數,收消息

10. 發消息給客戶端

?11.處理斷開的客戶端

四、客戶端設計

1.項目準備

2. 處理main函數參數

?3.初始化網絡庫

4.SOCKET創建客戶端套接字

5. 配置IP地址和端口號,連接服務器

6.創建兩線程,發送和接收

?7.處理發送消息線程函數

五、項目運行

1.編譯生成可執行文件

2.運行可執行程序

3.進行通訊

六、總代碼展示

1.服務端代碼:

2.客戶端代碼:

七、最后


一、前言

? ? ? ? 今天我們不學習其他的知識點,主要是復習之前學習過的TCP網絡通信和多線程以及線程同步互斥,然后結合這以上知識點設計實現一個小的項目,主要仿照qq群聊的服務器可客戶端的實現,下面我將會說明一下設計需求,以下是整個設計示意圖。

二、設計需求

1.服務器需求?

? ? ? ? 需求一對于每一個上線連接的客戶端,服務端會起一個線程去維護。????????

? ? ? ? 需求二:將服務器受到的消息轉發給全部的客戶端。例如:服務器接收客戶端A的消息后,將立即發送給客戶端A,B,C...

????????需求三:當某個客戶端斷開(下線),需要處理斷開的鏈接。

2.客戶端需求

????????需求一:請求連接上線,???

? ? ? ? 需求二:發消息給服務器。

????????需求三:客戶端等待服務端的消息。

????????需求四:等待用戶自己的關閉(下線)。

三、服務端設計

1.項目準備

? ? ? ? 在創建項目后,引入一些必需的頭文件以及創建項目需要的宏,例如:允許客戶端連接的最大數量,接收文件字節的大小,客戶端連接的個數等等。

#include <stdio.h>
#include <windows.h>
#include <process.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")#define MAX_CLEN 256        // 最大連接數量
#define MAX_BUF_SIZE 1024   // 接收文件大小SOCKET clnSockets[MAX_CLEN]; // 所有的連接客戶端的socket
int clnCnt = 0;   // 客戶端連接的個數// 互斥的句柄
HANDLE hMutex; 
?2.初始化網絡庫

????????WSAStartup初始化Winsock,這個函數用于初始化網絡環境,都是固定寫法,必須要有的,直接復制粘貼即可。

    // 1. 初始化庫WSADATA wsaData;int stu = WSAStartup(MAKEWORD(2, 2), &wsaData);if (stu != 0) {std::cout << "WSAStartup 錯誤:" << stu << std::endl;return 0;}
3.SOCKET創建服務器套接字

? ? ? ? ?這和我們之前學的windwos網絡一樣都是固定寫法,重點時查看函數原型以及它的參數,代碼如下:

	// 2. socket 創建套接字SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);if (sockSrv == INVALID_SOCKET){std::cout << "socket failed!" << GetLastError() << std::endl;WSACleanup(); //釋放Winsock庫資源return 1;}
4.?bind 綁定套接字

? ? ? ? 這個流程主要是綁定服務器的IP地址,端口號,以及協議版本。?

	// 3 bind 綁定套接字SOCKADDR_IN addrSrv;addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);   // 地址 IP地址anyaddrSrv.sin_family = AF_INET;    // ipv4協議addrSrv.sin_port = htons(6000);  // 端口號if ( SOCKET_ERROR == bind(sockSrv, (sockaddr*)&addrSrv, sizeof(SOCKADDR))){std::cout << "bind failed!" << GetLastError() << std::endl;WSACleanup(); //釋放Winsock庫資源return 1;}
?5. listen監聽套接字

? ? ? ? listen函數最重要的是理解它的第二個參數,為等待連接的最大隊列長度 ,這個解釋我有專門出過一篇文章windows網絡進階之listen參數含義。

	// 4. 監聽if (listen(sockSrv, 5) == SOCKET_ERROR) // 5 是指最大的監聽數目,執行到listen{printf("listen error = %d\n", GetLastError());return -1;}
?6. accept接受客戶端連接

????????對于每一個被接受的連接請求,accept函數都會創建一個新的套接字,用于與該客戶端的后續通信。也都是固定流程,后面互斥和多線程就比較難理解了。

    // 5. accept接受客戶端連接SOCKADDR_IN addrCli;int len = sizeof(SOCKADDR);while (true){// 接受客戶端的連接SOCKET sockCon = accept(sockSrv, (sockaddr*)&addrCli, &len);}
7.建立套接字數組

? ? ? ? 將accept生成的套接字放入全局套接字數組中,同時加上互斥鎖。

    //創建一個互斥對象hMutex = CreateMutex(NULL, false, NULL);while (true){// 接受客戶端的連接SOCKET sockCon = accept(sockSrv, (sockaddr*)&addrCli, &len);// 全局變量要加鎖WaitForSingleObject(hMutex, INFINITE);// 將連接放到數組里面clnSockets[clnCnt++] = sockCon;// 解鎖ReleaseMutex(hMutex);}closesocket(sockSrv);CloseHandle(hMutex);WSACleanup();return 0;
8. 建立多線程與客戶端通信

? ? ? ? 每通過accept函數返回的新創建的套接字,就建立一個線程去維護。

//創建一個互斥對象
hMutex = CreateMutex(NULL, false, NULL);
while (true){// 接受客戶端的連接SOCKET sockCon = accept(sockSrv, (sockaddr*)&addrCli, &len);// 全局變量要加鎖WaitForSingleObject(hMutex, INFINITE);// 將連接放到數組里面clnSockets[clnCnt++] = sockCon;// 解鎖ReleaseMutex(hMutex);// 每接收一個客戶端的連接,都安排一個線程去維護hThread = (HANDLE)_beginthreadex(NULL, 0, &handleCln, (void*)&sockCon, 0, NULL);printf("Connect client IP = %s\n, Num = %d \n", inet_ntoa(addrCli.sin_addr), clnCnt);}closesocket(sockSrv);CloseHandle(hMutex);WSACleanup();return 0;
9. 處理線程函數,收消息

? ? ? ? 上個步驟我們對每一個接受連接的套接字都創建了線程,現在我們開始來寫線程函數中的邏輯代碼,主要有三個部分:收到客戶端的消息,將收到的消息再發給所有客戶端,處理斷開的客戶端。

? ? ? ? 下面我們開始完成第一個部分:?收到客戶端的消息。

????????因為客戶端發消息會不止一個,所以我們要建立while循環,通關判斷接收到的消息來判斷,如果為0就退出循環。

// 處理線程函數, 收發消息
unsigned WINAPI handleCln(void *arg)
{SOCKET hClnSock = *((SOCKET *)arg);int iLen = 0;char recvBuff[MAX_BUF_SIZE] = { 0 };while (1){// iLen 成功時返回接收的字節數(收到EOF時為0),失敗時返回SOCKETERROR。iLen = recv(hClnSock, recvBuff, MAX_BUF_SIZE, 0);// if (iLen >= 0){// 將收到的消息轉發給所有客戶端SendMsg(recvBuff,iLen);}else{break;}}
10. 發消息給客戶端

? ? ? ??完成第二個部分:?將收到的消息再發給所有客戶端

? ? ? ? ?因為是仿照qq的小demo,所以服務器一旦收到消息,就要再發送給所有的客戶端。這段邏輯寫在SendMsg 函數中,同時還需要注意因為在多線程中,所以要避免多個線程同時訪問共享資源時產生數據不一致的問題,需要加互斥鎖和解鎖。

// 將收到的消息轉發給所有客戶端
void SendMsg(char* msg, int len)
{int i;WaitForSingleObject(hMutex, INFINITE);for (i = 0; i < clnCnt; i++){send(clnSockets[i], msg, len, 0);}ReleaseMutex(hMutex);
}
?11.處理斷開的客戶端

????????完成第三個部分:?處理斷開的客戶端。

? ? ? ? 這里也是通過 for 循環遍歷 socket 數組,通過匹配每一項,如果相匹配,就然后斷開連接。同時? socket 數組 中的數量減 1。

// 處理消息, 收發消息
unsigned WINAPI handleCln(void *arg)
{SOCKET hClnSock = *((SOCKET *)arg);int iLen = 0;char recvBuff[MAX_BUF_SIZE] = { 0 };while (1){// iLen 成功時返回接收的字節數(收到EOF時為0),失敗時返回SOCKETERROR。iLen = recv(hClnSock, recvBuff, MAX_BUF_SIZE, 0);// if (iLen >= 0){// 將收到的消息轉發給所有客戶端SendMsg(recvBuff,iLen);}else{break;}}printf("此時連接的客戶端數量 = %d\n", clnCnt);WaitForSingleObject(hMutex, INFINITE);for (int i = 0; i < clnCnt; i++){// 找到哪個連接下線的,移除這個連接if (hClnSock == clnSockets[i]){while (i++ < clnCnt){clnSockets[i] = clnSockets[i + 1];}break;}}// 斷開連接減 1 clnCnt--;printf("斷開連接后連接的客戶端數量 = %d\n", clnCnt);ReleaseMutex(hMutex);// 斷開連接closesocket(hClnSock);return 0;
}

四、客戶端設計

1.項目準備

? ? ? ? 客戶端設計和服務器端其實差別不大,代碼有些基本都相同,邏輯也大多一致,所以有些代碼不在過多贅述。

????????項目準備代碼:

#include <stdio.h>
#include <windows.h>
#include <process.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")#define NAME_SIZE 256   
#define MAX_BUF_SIZE 1024char szName[NAME_SIZE] = "[DEFAULT]"; //  默認的昵稱
char szMsg[MAX_BUF_SIZE];    // 收發數據的大小
2. 處理main函數參數

? ? ? ? 項目為仿qq群聊,所以我用main函數中的命令行參數作為我們輸入的每一個客戶端的名字,項目啟動在終端開始啟動,否則就退出程序。

int main(int argc, char* argv[])
{if (argc != 2){printf("必須輸入兩個參數,包括昵稱\n");printf("例如: WXS\n");system("pause");return -1;}sprintf_s(szName, "[%s]", argv[1]);printf("this is Client");
}
?3.初始化網絡庫

? ? ? ? 和服務器端代碼一樣。

    // 初始化庫WSADATA wsaData;int stu = WSAStartup(MAKEWORD(2, 2), &wsaData);if (stu != 0) {std::cout << "WSAStartup 錯誤:" << stu << std::endl;return 0;}
4.SOCKET創建客戶端套接字

? ? ? ? 以服務器類似。?

    SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);if (sockCli == INVALID_SOCKET){std::cout << "socket failed!" << GetLastError() << std::endl;WSACleanup(); //釋放Winsock庫資源return 1;}
5. 配置IP地址和端口號,連接服務器

? ? ? ? 也是基本固定寫法。

	// 配置IP地址 和 端口號SOCKADDR_IN addrSrv;addrSrv.sin_family = AF_INET;    // ipv4協議addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.1.7"); // 地址 IP地址anyaddrSrv.sin_port = htons(6000);  // 端口號// 連接服務器int res = connect(sockCli, (sockaddr*)&addrSrv, sizeof(sockaddr));
6.創建兩線程,發送和接收

? ? ? ? ?這里我們創建了兩個線程,分別處理發送消息給客戶端同時接收消息。同時這個函數WaitForSingleObject 會阻塞主進程代碼,直到子進程結束。

	// 定義兩個線程 HANDLE hSendThread, hRecvThread;// 發送消息hSendThread = (HANDLE)_beginthreadex(NULL, 0, &SendMsg, (void*)&sockCli, 0, NULL);// 接收消息hRecvThread = (HANDLE)_beginthreadex(NULL, 0, &RecvMsg, (void*)&sockCli, 0, NULL);// 阻塞代碼,處理子線程執行完后再執行WaitForSingleObject(hSendThread,INFINITE);WaitForSingleObject(hRecvThread, INFINITE);
?7.處理發送消息線程函數

? ? ? ? 我們客戶端發送消息是通過控制臺程序進行發送的,所以要用到用戶輸入。同時發送的時候帶上自己的名字前綴,也要處理快捷鍵客戶端下線的邏輯,不能一致發送消息。

unsigned WINAPI SendMsg(void* arg)
{SOCKET hClnSock = *((SOCKET*)arg);char szNameMsg[NAME_SIZE + MAX_BUF_SIZE] = { 0 };   // 昵稱和消息while (1){memset(szMsg, 0, MAX_BUF_SIZE);// 阻塞這一句,等待控制臺的消息//fgets(szMsg, MAX_BUF_SIZE, stdin);// 第二種寫法std::cin >> szMsg;if (!strcmp(szMsg, "Q\n") || !strcmp(szMsg, "q\n")){// 處理下線closesocket(hClnSock);exit(0);}// 拼接  名字和字符串一起發送sprintf_s(szNameMsg, "%s %s", szName, szMsg);send(hClnSock, szNameMsg, strlen(szNameMsg) + 1, 0);}
}

?7.處理接收消息線程函數

? ? ? ? 這里接收消息比較簡單,和正常接收客戶端消息的邏輯差不多,代碼如下:

unsigned WINAPI RecvMsg(void* arg)
{SOCKET hClnSock = *((SOCKET*)arg);char szNameMsg[NAME_SIZE + MAX_BUF_SIZE] = { 0 };   // 昵稱和消息int len;while (1){len = recv(hClnSock, szNameMsg, sizeof(szNameMsg), 0);if (len <= 0){break;return -2;}szNameMsg[len] = 0;std::cout << szNameMsg << std::endl;// fputs(szNameMsg, stdout);}
}

五、項目運行

? ? ? ? 以上我們分別講解了服務器和客戶端代碼的實現邏輯,現在我們來進行步驟驗證我們的操作結果。

1.編譯生成可執行文件

? ? ? ? 如圖所示:

2.運行可執行程序

? ? ? ? 這里要注意服務器直接運行exe文件即可,而客戶端要通過命令行輸入運行。

? ? ? ? 服務器端:

? ? ? ? 客戶端運行需要打開終端,輸入exe文件的路徑,以及名字。另外進行通訊還需要打開多個客戶端。

3.進行通訊

? ? ? ? ?結果展示為:

六、總代碼展示

1.服務端代碼:

? ? ? ? 如下所示:

// 1. 對于每一個上線的客戶端,服務端會起一個線程去維護
// 2. 將受到的消息轉發給全部的客戶端
// 3. 當某個客戶端斷開(下線),需要處理斷開的鏈接。怎么處理呢?
#include <stdio.h>
#include <windows.h>
#include <process.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")#define MAX_CLEN 256   
#define MAX_BUF_SIZE 1024SOCKET clnSockets[MAX_CLEN]; // 所有的連接客戶端的socket
int clnCnt = 0;   // 客戶端連接的個數HANDLE hMutex; // 將收到的消息轉發給所有客戶端
void SendMsg(char* msg, int len)
{int i;WaitForSingleObject(hMutex, INFINITE);for (i = 0; i < clnCnt; i++){send(clnSockets[i], msg, len, 0);}ReleaseMutex(hMutex);
}// 處理消息, 收發消息
unsigned WINAPI handleCln(void *arg)
{SOCKET hClnSock = *((SOCKET *)arg);int iLen = 0;char recvBuff[MAX_BUF_SIZE] = { 0 };while (1){// iLen 成功時返回接收的字節數(收到EOF時為0),失敗時返回SOCKETERROR。iLen = recv(hClnSock, recvBuff, MAX_BUF_SIZE, 0);// if (iLen >= 0){// 將收到的消息轉發給所有客戶端SendMsg(recvBuff,iLen);}else{break;}}printf("此時連接的客戶端數量 = %d\n", clnCnt);WaitForSingleObject(hMutex, INFINITE);for (int i = 0; i < clnCnt; i++){// 找到哪個連接下線的,移除這個連接if (hClnSock == clnSockets[i]){while (i++ < clnCnt){clnSockets[i] = clnSockets[i + 1];}break;}}// 斷開連接減 1 clnCnt--;printf("斷開連接后連接的客戶端數量 = %d\n", clnCnt);ReleaseMutex(hMutex);// 斷開連接closesocket(hClnSock);return 0;
}int main(int argc, char* argv[])
{	printf("this is Server\n");//0. 初始化網絡
#if 1
// 0 初始化網絡庫
// 初始化庫WSADATA wsaData;int stu = WSAStartup(MAKEWORD(2, 2), &wsaData);if (stu != 0) {std::cout << "WSAStartup 錯誤:" << stu << std::endl;return 0;}
#endifHANDLE hThread;// 1.  創建一個互斥對象hMutex = CreateMutex(NULL, false, NULL);// 2. socket 創建套接字SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);if (sockSrv == INVALID_SOCKET){std::cout << "socket failed!" << GetLastError() << std::endl;WSACleanup(); //釋放Winsock庫資源return 1;}// 3 bind 綁定套接字SOCKADDR_IN addrSrv;addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);   // 地址 IP地址anyaddrSrv.sin_family = AF_INET;    // ipv4協議addrSrv.sin_port = htons(6000);  // 端口號if ( SOCKET_ERROR == bind(sockSrv, (sockaddr*)&addrSrv, sizeof(SOCKADDR))){std::cout << "bind failed!" << GetLastError() << std::endl;WSACleanup(); //釋放Winsock庫資源return 1;}// 4. 監聽if (listen(sockSrv, 5) == SOCKET_ERROR) // 5 是指最大的監聽數目,執行到listen{printf("listen error = %d\n", GetLastError());return -1;}// 5SOCKADDR_IN addrCli;int len = sizeof(SOCKADDR);while (true){// 接受客戶端的連接SOCKET sockCon = accept(sockSrv, (sockaddr*)&addrCli, &len);// 全局變量要加鎖WaitForSingleObject(hMutex, INFINITE);// 將連接放到數組里面clnSockets[clnCnt++] = sockCon;// 解鎖ReleaseMutex(hMutex);// 每接收一個客戶端的連接,都安排一個線程去維護hThread = (HANDLE)_beginthreadex(NULL, 0, &handleCln, (void*)&sockCon, 0, NULL);printf("Connect client IP = %s\n, Num = %d \n", inet_ntoa(addrCli.sin_addr), clnCnt);}closesocket(sockSrv);CloseHandle(hMutex);WSACleanup();return 0;
}
2.客戶端代碼:

? ? ? ? 如下所示:

// 客戶端做的事情:
//1 請求連接上線,
//2 發消息
//3 客戶端等待服務端的消息
//4 等待用戶自己的關閉(下線)
#include <stdio.h>
#include <windows.h>
#include <process.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")#define NAME_SIZE 256   
#define MAX_BUF_SIZE 1024char szName[NAME_SIZE] = "[DEFAULT]"; //  默認的昵稱
char szMsg[MAX_BUF_SIZE];    // 收發數據的大小unsigned WINAPI SendMsg(void* arg)
{SOCKET hClnSock = *((SOCKET*)arg);char szNameMsg[NAME_SIZE + MAX_BUF_SIZE] = { 0 };   // 昵稱和消息while (1){memset(szMsg, 0, MAX_BUF_SIZE);// 阻塞這一句,等待控制臺的消息//fgets(szMsg, MAX_BUF_SIZE, stdin);std::cin >> szMsg;if (!strcmp(szMsg, "Q\n") || !strcmp(szMsg, "q\n")){// 處理下線closesocket(hClnSock);exit(0);}// 拼接  名字和字符串一起發送sprintf_s(szNameMsg, "%s %s", szName, szMsg);send(hClnSock, szNameMsg, strlen(szNameMsg) + 1, 0);}
}unsigned WINAPI RecvMsg(void* arg)
{SOCKET hClnSock = *((SOCKET*)arg);char szNameMsg[NAME_SIZE + MAX_BUF_SIZE] = { 0 };   // 昵稱和消息int len;while (1){len = recv(hClnSock, szNameMsg, sizeof(szNameMsg), 0);if (len <= 0){break;return -2;}szNameMsg[len] = 0;std::cout << szNameMsg << std::endl;// fputs(szNameMsg, stdout);}}
int main(int argc, char* argv[])
{if (argc != 2){printf("必須輸入兩個參數,包括昵稱\n");printf("例如: WXS\n");system("pause");return -1;}sprintf_s(szName, "[%s]", argv[1]);printf("this is Client");//0. 初始化網絡
#if 1
// 0 初始化網絡庫
// 初始化庫WSADATA wsaData;int stu = WSAStartup(MAKEWORD(2, 2), &wsaData);if (stu != 0) {std::cout << "WSAStartup 錯誤:" << stu << std::endl;return 0;}
#endif// 定義兩個線程 HANDLE hSendThread, hRecvThread;// 1. 建立 socketSOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);if (sockCli == INVALID_SOCKET){std::cout << "socket failed!" << GetLastError() << std::endl;WSACleanup(); //釋放Winsock庫資源return 1;}// 2, 配置IP地址 和 端口號SOCKADDR_IN addrSrv;addrSrv.sin_family = AF_INET;    // ipv4協議addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.1.7"); // 地址 IP地址anyaddrSrv.sin_port = htons(6000);  // 端口號// 3. 連接服務器int res = connect(sockCli, (sockaddr*)&addrSrv, sizeof(sockaddr));// 4. 發送服務器消息,啟動線程hSendThread = (HANDLE)_beginthreadex(NULL, 0, &SendMsg, (void*)&sockCli, 0, NULL);// 5. 等待hRecvThread = (HANDLE)_beginthreadex(NULL, 0, &RecvMsg, (void*)&sockCli, 0, NULL);WaitForSingleObject(hSendThread,INFINITE);WaitForSingleObject(hRecvThread, INFINITE);closesocket(sockCli);WSACleanup();return 0;
}

七、最后

? ? ? ? 制作不易,熬夜肝的,還請多多點贊,拯救下禿頭的博主吧!!? ?? ? ? ?

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

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

相關文章

【3GPP核心網】【5G】精講5G核心網系統架構主要特征

目錄 前言 1. 5G核心網系統架構主要特征 1.1 5G核心網與4G核心網EPC區別 1.2 5G核心網系統架構主要特征 2. 5G網絡邏輯架構 2.1 新型基礎設施平臺 2.2 邏輯架構 前言 首先需要理解核心網的角色定位&#xff0c;作為移動通信網絡的核心部分&#xff0c;核心網起著承上啟下的作用…

【收藏】歐盟CE、美國FDA法規及標準查詢常用網站

01 CE法規&標準查詢網站 醫療器械主管部門的網站 網址: https://www.camd-europe.eu/ 簡介: CAMD的全稱是Competent authorities for medical devices&#xff0c;翻譯成中文叫做醫療器械監管機構&#xff0c;實際上它指的是歐盟成員國醫療器械監管機構的聯盟&#xff…

PLSQL Day3

--7.鍵盤輸入1-10之間的任意一個數字&#xff0c;輸出這個數字的階乘&#xff1a; [3!1*2*3] [5!1*2*3*4*5] declare n number : &輸入一個數字; s number : 1; begin if n between 1 and 10 then for i in 1..n loop s : i*s; end loop; dbms…

程序人生【追光的日子】今天我們不談技術,談一談:人工智能的意義到底是什么?來看看今天分享的故事...我想我們都愿意相信,也許AI真的會有溫度,這一天不遠了~!

有志者,事竟成,破釜沉舟,百二秦關終屬楚;苦心人,天不負,臥薪嘗膽,三千越甲可吞吳。 ??作者主頁: 追光者♂?? ??個人簡介: ??[1] 計算機專業碩士研究生?? ??[2] 2023年城市之星領跑者TOP1(哈爾濱)?? ??[3] 2022年度博客之星人工智能領域…

Java SpringBoot MongoPlus 使用MyBatisPlus的方式,優雅的操作MongoDB

Java SpringBoot MongoPlus 使用MyBatisPlus的方式&#xff0c;優雅的操作MongoDB 介紹特性安裝新建SpringBoot工程引入依賴配置文件 使用新建實體類創建Service測試類進行測試新增方法查詢方法 官方網站獲取本項目案例代碼 介紹 Mongo-Plus&#xff08;簡稱 MP&#xff09;是一…

網絡服務器配置與管理

網絡服務器配置與管理是一個涉及多個方面的領域&#xff0c;它涵蓋了從物理硬件的設置到操作系統、網絡服務和應用的配置&#xff0c;再到日常維護和安全策略的實施。以下是網絡服務器配置與管理的一些核心概念和步驟&#xff1a; 硬件配置&#xff1a; 選擇合適的服務器硬件&a…

網站易被攻擊原因及保護措施

網絡攻擊是指通過惡意手段侵犯網絡系統的穩定性和安全性的行為。很多網站都成為黑客攻擊的目標&#xff0c;因此對于網站管理員和網絡用戶來說&#xff0c;了解各種被攻擊的方式以及如何解決是非常重要的。本文將介紹一些常見的網站攻擊方式&#xff0c;并提供一些解決方案 1.…

基于docker上安裝elasticSearch7.12.1

部署elasticsearch 首先&#xff0c;先創建網絡 # 創建網絡 docker network create es-net拉取elasticSearch的鏡像 #拉取鏡像 docker pull elasticsearch:7.12.1創建掛載點目錄 # 創建掛載點目錄 mkdir -p /usr/local/es/data /usr/local/es/config /usr/local/es/plugin…

智能決策的藝術:揭秘決策樹的奇妙原理與實戰應用

引言 決策樹&#xff08;Decision Tree&#xff09;是一種常用的監督學習算法&#xff0c;適用于分類和回歸任務。它通過學習數據中的規則生成樹狀模型&#xff0c;從而做出預測決策。決策樹因其易于理解和解釋、無需大量數據預處理等優點&#xff0c;廣泛應用于各種機器學習任…

【SD教程】進階篇圖片復現AnimateDiff動畫插件基礎教程(附模型插件)

當你成功安裝了SD&#xff08;Stable Diffusion&#xff09;后&#xff0c;是否也產生過這樣的疑惑&#xff1a;為何我創作的圖片與他人的作品在風格和質量上存在差異&#xff1f; 看著別人創作的精致、引人入勝的圖片&#xff0c;你是否也渴望縮小這種質感上的差距&#xff1…

游戲AI的創造思路-技術基礎-決策樹(1)

決策樹&#xff0c;是每個游戲人必須要掌握的游戲AI構建技術&#xff0c;難度小&#xff0c;速度快&#xff0c;結果直觀&#xff0c;本篇將對決策樹進行小小解讀~~~~ 目錄 1. 定義 2. 發展歷史 3. 決策樹的算法公式和函數 3.1. 信息增益&#xff08;Information Gain&…

深度解析:STM32對接米家平臺,打造WiFi智能插座(ESP8266、電流檢測)

摘要: 智能插座作為智能家居的入門級設備&#xff0c;憑借其低成本、易部署等優勢&#xff0c;受到了廣大用戶的青睞。本文將引領你從零開始&#xff0c;使用功能強大的STM32微控制器、廣受歡迎的ESP8266 WiFi模塊以及功能豐富的米家IoT平臺&#xff0c;一步步打造出一款能夠遠…

el-form rules動態限制

情景描述&#xff1a; el-form 的ref“obj” rules 對象obj有a,b,c三個字段&#xff0c;點擊按鈕a&#xff0c;a和b字段必填,點擊按鈕c,c字段必填&#xff0c;如何通過 this.$refs.obj.validate((valid)>{})去判斷呢 <template><div><!-- 你的表單組件 --&g…

代碼隨想錄-Day50

1143. 最長公共子序列 給定兩個字符串 text1 和 text2&#xff0c;返回這兩個字符串的最長 公共子序列 的長度。如果不存在 公共子序列 &#xff0c;返回 0 。 一個字符串的 子序列 是指這樣一個新的字符串&#xff1a;它是由原字符串在不改變字符的相對順序的情況下刪除某些…

【Linux】supervisor離線源碼安裝

一、安裝meld wget https://pypi.python.org/packages/45/a0/317c6422b26c12fe0161e936fc35f36552069ba8e6f7ecbd99bbffe32a5f/meld3-1.0.2.tar.gz#md53ccc78cd79cffd63a751ad7684c02c91tar -zxvf meld3-1.0.2.tar.gz cd meld3-1.0.2 python setup.py install二、安裝supervis…

Linux環境中安裝JDK

1.下載安裝包 可以通過訪問oracle官網&#xff1a;Java Downloads | Oracle 中國 下載對應的安裝包。 本文使用的是java8的安裝包&#xff0c;包名為&#xff1a;jdk-8u401-linux-x64.tar.gz 2.上傳安裝包到Linux環境 3.進入/usr目錄下&#xff0c;新建一個java的目錄&#…

Python數據分析-歐洲經濟聚類和主成分分析

一、研究背景 歐洲經濟長期以來是全球經濟體系中的重要組成部分。無論是在全球金融危機后的復蘇過程中&#xff0c;還是在新冠疫情期間&#xff0c;歐洲經濟的表現都對世界經濟產生了深遠的影響。歐洲各國經濟體之間既存在相似性&#xff0c;也存在顯著的差異。這些差異不僅體…

Linux下QT程序啟動失敗問題排查方法

文章目錄 0.問題背景1.程序啟動失敗常見原因2.排查依賴庫問題2.1 依賴庫缺失2.2 依賴庫加載路徑錯誤2.3 依賴庫版本不匹配2.4 QT插件庫缺失2.4.1 QT插件庫缺失2.4.2 插件庫自身的依賴庫缺失 2.5 系統基礎C庫不匹配 3.資源問題3.1 缺少翻譯文件3.2 缺少依賴的資源文件3.3 缺少依…

Unity3D批量修改名稱工具

介紹 該工具用于批量修改某游戲對象的一級子對象名稱&#xff0c;功能包括批量添加前后綴、批量修改公共名稱字段和批量修改為同一名稱&#xff0c;包括撤銷和恢復功能。 批量添加前后綴可使用預設從指定數字遞增或遞減至指定數字。 資源下載 GitHub 百度網盤&#xff08…