计算机网络(三) —— 简单Udp网络程序

目录

一,初始化服务器

1.0 辅助文件

1.1 socket函数

1.2 填充sockaddr结构体

1.3 bind绑定函数

1.4 字符串IP和整数IP的转换

二,运行服务器

2.1 接收

2.2 处理

2.3 返回

三,客户端实现

3.1 UdpClient.cc 实现

 3.2 Main.cc 实现

3.3 效果展示

3.4 代码分层

四,两种场景

4.1 发送部分命令给服务器并返回结果

4.2 实现Linux多终端窗口群聊

4.3 实现Windows做客户端,Linux做服务器群聊


一,初始化服务器

1.0 辅助文件

Log.hpp 日志打印文件:

#pragma once#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>#ifndef _LOG_H_
#define _LOG_H_#include <ctime>#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3const std::string msg[] = {"Debug","Notice","Warning","Error"};std::ostream &Log(std::string message, int level)
{std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;return std::cout;
}#endif

makefile文件:

.PHONY:all
all:udpserver udpclient
udpserver:Main.ccg++ -o $@ $^ -std=c++11
udpclient:UdpClient.ccg++ -o $@ $^ -lpthread -std=c++11.PHONY:clean
clean:rm -f udpserver udpclient 

UdpServer.cc 部分代码:

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <strings.h>
#include <string>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <functional>
#include <unordered_map>#include "Log.hpp"enum
{SOCKET_ERR = 1,BIND_ERR
};uint16_t defaultport = 8080;
std::string defaultip = "0.0.0.0"; // 设置为0,表示任意地址绑定
const int size = 1024;class Udpserver
{
public:Udpserver(const uint16_t &port = defaultport, const std::string &ip = defaultip): _sockfd(0), _port(port), _ip(ip), _isrunning(false){}~Udpserver(){if (_sockfd > 0)close(_sockfd);}private:int _sockfd;                                                      // 网络文件描述符uint16_t _port;                                                   // 表明服务器进程的端口号std::string _ip;                                                  // 地址绑定bool _isrunning;                                                  // 表明服务器是否在运行状态std::unordered_map<std::string, struct sockaddr_in> _online_user; // 第一个键值是ip,第二个键值是ip对应的套接字结构体信息
};

1.1 socket函数

我们首先会把服务器封装成一个类,然后定义一个服务器对象之后做的第一件事就是初始化服务器,而初始化服务器的第一件事,就是创建套接字,下面介绍以下socket接口:

参数说明:

  • domain:表示创建套接字的类型,该参数相当于struct sockaddr结构体的前16个比特位。在man手册中往下滑有很多很多AF开头的选项,但是我们目前只要关心几个:如果是本地通信,该参数就设置为AF_UNIX,如果是网络通信就设置为AF_INET(IPv4)或AF_INET6(IPv6)。
  • type:表示创建套接字时所需的服务类型,最常见的就是:①SOCK_DGRAM,基于UDP的用户数据报服务    ②SOCK_STREAM,基于TCP的流式套接字服务
  • protocol:表示创建套接字的协议类别,可以指名为TCP或UDP,但该参数一般直接设置为0即可,表示默认,此时就会根据传入的前两个参数自动推导出你最终需要使用的是哪种协议
  • 当套接字创建成功后,会直接返回一个文件描述符,创建失败返回-1,同时错误码被设置

问题:socket创建套接字时干了什么?

解答:上面说socket创建成功后会返回一个文件描述符,所以最简单的说法就是“socket创建套接字本质就是打开了一个文件”,以前的打开文件对应的一般是磁盘,把磁盘的文件加载到内存中,并且在进程内部构建files_struct,并且包括文件描述符表;而这里的打开“网络文件”,对应的就是网卡了,通过网卡的驱动层,在操作系统中构建“网卡文件”,通过操作“网卡文件”实现对网卡的宏观控制

下面是服务器初始化函数创建套接字代码: 

