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

news/2024/9/18 17:18:24/ 标签: 网络, 计算机网络, 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.ppmy.cn/news/1523324.html

相关文章

前端面试热点题目——typescript篇

在TypeScript面试中&#xff0c;面试官通常会考察你对TypeScript特性的理解、类型系统的掌握、以及在实际项目中的应用能力。以下是一些热点题目及其相应的代码示例&#xff0c;旨在帮助你准备TypeScript相关的面试。 1. 类型别名与接口的区别及使用场景 问题&#xff1a;请解…

react js 笔记 3

起因&#xff0c; 目的: 专注。 学习 react js 的时候&#xff0c; 就专注这一方面 &#xff0c;其他都不要碰。 比如&#xff0c; python, C语言&#xff0c; R, 都不看。 只看 js.专注&#xff0c;减少来回切换。 重复。 自己写的笔记&#xff0c;需要反复多看几遍&#xff…

java开发后端

1.BeanUtils.toBean 方法 它是一个常见的 Java 工具方法&#xff0c;用于将一个 JavaBean 对象转换为另一个 JavaBean 对象 FlowOrderDO flowOrder BeanUtils.toBean(createReqVO, FlowOrderDO.class); 这行代码使用了 BeanUtils.toBean 方法&#xff0c;它是一个常见的 Ja…

MySQL笔记2(DQL查询语言【条件、分组、排序、限制、子查询、左右连接、内连接、联合查询】)

DQL数据查询语言与项目高级查询实战 先安装数据库并创建一个库 并创建以下数据 /*创建部门表*/CREATE TABLE dept( deptnu INT PRIMARY KEY comment 部门编号, dname VARCHAR(50) comment 部门名称, addr VARCHAR(50) comment 部门地址 );/*某个公司的员工表*/ CREATE TABLE…

html备忘录

备忘录 网站收藏数据&#xff1a; 网站收藏.js const webLinks [{ title: "智能翻译", src: "https://fanyi.baidu.com" },{ title: "哔哩哔哩", src: "https://www.bilibili.com" },{ title: "百度一下&#xff0c;你就知道&…

漫谈设计模式 [9]:外观模式

引导性开场 菜鸟&#xff1a;老鸟&#xff0c;我最近在做一个项目&#xff0c;感觉代码越来越复杂&#xff0c;我都快看不懂了。尤其是有好几个子系统&#xff0c;它们之间的调用关系让我头疼。 老鸟&#xff1a;复杂的代码确实让人头疼。你有没有考虑过使用设计模式来简化你…

微信支付开发避坑指南

1 微信支付的坑 1.1 不能用前端传递过来的金额 订单的商品金额要从数据库获取&#xff0c;前端只传商品 id。 1.2 交易类型trade type字段不要传错 v2版API&#xff0c;不同交易类型&#xff0c;要调用的支付方式也不同。 1.3 二次签名 下单时&#xff0c;在拿到预支付交…

记录深度学习量化操作

0. 简介 深度学习中做量化提升运行速度是最常用的方法&#xff0c;尤其是大模型这类非常吃GPU显存的方法。一般是高精度浮点数表示的网络权值以及激活值用低精度&#xff08;例如8比特定点&#xff09;来近似表示达到模型轻量化&#xff0c;加速深度学习模型推理&#xff0c;目…

MySQL表的操作与数据类型

目录 前言 一、表的操作 1.创建一个表 2.查看表的结构 3.修改表 4.删除一个表 二、 MySQL的数据类型 0.数据类型一览&#xff1a; 1.整数类型 2.位类型 3.小数类型 4.字符类型 前言 在MySQL库的操作一文中介绍了有关MySQL库的操作&#xff0c;本节要讲解的是由库管理的结构——…

TinyWebSever源码逐行注释(三)_ thread_pool.cpp

前言 项目源码地址 项目详细介绍 项目简介&#xff1a; Linux下C轻量级Web服务器&#xff0c;助力初学者快速实践网络编程&#xff0c;搭建属于自己的服务器. 使用 线程池 非阻塞socket epoll(ET和LT均实现) 事件处理(Reactor和模拟Proactor均实现) 的并发模型使用状态机…

