【网络编程】UDP 编程实战:从套接字到聊天室多场景计划构建

news/2025/9/25 22:51:51/文章来源:https://www.cnblogs.com/ljbguanli/p/19112257

【网络编程】UDP 编程实战:从套接字到聊天室多场景计划构建


请添加图片描述


半桔:个人主页

  个人专栏: 《网络编程》《手撕面试算法》《C++从入门到入土》

我们需要为自己设定的任务,不是拥有安全感,而是能够接受不安全感。 -艾伦·弗洛姆-

文章目录

  • 前言
  • 一. 套接字接口
  • 二. UDP服务端
  • 三. 服务端 + 线程池
  • 四. 在线字典
  • 五. UDP简单聊天室
  • 六. 补充

前言

在互联网深度渗透的今天,网络编程已成为软件开发的核心能力之一。从实时互动的在线游戏,到物联网设备的远程通信,再到分布式系统的高效协作,网络协议的灵活运用直接决定了应用的性能与体验。

其中,UDP(用户数据报协议) 以其 无连接、低延迟、高吞吐量 的特性,在直播、实时监测、轻量化通信等场景中占据不可替代的地位 —— 比如游戏中的实时对战、物联网传感器的批量数据上报,都依赖 UDP 实现 “高效优先” 的通信。

本文围绕 UDP 网络编程 展开,以 “从基础到实战” 的递进逻辑,帮你拆解开发中的核心环节,本文分为5个部分:

  1. 套接字接口 :我们将从底层 API 入手,理解 UDP 如何创建套接字,绑定以及收发消息的,建立网络编程的基本认知;
  2. 搭建 UDP 服务端 :构建服务端的工作全流程,掌握 “单线程服务端” 的开发;
  3. 引入 线程池优化服务端 :面对高并发场景,学习通过线程池提升服务端性能,理解 “并发模型” 如何解决性能瓶颈;
  4. 实战项目落地 :通过 在线字典(演示 UDP 如何实现 “请求 - 响应” 式服务)和 UDP 简单聊天室(实践多客户端实时通信),将理论转化为可运行的应用,体会 UDP 在实际业务中的设计思路;

现在,就让我们从 “套接字接口” 开始,开启 UDP 网络编程的学习之旅吧。

一. 套接字接口

创建套接字

int socket(int domain , int type , int protrol)

  1. 参数一:域/协议族,常用的有:AF_INET基于IPv4网络的通信,支持TCP和UDP;AF_INET6基于IPv6的网络通信;AF_UNIX用于本地进程间通信。本文实现UDP采用IPv4的协议;
  2. 参数二:套接字种类,常用是有:SOCK_DGRAM关联UDP协议;SOCK_STREAM关联TCP协议;
  3. 参数三:协议,在参数一和二的基础上进一步锁定具体的传输规则,一般直接传0即可;
  4. 返回值,一个Socket描述符,类似于文件描述符,只不过是网络文件描述符,失败返回-1.

对于服务端要进行IP地址和端口号绑定才能进行使用;
绑定套接字:

int bind(int sockfd , const struct sockaddr* addr , socklen_t addren)

  1. 参数一:Socket描述符;
  2. 参数二:一个套接字结构体,内部存储要进行绑定的的IP和端口号;
  3. 参数三:第二个参数的大小;
  4. 返回值:成功返回0,失败返回-1。

以为是UDP通信,因此第二个参数我们使用的结构体是:struct sockaddr_in

struct sockaddr_in {
sa_family_t sin_family;
/* Address family */
unsigned short int sin_port;
/* Port number */
struct in_addr sin_addr;
/* Internet address */
/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
/* Internet address. */
struct in_addr {
__u32 s_addr;
};
  • 其中sin_famuliy标识协议家族,使用与创建套接字时传入的第一个参数相同;
  • unsighned short int存储端口号;
  • struct in_addr存储IP地址。

网络字节序一律使用大端字节序存储,因此对于端口要保证放入的是大端。
库中有一系列的接口来让我们传入的数据转化为网络字节序:

uint32_t htonl(uint32_t hostlong);
// 转网络字节序,对uint32_t
uint16_t htons(uint16_t hostshort);
// 转网络字节序,对uint16_t
uint32_t ntohl(uint32_t netlong);
// 转主机字节序
uint16_t ntohs(uint16_t netshort);
// 转主机字节序

对于IP地址,存储时需要存储的是整形,而不是字符串,库中也提供了相关的接口供我们使用:

int inet_aton(const char *cp, struct in_addr *inp);
// 传入字符串和结构体,将字符串转为整数后填入到结构体中
in_addr_t inet_addr(const char *cp);
// 将字符串整数
char *inet_ntoa(struct in_addr in);
// 转化为字符串

接受信息
ssize_t recvfrom(int sockfd , void* buf , size_t len , int flag , struct sockaddr *src_addr , socklen_t *addrlen)

