多线程网络实战之仿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.mzph.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…

程序人生【追光的日子】今天我们不谈技术,谈一谈:人工智能的意义到底是什么?来看看今天分享的故事...我想我们都愿意相信,也许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…

基于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…

【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;一步步打造出一款能够远…

代码随想录-Day50

1143. 最长公共子序列 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对顺序的情况下删除某些…

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 缺少依…

水果商城系统 SpringBoot+Vue

1、技术栈 技术栈&#xff1a;SpringBootVueMybatis等使用环境&#xff1a;Windows10 谷歌浏览器开发环境&#xff1a;jdk1.8 Maven mysql Idea 数据库仅供学习参考 【已经答辩过的毕业设计】 项目源码地址 2、功能划分 3、效果演示

化工厂定位的意义?如何有效解决管理难题

化工厂定位是运用于工厂人员定位管理的新技术&#xff0c;这一技术的应用具有特殊的意义&#xff0c;和传统管理模式相比具有很大的区别&#xff0c;那么&#xff0c;你是否清楚化工厂定位的意义&#xff0c;它是如何有效的去解决工厂现存的管理难题呢? 传统化工厂管理到底有哪…

PySide6开发桌面程序,PySide6入门实战(上)

文章目录 系列文章索引一、前期准备1、简介及安装2、PyCharm PySide6环境搭建&#xff08;1&#xff09;基础环境&#xff08;2&#xff09;配置QT Designer、PyUIC、PyRCC&#xff08;3&#xff09;使用pyside6项目&#xff08;4&#xff09;资源文件编写与编译 二、QT常用控件…

排序矩阵查找

题目链接 排序矩阵查找 题目描述 注意点 每一行、每一列都按升序排列 解答思路 可以从右上角开始遍历&#xff0c;如果当前元素就等于target&#xff0c;直接返回true&#xff1b;如果当前元素小于target&#xff0c;则target肯定在当前位置下方&#xff1b;如果当前元素大…

挑战杯 LSTM的预测算法 - 股票预测 天气预测 房价预测

0 简介 今天学长向大家介绍LSTM基础 基于LSTM的预测算法 - 股票预测 天气预测 房价预测 这是一个较为新颖的竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f9ff; 更多资料, 项目分享&#xff1a; https://gitee.com/dancheng-senior/postgraduate 1 基于 Ke…

手机飞行模式是什么意思?3个方法教你如何开启

在现代生活中&#xff0c;手机已经成为我们日常生活中不可或缺的一部分。然而&#xff0c;有时我们需要暂时切断手机的通信功能&#xff0c;比如在飞机上、开会时或需要安静休息的时候。这时候&#xff0c;苹果手机上的“飞行模式”功能就派上了用场。 那么&#xff0c;手机飞…

人脸表情识别Facial Expression Recognition基于Python3和Keras2(TensorFlow后端)

人脸表情识别项目是一个结合了计算机视觉和深度学习技术的高级应用&#xff0c;主要用于分析和理解人类面部表情所传达的情感状态。这样的系统可以用于多种场景&#xff0c;比如情绪分析、用户交互、市场调研、医疗诊断以及人机接口等领域。 一个典型的人脸表情识别项目可以分…