python基础语法四-数据可视化

书接上回&#xff1a; python基础语法一-基本数据类型 python基础语法二-多维数据类型 python基础语法三-类 1. plot函数绘制简单折线图 (1)需要的模块&#xff1a;matplotlib.pyplot (2)语法&#xff1a;matplotlib.pyplot.plot(x, y, format_string, **kwargs) x: x轴数…

C语言程序设计-练习篇

不知道结果仍义无反顾地才是勇士。 三&#xff0c;打印整数二进制的奇数位和偶数位 题目内容&#xff1a; 获取一个整数二进制序列中所有的奇数位和偶数位&#xff0c;分别打印出二进制序列 #include <stdio.h>//打印整数二进制的奇数位和偶数位 int main() {int i 0…

C语言从头学55——学习头文件errno.h、float.h

1、头文件 errno.h 中的变量 errno 的使用 在 errno.h 定义了一个 int 类型的变量 errno&#xff08;错误码&#xff09;&#xff0c;如果发现这个变量出现非零值&#xff0c;表示已经执行的函数发生了错误。这个变量一般多用于检查数学函数运算过程中发生的错误。 …

Vue面试题——项目介绍以及SPA介绍

谈谈你开发的项目背景与、架构和技术栈 项目背景 假设我们正在开发一个名为“智慧旅游助手”的Web平台。该平台旨在为用户提供一站式的旅游服务&#xff0c;包括目的地推荐、酒店预订、行程规划、在线购票&#xff08;如门票、机票&#xff09;、旅游攻略分享以及基于地理位置…

不到200行代码,一键写出简单贪吃蛇网页游戏!附详细代码!快来看看吧!

​哈喽大家好&#xff0c;这里是大白百宝阁&#xff0c;每天分享一段小代码~ 今天要分享的是&#xff0c;不到200行代码&#xff0c;制作html版贪吃蛇&#xff0c;效果如下&#xff1a; 游戏结束后&#xff0c;还会显示&#xff1a; 代码如下&#xff1a; <!DOCTYPE html&g…

传统CV算法——边缘算子与图像金字塔算法介绍

边缘算子 图像梯度算子 - Sobel Sobel算子是一种用于边缘检测的图像梯度算子&#xff0c;它通过计算图像亮度的空间梯度来突出显示图像中的边缘。Sobel算子主要识别图像中亮度变化快的区域&#xff0c;这些区域通常对应于边缘。它是通过对图像进行水平和垂直方向的差分运算来…

基于Java+SpringBoot+Vue+MySQL的美容美发管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 基于SpringBootVue的美容美发管理系统【附源码文档】、前后…

【Visual Studio 报错】vs 在使用二进制写入文件时弹窗报错:使用简体中文 gb2312 编码加载文件

如以下报错 解决办法 解决方法&#xff1a;文件->高级保存选项->将文件编码形式改为“UTF-8带签名” 若找不到高级保存选项&#xff0c;可以跟着下面路径把该选项调出来 &#xff1a;工具->自定义->命令->菜单栏中改成文件->预览右边点添加命令->类别中…

第二证券:涨停潮!传手机将使用钛金属外壳?

今天早盘&#xff0c;银行股再度重挫&#xff0c;导致上证指数、上证50纷乱创出阶段性新低&#xff0c;上证指数跌破2800点&#xff0c;小盘成长股则大面积反弹&#xff0c;创业板指、科创50等股指飘红。 盘面上&#xff0c;新式烟草、钛金属、锂矿、玻璃基板等板块涨幅居前&a…

全球性“微软蓝屏”事件的深思:网络安全与系统稳定性的挑战与应对

近日&#xff0c;由于微软视窗系统软件更新引发的全球性“微软蓝屏”事件&#xff0c;成为科技领域的热点新闻。这次事件不仅影响了全球约850万台设备&#xff0c;波及航空、医疗、传媒等关键行业&#xff0c;还导致美国超过2.3万架次航班延误。如此规模的系统中断&#xff0c;…