void Init(){// 1,创建Udp套接字,Udp的socket是全双工的,允许被同时读写的_sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 表示使用IPv4协议,类型为Udp用户数据报if (_sockfd < 0)                          // 创建失败{Log("socket create error", Error) << "\n";exit(SOCKET_ERR);}Log("socket create success", Debug) << "\n"; // 创建成功,输出日志// ...}

创建成功后,_sockfd会被赋值成3 

1.2 填充sockaddr结构体

在上一篇文章的 4.2 sockaddr结构的时候,讲到过,在代码部分我们会对该结构体进行填充,原因请参照上篇博客:计算机网络(二) —— 网络编程套接字-CSDN博客

创建完套接字后,服务器初始化第一步完成,接着第二步就是构建填充sockaddr结构体了,如下代码:

 // 2,创建和填充sockaddr结构体struct sockaddr_in local;// 一bzero(&local, sizeof(local)); // 把指定类型的指定大小初始化为0,功能类似于memset// 二// 然后就是将我们自己的服务器的一些信息填充进这个结构体里,方便socket API使用local.sin_family = AF_INET; // 表明自身的结构体类型为IPv4   这个family是在宏定义用##来实现的// 三// local.sin_port = _port; //表明服务器将来要绑定的端口号 -- 需要保证我的端口号是网络字节序列,因为该端口号是要给对方发送的// 除了发正常消息外,我也要把我的端口号发给客户端,这样客户端发给我的时候才能找到我,所以端口号需要发送到网络里的,所以一开始我们把这个东东填充到结构体里时,必须是网络字节序local.sin_port = htons(_port); // 把主机序列转化成网络序列,大端不变,小端会转大端// 四// local.sin_addr = _ip;  //s表示socket,然后in表示inet,addr表示IP地址(ifconfig命令)// 我们需要先把string的ip --> uint32_t的ip,并且必须是网络序列的,而这样的各种转化都是有相应的接口的,不需要我们自己写了local.sin_addr.s_addr = inet_addr(_ip.c_str()); // inet_addr表示把字符串转为网络字节序列也就是uint32_t// 查看sockaddr_in的定义后可以发现,sin_addr其实是一个struct类型,这个类型里的s_addr才是要转化的类型//  local.sin_addr.s_addr = htonl(INADDR_ANY); // 这个宏表示任意ip地址,数值为0

1.3 bind绑定函数

上面两步操作完成之后,就是绑定了,下面介绍以下bind函数:

参数解释:

  • sockfd:就是之前socket函数返回的套接字
  • addr:这个就是我们前面填充的sockaddr结构体的指针,里面有绑定的所有必须信息
  • addrlen:传入的sockaddr结构体的长度

下面是初始化服务器的第三步绑定端口的代码:

// 3,绑定套接字
//  local是在地址空间的用户栈上定义的,上面三个参数的所有操作都是在栈上填好,并没有将local与网络套接字socket相关联,所以需要绑定bind函数
int n = bind(_sockfd, (const struct sockaddr *)&local, sizeof(local));
if (n < 0)
{Log("port bind error", Error) << "\n";exit(BIND_ERR);
}
Log("port bind success", Debug) << "\n";
std::cout << "Waiting user to connect ... " << std::endl;

1.4 字符串IP和整数IP的转换

网络传输的数据是寸土寸金的,如果我们在传输IP时以字符串的点分十进制进行IP传输,那么一个IP就需要15字节,但实际上不需要消耗这么多

IP地址可以划分位四个区域,每个区域取值都是0~255,每个区域是8个比特位,我们就可以只用32比特位表示四个区域来表示IP:

所以完成上面的操作就需要将IP在整数二号字符串之间做转换

首先是数字IP转字符串IP:

然后是字符串IP转数字IP: 

 inet_addr函数与inet_ntoa函数

上面的步骤了解一下即可,而且实际实现起来比较麻烦,所以系统为我们提供了相应的转换函数,我们直接调用即可:

二,运行服务器

2.1 接收

服务器初始化完成后,紧接着就是启动辣,服务器的任务就是周而复始为我们提供服务,所以运行起来一般不会退出,因此服务器的运行代码应该是一个死循环。

服务器运行起来后有三个基础动作

  1. 接收来自客户端的信息
  2. 处理信息
  3. 将结果返回给客户端

第一步就是接收,用到的函数名称为:recvfrom函数,下面来介绍一下这个函数 :

参数有点多,但不复杂,解释一下:

  • sockfd:老朋友了,socket的返回值
  • buf:表示要将读取到的数据放到哪里
  • len:表示要读取数据的字节数
  • flags:表示读取的方式,一般设为0,表示阻塞读取,没数据来的时候就给我等着
  • src_addr输出型参数,会保存发送方的协议结组,IP地址,端口号等,简单来说就是,这个字段会告诉程序“谁发数据过来的”,是为了后面发回去的之后,知道对方是谁在哪(传入结构体地址时需要强转,代码会有)
  • addrlen:表示读取到的src_addr的长度,需要和src_addr的大小一致
  • 返回值:表示读取成功返回实际读取到的字节数,读取失败返回-1,错误码被设置
void Run()
{_isrunning = true;char inbuffer[size]; //while (_isrunning)   // 服务器应该能周而复始一直在运行的,所以是死循环{struct sockaddr_in client;socklen_t len = sizeof(client);// 读取数据,从指定套接字里读取消息,然后把读取到的数据放到缓冲区中并指名长度,最后两个参数作为输出型参数,保存对方的IP和port等信息,方便后面我发消息回去ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&client, &len);if (n < 0){Log("recvfrom error", Warning) << "\n";continue;}// ...
}

2.2 处理

上面拿到数据之后,就是要对数据进行处理辣

但是这个处理,就是根据具体的业务需要,由公司具体实施了,我们这里只用很简单的几行代码模拟处理过程,后面会有几种场景专门针对处理方法做调整,如下代码:

inbuffer[n] = 0;
std::string info = inbuffer;
std::string echo_string = "server echo# " + info;
//就是简单的字符串拼接,最后的echo_string就是处理完后最终形成的数据std::string echo_string = "server echo# " + info;

2.3 返回

当处理完数据后,紧接着就是最后一步辣,就是把结果返回给客户端,返回用到的socket API是sendto函数,下面来介绍下这个函数:

它的参数和recvfrom是一样的,这里就不过多介绍了

// 处理完后要再发送回对方
sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr *)&client, len); // 把数据发回给对方