  1. 参数一:网络文件描述符;
  2. 参数二:输出型参数,将读取到的信息放到此处,参数三这是空间的大小;
  3. 参数四:选项,选择等待方式,其中0标识阻塞式等待;
  4. 参数5和6都是输出型参数,获取发送端的套接字结构体。

发送信息

ssize_t sendto(int sockfd , const void* buf , size_t len , int flag , const struct sockaddr *dest_addr , socklen_t addrlen ):参数与上面一样。

二. UDP服务端

在构建服务器代码中,统一使用一个日志类来对异常信息进行打印处理,关于日志类不是本文的重点,如果想要了解,可以在此处进行跳转——【工具分享】日志类该怎么设计?从级别划分到异步写入的全解析

使用一个类来实现UDP服务器:

初始化的时候需要外界将这些参数都传进行保存起来,但是并不在初始化时创建套接字,而是当用户运行时才进行创建。

const std::string defaultip = "0.0.0.0";
// 编写Udp服务器
class Server
{
public:
Server(uint16_t port, const std::string &ip = defaultip )
: port_(port), ip_(ip), sockfd_(-1), isrunning_(false)
{
}
private:
uint16_t port_;
// 保存服务端端口号
std::string ip_;
// 保存服务端IP
int sockfd_;
// 网络文件描述符
bool isrunning_;
};

注意:后续在进行bind()绑定的时候,云服务器一般是禁止绑定公网IP的;因为云服务器上一般都多个网卡,可以通过多个IP来接收发送过来的消息,如果直接进行绑定就会导致其他IP都无法进行使用了。

所以一般用服务器的IP地址使用0.0.0.0,表示接收所用发送到这台主机上的数据,再进行端口号进行交付。一次服务端在进行传参的时候就只需要传端口号就行了。

运行云服务器:

  1. 创建套接字;
  2. 进行绑定;
  3. 死循环的接收外部信息。

在此之前我们需要思考以下接收到的信息如何进行处理?

如果我们直接让处理方法都在循环内完成,就会导致代码拓展性差,如果后续希望接入进程池就需要对代码进行重构,因此此处将对接收到的信息处理方法也单独封装一个类:

该类要能够处理接收到的信息,并且将处理完的信息返回到客户端上去,因此该类也需要有套接字,客户端的IP和端口号,以及要进行处理的信息,因为在进行接收数据的时候,就已经拿到了客户端得struct sockaddr_in结构体了,因此直接传该结构体就可以,不用再使用IP和端口号了。

此处对于数据得处理,为了简单我们仅加上前置告诉客户端都到了对应的数据即可:

class Task
{
public:
Task(int sockfd , std::string message , struct sockaddr_in client)
:sockfd_(sockfd) , message_(message) , client_(client)
{
}
void operator()()
{
// 处理任务
std::string ret = "I have got your message : " + message_;
sendto(sockfd_, ret.c_str(), ret.size(), 0, (sockaddr *)&client_, sizeof(client_));
}
private:
int sockfd_;
struct sockaddr_in client_;
std::string message_;
};

有以上方法用来解决数据的处理之后,我们就可以让循环中仅负责读取数据即可:

std::string Recv(struct sockaddr_in *pclient)
{
char buffer[1024];
socklen_t len = sizeof(*pclient);
int n = recvfrom(sockfd_, buffer, sizeof(buffer) - 1, 0, (sockaddr *)pclient, &len);
if (n >
0)
buffer[n] = 0;
return buffer;
}
void Start()
{
isrunning_ = true;
// 创建套接字
sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd_ <
0)
{
Log(Fatal) <<
"socket failed";
exit(Sockfd_Err);
}
// bind绑定
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port_);
inet_aton(ip_.c_str(), &
(local.sin_addr));
if (bind(sockfd_, (sockaddr *)&local, sizeof(local)) <
0)
{
Log(Fatal) <<
"bind failed";
exit(Bind_Err);
}
Log(Info) <<
"bind success , the server is running";
// while循环的接收任务
while(1)
{
struct sockaddr_in client;
std::string message = Recv(&client);
Task task(sockfd_ , message , client);
task();
}
}

服务器的类已经编写完成了,下一步就可以写服务器的程序了:

关于服务器的程序编写很简单,此处就不过多赘述了:

#include "server.hpp"
void Menu(char* argv[])
{
std::cout <<
"\r" << argv[0] <<
" [port] " << std::endl;
}
int main(int argc , char* argv[])
{
if(argc <
2)
{
Menu(argv);
exit(1);
}
uint16_t port = std::stoi(argv[1]);
Server server(port);
server.Start();
return 0;
}

紧接着服务器编写好了,还需要一个客户端来进行通信:

客户端我们也采用一个类来实现,其中类成员与服务端是一样的:

  • 只不过我们可以使用一个strcut sockaddr_in存储服务端的信息,这样在后续进行通信的时候,就不需要重复的设置该结构体了:
class Client
{
public:
Client(const std::string &ip, uint16_t port)
: ip_(ip), port_(port), sockfd_(-1)
{
memset(&server_ , 0 , sizeof(server_));
}
private:
int sockfd_;
std::string ip_;
struct sockaddr_in server_;
uint16_t port_;
};

下一步对服务器进行初始化:

  1. 创建套接字;
  2. “不需要进行绑定“;
  3. 初始化struct sockaddr_in结构体,存储服务端的信息。

上述所说不需要进行绑定,指的是用户不需要进行手动的绑定,有操作系统来进行绑定;因为用户并不知道操作系统中那些端口可以进行绑定,并且用户也不应该关心。

void Init()
{
sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd_ <
0)
{
Log(Fatal) <<
"socket failed";
exit(Socket_Err);
}
server_.sin_family = AF_INET;
server_.sin_port = htons(port_);
if (inet_aton(ip_.c_str(), &
(server_.sin_addr)) == 0)
{
Log(Error) <<
"invalid IP address: " << ip_ ;
close(sockfd_);
// 先关闭已创建的socket
exit(Ip_Err);
// 或其他错误码
}
}

最后就是用户进行发送和接收消息了,依旧是采用sendto()recvfrom()接口:

void Send(std::string message)
{
// 进行发送消息
int n = sendto(sockfd_, message.c_str(), message.size(), 0, (sockaddr *)&server_, sizeof(server_));
}
std::string Recv()
{
char buffer[1024];
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
int n = recvfrom(sockfd_, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&temp, &len);
if (n >
0)
buffer[n] = 0;
return buffer;
}

最后只要再实现服务端即可,为了简单,客户端向服务器发送数据后,当接收到服务端的信息后直接打印即可:

#include "client.hpp"
void Menu(char* argv[])
{
std::cout <<
"\r" << argv[0] <<
" [ip] " <<
" [port] " << std::endl;
}
int main(int argc , char* argv[])
{
if(argc <
3)
{
Menu(argv);
exit(1);
}
std::string ip = argv[1];
uint16_t port = std::stoi(argv[2]);
Client client(ip , port);
client.Init();
while(1)
{
std::string message;
std::cout <<
"Please Enter@";
std::cin >> message;
client.Send(message);
std::string reply = client.Recv();
std::cout <<
"server reply#" << reply << std::endl;
}
return 0;
}

以上就是服务端和客户端的全部实现了。

该服务端可以支持各个平台的客户端运行,下面贴一张Windows的客户端,只是底层调用的接口有一些差异而已:

#define _WINSOCK_DEPRECATED_NO_WARNINGS 
#define _CRT_SECURE_NO_WARNINGS
#include <winsock2.h>#include <stdio.h>#include <string.h>#include <stdbool.h>#pragma comment(lib, "ws2_32.lib")#define BUFFER_SIZE 1024 // 缓冲区大小#define SERVER_FAMILY AF_INET#define SERVER_PORT 8888#define SERVER_ADDR "175.178.50.213"static char s_receBuf[BUFFER_SIZE];// 发送数据的缓冲区static char s_sendBuf[BUFFER_SIZE];// 接受数据的缓冲区void config_server(SOCKADDR_IN* addr_server){addr_server->sin_family = SERVER_FAMILY;addr_server->sin_port = htons(SERVER_PORT);addr_server->sin_addr.S_un.S_addr = inet_addr(SERVER_ADDR);}int main(){SOCKET sock_Client;// 客户端用于通信的SocketWSADATA WSAData;if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0){printf("init error");return -1;} // 初始化sock_Client = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);// 创建客户端用于通信的SocketSOCKADDR_IN addr_server;// 服务器的地址数据结构config_server(&addr_server);SOCKADDR_IN sock;int len = sizeof(sock);while (true){printf("please input send data:");scanf("%s", s_sendBuf);sendto(sock_Client, s_sendBuf, strlen(s_sendBuf), 0, (SOCKADDR*)&addr_server, sizeof(SOCKADDR));// int last = recv(sock_Client, s_receBuf, strlen(s_receBuf), 0); // (调用recv和recvfrom都可以)int last = recvfrom(sock_Client, s_receBuf, sizeof(s_receBuf), 0, (SOCKADDR*)&sock, &len);printf("last:%d,%s\n", last, s_receBuf);if (last >0){s_receBuf[last] = '\0';// 给字符数组加一个'\0',表示结束了。不然输出有乱码if (strcmp(s_receBuf, "bye") == 0){printf("server disconnect\n");break;}else{printf("receive data:%s\n", s_receBuf);}}}closesocket(sock_Client);WSACleanup();return 0;}

三. 服务端 + 线程池

上面的程序是单线程的,当请求过多的时候可能会导致一些数据包丢失,因此为了让服务器更健壮一些,我们接入线程池来使用,关于线程池之前有一篇博客进行过详细剖析,没有看的可以看一下:多线程编程

接入线程池也很简单,只需要进行分工:

void Start()
{
// 先获取线程池,并让线程池运行起来
std::unique_ptr<thread_poll<Task>>&tp_ = thread_poll<Task>::GetInstance();tp_->run();isrunning_ = true;// 创建套接字sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd_ <0){Log(Fatal) <<"socket failed";exit(Sockfd_Err);}// bind绑定struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port_);inet_aton(ip_.c_str(), &(local.sin_addr));if (bind(sockfd_, (sockaddr *)&local, sizeof(local)) <0){Log(Fatal) <<"bind failed";exit(Bind_Err);}Log(Info) <<"bind success , the server is running";// while循环的接收任务while(1){struct sockaddr_in client;std::string message = Recv(&client);Task task(sockfd_ , message , client);tp_->push(task);// 先任务队列中放入任务,让其他线程来处理任务}}

四. 在线字典

上面在进行编写任务的时候比较简单,就是对客户端的信息进行简单的收到返回。

现在我们希望实现一个在线字典,客户端发送一个单词过来,我们可以在字典中进行查找,将含义返回给用户。

  • 首先毫无疑问,我们需要实现一个类,来进行单词的读取,该类要从文件中读取出单词的汉语和英语并存储起来,然后向外提供一个接口可以进行查询;

字典类的实现也很简单,就是单纯的对文本读取,将读取到的数据放入哈希表,建立一一映射关系即可:

class directory
{
public:
directory(const std::string file_name)
{
std::ifstream ifs(file_name.c_str());
std::string eng, ch;
std::string line;
while (std::getline(ifs, line))
{
int pos = line.find(':');
ch = line.substr(0 , pos) , eng = line.substr(pos + 1);
ch_toeng_[ch] = eng;
eng_toch_[eng] = ch;
}
}
std::string find(const std::string word)
{
if(ch_toeng_.count(word)) return ch_toeng_[word];
if(eng_toch_.count(word)) return eng_toch_[word];
return "NO Wold !!! ";
}
private:
std::unordered_map<std::string, std::string> ch_toeng_;// 汉译英std::unordered_map<std::string, std::string> eng_toch_;// 英译汉};

再对Task类进行修改,接入字典类即可,将字典类作为静态成员,方式每次创建:

class Task
{
public:
Task(int sockfd , std::string message , struct sockaddr_in client)
:sockfd_(sockfd) , message_(message) , client_(client)
{
}
void operator()()
{
// 处理任务
std::string ret = dict.find(message_);
sendto(sockfd_, ret.c_str(), ret.size(), 0, (sockaddr *)&client_, sizeof(client_));
}
private:
int sockfd_;
struct sockaddr_in client_;
std::string message_;
static directory dict;
// 接入字典类
};
directory Task::dict = directory("words.txt");

通过以上增添就可以让我们的服务器支持单词的查找了。

五. UDP简单聊天室

将我们的UDP服务器修改成一个聊天室。

我们的服务器可以对先各个用户发送信息,但是想要实现聊天室的功能还要让一个人发送的消息,全部人都能看见才行。

那么我们就需要对所有进行通信过的客户端信息存储起来,当一个人先服务端发送消息的时候,服务端负责将消息发送给全部人

我们在服务器中要添加一个新成员:std::unordered_map<std::string , sockaddr_in> all_client_,将每一个用户存储起来,其中我们以用户的IP作为key值,用户的sockaddr_in结构体作为value值。

在将信息进行转化之前,要先进行判断,判断ip是否是新ip,是否需要加入到哈希表中。

bool IsNewMember(const std::string& ip ,const sockaddr_in &client)
{
if(all_client_.count(ip)) return false;
all_client_[ip] = client;
return true;
}

并且在Task任务的参数中,不能在使用struct sockaddr_in了,而要使用std::unordered_map<std::string , sockaddr_in> all_client_来保证将信息转化给所有人。

class Task
{
public:
Task(int sockfd , std::string message , std::unordered_map<std::string, sockaddr_in>& client):sockfd_(sockfd) , message_(message) , client_(client){}void operator()(){for(auto&[ip_ , client] : client_) // 转发给所有人{sendto(sockfd_ , message_.c_str() , message_.size() , 0 ,(sockaddr*)&client , sizeof(client));}}private:int sockfd_;std::unordered_map<std::string, sockaddr_in>& client_;// 保存哈希表std::string message_;//static directory dict;};

以上就是所有服务端的改写,只不过我们的客户端是单执行流的,不能同时进行接收消息和发送消息,因此我们需要对客户端进行改写:

  • 使用子进程来接收消息,而父进程来进行发送消息。

到目前为止:服务端客户端都已经实现了,可以模拟简单聊天室的功能了。

六. 补充

在服务端绑定端口号的时候也是有讲究的:

一般[0 , 1023]是系统内定端口号,我们一般不能直接绑定,而只能使用1024之后的,但是1024之后也有一些端口号是专属端口号,也不能绑,一般在进行绑定的时候,使用8000以后的端口号。

在上面代码中,使用inet_ntoa来获取struct sockaddr_in中ip地址的,但是它的返回值是一个指针,也就是说该函数内部决定了应该将ip地址存储在哪,一般我们不建议使用inet_ntoa,原因如下:

  • net_ntoa 内部通过 静态内存 存储转换后的 IP 字符串,每次调用都会复用同一块内存。若 连续调用(如同时转换源 IP 和目的 IP),后一次结果会覆盖前一次,导致最终获取的 IP 值错误。

我们一般更建议使用const char *inet_ntop(int af, const void *restrict src, char dst[restrict .size], socklen_t size);这个函数由调用者提供一个缓冲区保存结果, 可以规避线程安全问题

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

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

相关文章

网站重新备案需要多长时间深圳移动网站建设

当Django的内置权限无法满足需求的时候就自己扩展吧~ 背景介绍 overmind项目使用了Django内置的权限系统&#xff0c;Django内置权限系统基于model层做控制&#xff0c;新的model创建后会默认新建三个权限&#xff0c;分别为&#xff1a;add、change、delete&#xff0c;如果给…

做网站的顶部图片怎么打开文件做的网站

目录 一.平台架构与技术特点 二、DeepSeek R1模型介绍与优势 DeepSeek R1 模型简介 DeepSeek R1 模型优势 三.蓝耘智算平台使用DeepSeek教程 展望未来 耘元生代智算云是蓝耘科技推出的一款智算云平台有着以下特点&#xff1a; 一.平台架构与技术特点 基于 Kubernetes 原…

AC自动机在线版本(alert命中报警)

模板洛谷p3311 code: #include<bits/stdc++.h> using namespace std; typedef long long LL; // 常量定义:N为AC自动机状态数上限、数位DP位数上限;mod为答案取模值 const int N=2010,mod=1e9+7;// AC自动机核…

US$79 BMW FEM/BDC Key Programmer Data Desktop Test Platform for FEM/BDC Key and Program ECU Gearbox

BMW FEM/BDC Key Programmer Data Desktop Test Platform for FEM/BDC Key and Program ECU GearboxFEM BDC Module Testing Platform for BMW F20 F30 F35 X5 X6 I3With your FEM/BDC keys, FEM BDC Module Testing P…

week1 homework

C语言代码规范 参考了大公司的标准 1、程序采用缩进风格,每层缩进使用一个制表位(TAB) 2、源程序使用英文书写,尽量不含有中文。 3、左花括号要另起一行,不能跟在上一行的行末; 4、一个变量定义占一行,一个语句…

(南科大深度学习课程笔记)Lecture_2_Mathematical background(数学背景)(上) - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

app那个网站开发比较好xp优化大师

Serverless 话题涉及范围极广&#xff0c;几乎包含了代码管理、测试、发布、运维和扩容等与应用生命周期关联的所有环节。在线应用如何不改代码也能迁移到 Serverless 架构&#xff1f;今天&#xff0c;我们来揭秘阿里巴巴成千上万在线应用的Serverless 演进过程。 AWS Lambda …

最牛网站建设是谁装饰公司办公室图片

有的时候博客内容会有变动&#xff0c;首发博客是最新的&#xff0c;其他博客地址可能会未同步,认准https://blog.zysicyj.top 首发博客地址 系列文章地址 什么是WebSocket&#xff1f; WebSocket是一种在Web应用程序中实现双向通信的协议。它允许在客户端和服务器之间建立持久…

Java EE ----- Spring MVC (上) - 实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

秦皇岛网站排名郑州手机网站制作公司哪家好

可以删一下 按住alt按移除可以删掉 选择你要删的那些线 按住alt点移除

浦东做网站如何在社交网站做销售

一、介绍部分 (win7 下的 GUI 效果图见 本篇文章的最后部分截图2张)wxWidgets是一个开源的跨平台的C构架库(framework)&#xff0c;它可以提供GUI(图形用户界面)和其它工具。目前的2.x版本支持所有版本的Windows、带GTK或Motif的Unix和MacOS。相当于大家熟悉的 VC。二、wxWidge…

Windows 10 C盘占用释放 - tfel

C盘又要撑爆...前言 安装的Windows 10 LSTC, 系统盘留了 200G 最近发现可用只剩下50G, 甚至还在减少 排查 使用 SpaceSniffer 看下具体占用 好用,到Windows 10我也觉得它装机必备!!!占用大头 网易云音乐 缓存它默认…

CherryStudio+cpolar:让智能工作流突破组织边界 - 详解

CherryStudio+cpolar:让智能工作流突破组织边界 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&…

科学计算方法--矩阵分析记录

1. 前言砚上三五笔,落墨鹧鸪啼本文用于记录:科学计算方法--矩阵分析记录 最近有点忘记矩阵了(汗),这里记录一下。 如有不对,欢迎评论区指正! 2. 正文 2.1 矩阵(matrix) 矩阵理解为空间中的一种变换,作用到对…

window.addEventListener(message,()={})中的回调函数无故被一直触发的问题 - broky

遇到的问题使用第一个进入无痕模式就没有被一直触发的问题了 一、最可能的 3 个 “隐式消息发送源”(非你主动写的代码)浏览器插件 / 扩展程序发送的消息 很多浏览器插件(如广告拦截、翻译、开发者工具插件)会通过…

建设单位网站设计有了公网ip如何做网站

盒模型是界面布局需要掌握的基本功。盒模型基本概念 盒模型四要素&#xff1a;margin、border、padding、content。 盒模型分为&#xff1a;标准盒模型&#xff08;W3C盒模型&#xff09; 、 怪异盒模型&#xff08;IE盒模型&#xff09; 盒模型区别 怪异盒模型总宽度 content…

做网站包括什么网站备案的意思

list: quert.list()方法会将从数据库查找到的数据库放到一级缓存和二级缓存,但是不能从一级和二级缓存读取数据,但是可以从二级缓存的查询缓存读取数据, iterate: quert.iterate()方法总会先查找数据表的主键,然后根据每一个主键发送对应的sql语句从数据库读取数据,它可以把数据…

iis7重启 网站河南网站建设服务公司

当然&#xff0c;可以使用 Python 编写一个简单的程序来查找给定列表中的最大和最小值。以下是一个示例程序&#xff1a; def find_max_min(values):if not values: # 检查列表是否为空return None, Nonemax_value values[0]min_value values[0]for value in values:if val…

大连哪家公司做网站桃源网站建设

一、引言 在信息爆炸的时代,网络上蕴含着海量的数据。如果我们想要获取特定的信息,手动从网页上复制粘贴显然效率极低。这时,Web 爬虫就派上了用场。Web 爬虫是一种自动获取网页内容的程序,它可以模拟人类在浏览器中的操作,快速地抓取网页上的数据。本文将带领大家使用 Py…