三,客户端实现

3.1 UdpClient.cc 实现

在我们这个简单的Udp网络程序中,客户端的工作其实非常简单,就是“发送数据”,“接收数据”,“打印数据”,所以实现方面比服务器简单许多。

客户端一般是主动发数据给服务器的一方,所以客户端也要有相应的两个过程:

  • 创建套接字
  • 填写sockaddr结构体

问题:为什么客户端不需要我们手动绑定端口?

解答: 

  • 客户端都是最先发出请求的一方,所以服务器的IP地址和端口必须让客户端知道,因为服务器一旦启动,基本情况下不会关闭,所以端口号也不会更改了,所以服务器需要进行端口号绑定
  • 客户端是经常开启和关闭的,因此客户端的端口号是经常变化的,所以每次都绑定会加大成本,所以客户端的端口号只要能标识“唯一性”就可以了,只要客户端首次发送数据的时候,操作系统会自动帮我们绑定一个端口
// ./udpclient serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(0);}string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// 构建服务器信息,因为客户端发给服务端需要知道服务端的ip和portstruct ThreadData td;bzero(&td.server, sizeof(td.server));td.server.sin_family = AF_INET;td.server.sin_port = htons(serverport);                  // 转成网络序列td.server.sin_addr.s_addr = inet_addr(serverip.c_str()); // 点分十进制的字符串转化为数字td.sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (td.sockfd < 0){cout << "socket error" << endl;return 1;}string message;char buffer[1024];socklen_t len = sizeof(td.server);while (true){cout << "Please Enter@ ";getline(cin, message);// std::cout << message << std::endl;// 1. 数据 2. 给谁发sendto(td.sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&td.server, len);struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(td.sockfd, buffer, 1023, 0, (struct sockaddr *)&temp, &len);if (s > 0){buffer[s] = 0;cout << buffer << endl;}}close(td.sockfd); // 不用了就和关闭文件描述符一样关闭套接字return 0;
}

 3.2 Main.cc 实现

服务器的main函数所在的Main.cc如下:

#include "Udpserver.hpp"
#include "Log.hpp"
#include <memory>
#include <cstdio>
#include <vector>
#include <string>void Usage(std::string proc)
{std::cout << "\n\rUsage: " << proc << "port[1024+]\n"<< std::endl;
}int main(int argc, char *argv[])
{if (argc != 2) // argc表示命令行中命令个数{Usage(argv[0]);exit(0);}uint16_t port = std::stoi(argv[1]); // 字符串转整数std::unique_ptr<Udpserver> svr(new Udpserver(port));svr->Init(); // 初始化svr->Run(); // 服务器启动return 0;
}

3.3 效果展示

3.4 代码分层

 在实际开发场景中,其实很少会和 2.2 一样,直接把处理方法内置进服务器头文件中,所以我们可以在服务器启动前构建好处理函数,然后在服务器 Run 的时候,直接把方法传进去,实现代码分用,解耦,要更改的位置如下:

首先在Main.cc 的main函数前面加上处理方法:

// 代码分用
std::string Handler(const std::string &info, const std::string &clientip, uint16_t clientport)
{std::cout << "[" << clientip << ": " << clientport << "]#" << info << std::endl;std::string res = "Server get a message: ";res += info;std::cout << res << std::endl;return res;
}

然后就是利用C++的包装器,更改服务器的 Run 函数:

 加上包装器,也可以用typedef代替:

// using func_t = std::function<std::string(const std::string&, const std::string &, uint16_t)>; //c++11包装器
typedef std::function<std::string(const std::string &, const std::string &, uint16_t)> func_t;

 最后就是更改Run函数,代码如下:

void Run(func_t func)
{_isrunning = true;char inbuffer[size]; //while (_isrunning)   // 服务器应该能周而复始一直在运行的,所以是死循环{struct sockaddr_in client;socklen_t len = sizeof(client);ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&client, &len);if (n < 0){Log("recvfrom error", Warning) << "\n";continue;}// 模拟群聊// ①拿到各客户端的ip和端口号uint16_t clientport = ntohs(client.sin_port);      // 拿到客户端的端口号,网络序列转主机序列std::string clientip = inet_ntoa(client.sin_addr); // 那搭配客户端ip地址,把inet_ntoa四字节ip转化为char*inbuffer[n] = 0;std::string info = inbuffer;// 充当了一次数据的处理,下面两条语句被第三条语句代替// std::string echo_string = "server echo# " + info;// std::cout << echo_string.c_str() << std::endl;std::string echo_string = func(info, clientip, clientport);// 处理完后要再发送回对方sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr *)&client, len); // 把数据发回给对方}
}

 完成好后就可以直接实践了:

(可能各位有些混乱,如果遇到了不会了随时评论或私信哦~~,源代码在文章最后会附上 gitee 仓库链接) 

四,两种场景

4.1 发送部分命令给服务器并返回结果

上面的是最简单的客户端服务器的通讯结果,下面我们来搞一点好玩的

出了发送字符串,我们也可以发送部分命令给服务器,服务器处理好命令后再把结果返回给客户端,最后客户端打印

下面我们来更改一下代码,客户端代码不变,要变的就是服务器处理客户端信息的那部分代码,还是和上面代码分用一样,直接在Main.cc里实现处理函数,然后再传给Run函数

下面是Mani.cc 的处理命令的方法,用到的新函数是 popen,作用是直接执行命令并返回结果,有兴趣可以自行了解下:

Main.cc:

// 场景一,实现命令
bool SafeCheck(const std::string &cmd)
{std::vector<std::string> key_word = {"rm","mv","cp","kill","sudo","unlink","uninstall","yum","tcp","while"};for (auto &word : key_word){auto pos = cmd.find(word);if (pos != std::string::npos)return false; // 在你的命令中找到上面任意一个的话,就是不合法的,直接返回falsse}return true;
}
std::string ExcuteCommand(const std::string &cmd)
{std::cout << "get a request cmd: " << cmd << std::endl;if (!SafeCheck(cmd))return "Bad man";FILE *fp = popen(cmd.c_str(), "r");if (fp == NULL){perror("popen");return "error";}std::string result;char buffer[4096];while (true){char *ok = fgets(buffer, sizeof(buffer), fp);if (ok == nullptr)break; // 为空了,说明读完了result += buffer;}pclose(fp);return result;
}

 然后是UdpServer.hpp 的Run函数的更改:

先更改一下包装器函数的参数数量:

下面是效果展示:

 

4.2 实现Linux多终端窗口群聊

实现群聊我们要做下面几点工作:

  • 能够保存连接服务器的IP和端口,一个人发消息后遍历保存的IP和端口,把消息往所有连接服务器的IP都发送一次
  • 能够让群聊所有人知道是谁发的,也就是消息前要带上IP和端口
  • 利用dup2函数,实现两个窗口,一个窗口只负责发消息,一个窗口只负责收消息

先看效果演示: 

 有点复杂,但是不用担心,我们一步一步来

①首先是我们能够保存发起连接的用户的IP和端口

现在就要用到我们最开始就定义好的一个unordered_map了:

 然后我们在UdpServer.hpp里直接实现一个添加用户的函数:

void CheckUser(const struct sockaddr_in &client, const std::string clientip, uint16_t clientport) // 检查是否为新用户
{auto iter = _online_user.find(clientip); // 去哈希表去找对应IP的信息if (iter == _online_user.end())          // 如果上面这个查找的迭代器走到了结尾,说明哈希表里还没有这个ip,添加{_online_user.insert({clientip, client}); // 把ip和对应的套接字结构体插入std::cout << "[" << clientip << ": " << clientport << "]add to online user" << std::endl;}else // 如果存在了,则什么都不做{}
}

更改Run函数,先将Run函数参数去掉,因为群聊场景不需要传处理方法:

 ②遍历哈希表,对每一个IP都发送

上面的Run函数已经出现了,下面是实现Broadcast发送函数的代码:

void Broadcast(const std::string &info, const std::string clientip, uint16_t clientport)
{for (const auto &user : _online_user) // 遍历在线用户,遍历发送{// 编辑发送形式std::string message = "[";message += clientip;message += ":";message += std::to_string(clientport);message += "]#";message += info;socklen_t len = sizeof(user.second);sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)(&user.second), len); // sendto发送回给对方}
}

 ③最后就是客户端的调整了,我们可以使用dup2重定向函数,实现两个终端,一个终端窗口只发消息,一个窗口接收消息,就和上面的演示一样

 

#include <iostream>
#include <unistd.h>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>std::string terminal = "/dev/pts/2";int OpenTerminal()
{int fd = open(terminal.c_str(), O_WRONLY);if (fd < 0) // 打开失败{std::cerr << "open termial error";return 1;}dup2(fd, 2);// 测试//  printf("hello world\n"); // 把即将打印在当前终端的内容往特定路径的终端打// close(fd);return 0;
}

(可能有点复杂,如果看到这里的小伙伴有不懂或者有问题的,欢迎随时评论和私聊)

4.3 实现Windows做客户端,Linux做服务器群聊

如标题一样,Linux做服务器,Windows做客户端是非常常见的事情

我们可以在Windows本地实现一个客户端,然后和Linux服务器做通信,因为网络基础入门说过,虽然操作系统不一样,但是网络协议栈是一样的,因为这时规定,生产厂家必须遵守规则

下面是VS2022在Windows环境下的客户端代码:

#define _WINSOCK_DEPRECATED_NO_WARNINGS 1
#include <iostream>
#include<WinSock2.h> //这两个W开头的头文件顺序必须是这样,反过来编译时就会报错
#include<Windows.h>
#include <cstdlib>
#include <string>
#include<cstdio>
#pragma comment(lib, "ws2_32.lib")#include<thread>
uint16_t serverport = 8080;
std::string serverip = "58.87.91.241";struct ThreadData
{struct sockaddr_in server;SOCKET sockfd;std::string serverip;
};void* send_message(void* args)
{ThreadData* td = static_cast<ThreadData*>(args);std::string message;std::cout << td->serverip << " coming... " << std::endl;while (true){std::cout << "Please Enter: ";std::getline(std::cin, message);sendto(td->sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&(td->server), sizeof(td->server));}
}
void* recv_message(void* args)
{ThreadData* td = static_cast<ThreadData*>(args);char buffer[1024];while (true){memset(buffer, 0, sizeof(buffer)); //每次接收消息前清空缓冲区int len = sizeof(td->server);int s = recvfrom(td->sockfd, buffer, 1023, 0, (struct sockaddr*)&(td->server), &len);if (s > 0){buffer[s] = 0;std::cout << buffer << std::endl;}}
}int main()
{WSADATA wsd;WSAStartup(MAKEWORD(2, 3), &wsd);struct ThreadData td;// 构建服务器信息,因为客户端发给服务端需要知道服务端的ip和portmemset(&td.server, 0, sizeof(td.server));td.server.sin_family = AF_INET;td.server.sin_port = htons(serverport);                  // 转成网络序列td.server.sin_addr.s_addr = inet_addr(serverip.c_str()); // 点分十进制的字符串转化为数字td.sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (td.sockfd < 0){std::cout << "socket error" << std::endl;return 1;}td.serverip = serverip;std::thread sender(send_message, &td);std::thread recver(recv_message, &td);sender.join();recver.join();WSACleanup();return 0;
}

代码和Linux差不多,也是多线程,只是Windows对于库的处理有点不一样,下面是效果演示:

两边打印中文时会乱码,其实是因为两边的编码不一致,我们暂时不考虑,反正能达到Windows和Linux实现网络通信的目的就行了莫

代码gitee链接:计算机网络/网络编程套接字/Udp · 小堃学编程/Linux学习 - 码云 - 开源中国 (gitee.com) 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/417297.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Mysql】系统服务启动访问报错问题处理:this is incompatible with sql_mode=only_full_group_by

一、背景&#xff1a; 本来已经正常运行的平台&#xff0c;突然有一天由于对服务器进行部分操作迁移&#xff0c;发现jar可以正常启动&#xff0c;但是访问功能一直报错&#xff0c;监控后台日志后&#xff0c;发现了问题&#xff1a; 报错的具体信息如下&#xff1a; Caused…

Linux编译器--gcc/g++使用

目录 一、预编译指令 1.1预处理功能 1.2指令 1.3问题扩展 二、编译&#xff08;生成汇编&#xff09; 三、汇编&#xff08;生成二进制机器语言&#xff09; 四、链接&#xff08;生成可执行文件或库文件&#xff09; 4.1库文件 4.2目标文件和库的链接 4.3动态库和静态…

【Django-Minio-Storage 使用教程】

Django-Minio-Storage 使用教程 安装 Django-Minio-Storage配置 Django 项目官方文档 安装 Django-Minio-Storage 使用 pip 安装 Django-Minio-Storage pip install django-minio-storage配置 Django 项目 在 Django 项目的 settings.py 文件中进行以下配置 INSTALLED_APPS…

【mysql】mysql修改sql_mode之后无法启动

现象&#xff1a;修改后mysql无法启动&#xff0c;不报错 原因&#xff1a;MySQL在8以后sql_mode已经取消了NO_AUTO_CREATE_USER这个关键字。去掉这个关键字后&#xff0c;启动就可以了 修改前&#xff1a; sql_modeSTRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR…

Bootstrap前端框架Glyphicons字体图标

115工具网收集提供Bootstrap前端框架Glyphicons字体图标库对照表​​​​​​​&#xff0c;Bootstrap前端UI,Glyphicons字体图标调用,Bootstrap按钮字体图标对照表,包括250多个来自Glyphicon Halflings的字体图标.项目中引用Bootstrap相关文件后即可直接调用下列图标class&quo…

Linux CentOS安装PySpark3.5(单机版)详细教程及机器学习实战

目录 一、安装须知 二、安装Spark 1、下载安装包 2、修改配置文件spark-env.sh 3、验证Spark是否安装成功 三、安装py4j 四、配置环境变量 五、基于PySpark的机器学习实战 1、将数据文件上传HDFS 2、创建代码文件 3、提交应用程序 一、安装须知 前置依赖&#xff1…

Acrobat Pro DC 2023 for Mac/Win:全能型PDF编辑器深度解析

Adobe Acrobat Pro DC 2023作为一款跨平台的PDF编辑器&#xff0c;无论是对于Mac还是Windows用户&#xff0c;都提供了极为全面且强大的PDF处理功能。该软件凭借其卓越的性能和丰富的特性&#xff0c;成为了全球范围内用户处理PDF文档的首选工具。 一、强大的编辑功能 Acroba…

【2024高教社杯全国大学生数学建模竞赛】ABCDEF题 问题分析、模型建立、参考文献及实现代码

【2024高教社杯全国大学生数学建模竞赛】ABCDEF题 问题分析、模型建立、参考文献及实现代码 1 比赛时间 北京时间&#xff1a;2024年9月5日 18:00-2024年9月8日20:00 2 思路内容 2.1 往届比赛资料 【2022高教社杯数学建模】C题&#xff1a;古代玻璃制品的成分分析与鉴别方案…

HBase 部署及shell操作

HBase 数据库 一、HBase 概述1.1 HBase 是什么HBase 的特点 二、HBase 模型及架构2.1 HBase 逻辑模型2.2 HBase 数据模型2.3 HBase 物理模型2.3.1 列簇物理模型2.3.2 Rowkey 字段排序2.3.3 Region 存储到不同节点2.3.4 Region 结构 2.4 HBase 基本架构 三、搭建 HBase 分布式集…

Claude的小白入门指南

要想快速上手Claude AI&#xff0c;其实并没有那么复杂。作为新一代的AI助手&#xff0c;Claude致力于为用户提供高效、无害、透明的交互体验。这篇入门指南将从Claude AI的特点、主要功能和如何实际操作等几个方面为大家做一个详细的介绍。 Claude AI是什么&#xff1f; Claud…

【SRC挖掘】越权漏洞——burp插件被动检测越权漏洞,一个插件让挖洞效率翻倍!Autorize

越权与未授权漏洞 越权漏洞什么是越权漏洞&#xff1f;Autorize插件安装使用步骤拦截过滤器 越权漏洞 什么是越权漏洞&#xff1f; 越权漏洞是指应用程序未对当前用户操作的身份权限进行严格校验&#xff0c;导致用户可以操作超出自己管理权限范围的功能&#xff0c;从而操作…

大模型笔记01--基于ollama和open-webui快速部署chatgpt

大模型笔记01--基于ollama和open-webui快速部署chatgpt 介绍部署&测试安装ollama运行open-webui测试 注意事项说明 介绍 近年来AI大模型得到快速发展&#xff0c;各种大模型如雨后春笋一样涌出&#xff0c;逐步融入各行各业。与之相关的各类开源大模型系统工具也得到了快速…

UnityShader自定义属性特性

前言&#xff1a; 在编写UnityShader时&#xff0c;我们常常会使用特性来更换材质球面板的属性外观&#xff0c;除此之外&#xff0c;还可以使用自定义的扩展脚本来实现自定义的材质球界面&#xff0c;参考我之前的文章UnityShaderUI编辑器扩展 但是自定义扩展每次都要单独写…

性能测试经典案例解析——远程培训系统

各位好&#xff0c;我是 道普云 一站式云测试SaaS平台。一个在软件测试道路上不断折腾十余年的萌新。 欢迎关注我的专栏和我的主页 道普云 文章内容具有一定门槛&#xff0c;建议先赞再收藏慢慢学习&#xff0c;有不懂的问题欢迎私聊我。 希望这篇文章对想提高软件测试水平…

5.sklearn-朴素贝叶斯算法、决策树、随机森林

文章目录 环境配置&#xff08;必看&#xff09;头文件引用1.朴素贝叶斯算法代码运行结果优缺点 2.决策树代码运行结果决策树可视化图片优缺点 3.随机森林代码RandomForestClassifier()运行结果总结 本章学习资源 环境配置&#xff08;必看&#xff09; Anaconda-创建虚拟环境…

Keil下载烧录程序到单片机提示flash outtime超时

Flash Timeout.Reset the Target and try it again. Error:Flash Download failed - “Cortex-M4” 解决方法&#xff1a; 1.检查杜邦线 2.RESET按键按一下再下载(或者一直按着下载)&#xff0c;这样能让单片机进入烧录模式。 3.有外部看门狗&#xff0c;外部看门狗用跳帽屏…

『功能项目』DOTween动态文字【26】

打开上一篇25协程生成怪物模型的项目&#xff0c; 本章要做的事情是用DOTween插件做一个动态文字效果 首先在资源商店中免费下载一个DOTween插件 新建脚本&#xff1a;DowteenFlicker.cs 编写脚本&#xff1a; using DG.Tweening; using UnityEngine; using UnityEngine.UI;pu…

2024 年高教社杯全国大学生数学建模竞赛题目-C 题 农作物的种植策略

根据乡村的实际情况&#xff0c;充分利用有限的耕地资源&#xff0c;因地制宜&#xff0c;发展有机种植产业&#xff0c;对乡村经济 的可持续发展具有重要的现实意义。选择适宜的农作物&#xff0c;优化种植策略&#xff0c;有利于方便田间管理&#xff0c;提 高生产效益&#…

在模板中使用 Django 会话

在 Django 中使用会话&#xff08;session&#xff09;可以让你在用户访问网站的过程中存储和访问临时数据。我们可以利用会话在速度计算器的例子中存储和显示上次计算的结果。 1、问题背景 在 Django 中&#xff0c;可以使用会话来存储用户数据。在某些情况下&#xff0c;我们…

Python实战项目:天气数据爬取+数据可视化(完整代码)_python爬虫实战

一、选题的背景 随着人们对天气的关注逐渐增加&#xff0c;天气预报数据的获取与可视化成为了当今的热门话题&#xff0c;天气预报我们每天都会关注&#xff0c;天气情况会影响到我们日常的增减衣物、出行安排等。每天的气温、相对湿度、降水量以及风向风速是关注的焦点。通过…