网络——套接字编程UDP
目录
端口号
源端口号和目的端口号
认识TCP协议和UDP协议
网络字节序
socket编程接口
socket常见接口
sockaddr结构
UDP
socket
bind
recvfrom
sendto
编写客户端
绑定INADDR_ANY
实现聊天功能
端口号
在这之前我们已经说过源IP地址和目的IP地址,还有源MAC地址和目的MAC地址。接下来我们再来谈一谈什么是端口号。
源端口号和目的端口号
首先我们要知道的是IP地址标定了主机的唯一性,当我们在打游戏或者刷视频的时候,一定是通过手机或者电脑,里面一定会有对应的App,这叫做客户端软件,运行起来后就是客户端进程,我们通过请求服务端进程来执行某种功能,通过TCP/IP协议把数据送到对方的主机,但是这不是真正的网络通信的过程,它本质上是在进行进程间通信,将数据在主机间转发只是过程,所以机器收到后需要将数据交付给指定的进程。
端口号(port)就是标识特定主机上进程的唯一性,他是一个2字节16位的整数。但是之前我们说过,进程pid也是标识进程的唯一性,那么为什么不用pid代替端口号呢?原因是,进程是进程,端口号是端口号,这两个概念本来就没有什么关系,我们不希望让两个没有关系的概念揉在一起。
所以在网络上两台主机要进行通信就要有源IP地址和源端口号,还要有目的IP和目的端口号,通过IP地址和端口号就可以标识网络中唯一一个进程,我们把他们就叫做套接字。
认识TCP协议和UDP协议
在应用层下就是传输层,传输层使用的是由操作系统提供的系统调用接口,而传输层最典型的两种协议就是TCP和UDP协议。
UDP协议:
UDP协议叫做用户数据报协议(User Datagram Pool),它无需建立连接,而且不可靠,面向数据报的传输层协议,可能会出现丢包、数据包乱序的问题。
TCP协议:
TCP协议叫做传输控制协议(Transmission Control Protocol),它是一种可连接、可靠的、面向字节流的传输层通信协议。
看着UDP是不是没有TCP可靠,但是在现在的主流网络丢包的问题概率并不大,有些是可以容忍的。可不可靠只是他们两个的一个特点,不可靠不会为了让网络通信变得可靠而做过多的工作,可靠性是需要大量的编码和数据处理的,TCP为了保证可靠性一定要设计更多的策略,这些都要用户自己去完成,虽然保证了可靠性,但是它就会更复杂,维护成本也更高。
网络字节序
在计算机中是有大小端的概念的,大端就是高字节内容放在低地址处,低字节内容放在高地址处;小端就是高字节内容放在高地址处,低字节内容放在低地址处。
如果编写的程序在本地上是不需要考虑这些问题的,如果是网络通信,就不确定对方的主机是哪种存储方式。
由于我们不能保证通信双方存储数据的方式是一样的,所以就规定网络当中传输的数据必须采用大端字节序,无论是大端机还是小端机,都必须转换成大端。
所以小端机器发送时要将数据转换成大端,接受数据时再把数据转换成小端。大端机器就不需要考虑这些。
为了解决这个问题,也有了这些接口:
- h代表host,主机的意思
- n代表network,网络的意思
- l 代表long,32位的意思
- s代表short,16位的意思
所以上面这些接口无非就是主机转网络,或者是网络转主机。
socket编程接口
socket常见接口
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器) int socket(int domain, int type, int protocol);// 绑定端口号 (TCP/UDP, 服务器) int bind(int socket, const struct sockaddr *address, socklen_t address_len);// 开始监听socket (TCP, 服务器) int listen(int socket, int backlog);// 接收请求 (TCP, 服务器) int accept(int socket, struct sockaddr* address, socklen_t* address_len);// 建立连接 (TCP, 客户端) int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockaddr结构
套接字不仅支持网络的进程间通信,还支持本地的进程间通信(域间套接字)。在进行网络通信时我们需要传递IP地址和端口号,而本地通信则不需要,因此套接字提供了sockaddr_in结构体和sockaddr_un结构体,其中sockaddr_in结构体是用于跨网络通信的,而sockaddr_un结构体是用于本地通信的。
因为设计师不想设计不同的结构对应不同的通信方式,于是就出现了sockaddr结构体,他们三个虽然结构不同,但是三个结构体前16位比特位都是一样的,这叫做套接字的域或者叫协议家族,之后我们也是要填充这个字段的。
在传参时统一传的都是sockaddr结构体。通过前16个比特位确定通信方式,
UDP
作为一个服务器,那就一定要有服务端和用户端,我们就先来写一下服务端。
socket
要进程网络通信,第一步就要创建socket套接字。
参数:
- domain:创建套接字的域或者叫协议家族,网络通信就设置为AF_INET(IPV6折纸为AF_INET6),本地通信就设置为AF_UNIX,这其实是一个宏。
- type:创建套接字的服务类型,字节流SOCK_STREAM或数据报SOCK_DGRAM。
- protocol:创建套接字的协议类别,可以指明值TCP或UDP。一般只设置为0。
返回值:成功返回一个文件描述符,失败返回-1,错误码被设置。
_sock = socket(AF_INET, SOCK_DGRAM, 0);if (_sock < 0){logMessage(FATAL, "%d:%s", errno, strerror(errno));exit(2);}
bind
作用:将ip和port在内核中和当前进程强关联。
参数:
- sockfd:套接字文件描述符
- addr:我们要填充的服务端网络属性信息,包括协议家族、端口号、IP地址,因为我们使用的是sockaddr_in,所以最后要取地址强转为struct sockaddr*
- addrlen:addr结构体的长度
返回值:绑定成功返回0,失败返回-1,错误码被设置。
套接字文件描述符我们是有的,addrlen可以使用sizeof,那就剩addr了,除了memset可以初始化,bzero也是可以初始化的,这些接口也多用一下。
struct sockaddr_in local就是我们要填充的字段。
三个参数看着也很熟悉:
- sin_family:这是域或者协议家族。
- sin_port:这是端口号,16位整数。
- sin_addr:这是IP地址,32位整数。
typedef unsigned short int sa_family_t; #define __SOCKADDR_COMMON(sa_prefix) \sa_family_t sa_prefix##familytypedef uint16_t in_port_t;typedef uint32_t in_addr_t; struct in_addr {in_addr_t s_addr; };struct sockaddr_in {__SOCKADDR_COMMON (sin_); // 协议家族in_port_t sin_port; // 16位端口号struct in_addr sin_addr; // 32位IP地址// ... };
未来要把数据发送给服务端,也要把自己的IP和端口号发送到网络,所以一定要改变网络字节序。不过我们要注意的是,IP地址通常是以字符串形式出现的,所以还要再做一下处理,使用inet_addr函数就可以把IP地址从char*类型转换成网络序列的32位整数。
至此我们初始化的工作就做好了,我们把它封装成一个类中的成员函数。
class UdpServer { public:UdpServer(uint16_t port, std::string ip = "0.0.0.0"):_port(port),_ip(ip),_sock(-1){} bool initServer(){// 1. 创建套接字_sock = socket(AF_INET, SOCK_DGRAM, 0);if (_sock < 0){// 这就是使用到我们进程池写的日志文件了logMessage(FATAL, "%d:%s", errno, strerror(errno));exit(2);}// 2. bind绑定,将ip和port在内核中和当前进程强关联struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;// 主机到网络的转换,port是16位的使用host to net shortlocal.sin_port = htons(_port);// 把IP地址从点分十进制字符串变成4字节32位整数,再变成网络序列local.sin_addr.s_addr = inet_addr(_ip.c_str());if (bind(_sock, (struct sockaddr*)&local, sizeof local) < 0){logMessage(FATAL, "%d:%s", errno, strerror(errno));exit(2);}logMessage(NORMAL, "%s", "init udp server done ... ", strerror(errno));}private:// 服务器必须要有ip地址和端口号uint16_t _port; // 192.168.16.1std::string _ip;int _sock; };
recvfrom
这个时候我们的服务器就已经可以启动了,UDP的初始化只需要创建套接字加绑定就可以了,服务器就是一直为用户提供某种服务,所以服务器运行起来后就永远不会退出,实际上执行的就是一个死循环代码。
由于UDP服务器是不面向连接,也不需要其他操作,而UDP服务器读取客户端数据的函数就是这个。
参数:
- sockfd:套接字文件描述符
- buf:读取的数据要放到的缓冲区
- len:要读取的字节数
- flags:读取的方式,一般设置为0,表示阻塞式读取
- src_addr:输出型参数,对端网络的属性
- addrlen:调用时传入的src_addr结构体的长度,返回实际读取到的实际长度,这是一个输入输出型参数
返回值:读取成功返回实际读取的字节数,读取失败返回-1,错误码被设置。
因为UDP不面向连接,所以一定要获取对方的网络信息,如IP和端口号,recvfrom函数提供的参数是struct sockaddr*类型的,所以一定要取地址强转。
使用inet_ntoa函数就可以把struct in_addr中的IP从32位整数的网络序列转化为本机的char*类型。
// 作为一款服务器来说,一定是永远不退出的 // 所以该进程一定是一个常驻进程,永远在内存中存在。除非进程终止了#define SIZE 1024char buffer[SIZE]; for (;;) {struct sockaddr_in peer; // 对端网络的属性信息bzero(&peer, sizeof(peer)); // 初始化socklen_t len = sizeof(peer);// 读取数据ssize_t s = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);if (s > 0){buffer[s] = 0; // 传过来的数据// 1.输出信息// 2.是谁发的uint16_t cli_port = ntohs(peer.sin_port); // 从网络中来的数据要转换成本机字节序std::string cli_ip = inet_ntoa(peer.sin_addr); // 4字节的32位整数IP网络序列->本主机的字符串点分十进制IPprintf("[%s:%d]# %s\n", cli_ip, cli_port, buffer);}// ... }
sendto
UDP服务器返回给客户端数据的函数
参数:
- sockfd:套接字文件描述符
- buffer:要把那个缓冲区中的数据返回对端
- len:缓冲区字符的个数
- flags:一般为0,表示阻塞写入
- dest_addr:对端网络相关的属性信息
- addrlen:dest_addr结构体的长度
返回值:成功返回实际写入的字节数,写入失败返回-1,错误码被设置。
// udp_server.hpp#include <iostream> #include <string>#include <unistd.h> #include <cerrno> #include <cstring> #include <cstdio> #include <cstdio>#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h>#include "log.hpp"#define SIZE 1024class UdpServer { public:UdpServer(uint16_t port, std::string ip = "0.0.0.0"):_port(port),_ip(ip),_sock(-1){} bool initServer(){// 1. 创建套接字_sock = socket(AF_INET, SOCK_DGRAM, 0);if (_sock < 0){logMessage(FATAL, "%d:%s", errno, strerror(errno));exit(2);}// 2. bind绑定,将ip和port在内核中和当前进程强关联struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;// 主机到网络的转换,port是16位的使用host to net shortlocal.sin_port = htons(_port);// 把IP地址从点分十进制字符串变成4字节32位整数,再变成网络序列local.sin_addr.s_addr = inet_addr(_ip.c_str());if (bind(_sock, (struct sockaddr*)&local, sizeof local) < 0){logMessage(FATAL, "%d:%s", errno, strerror(errno));exit(2);}logMessage(NORMAL, "init udp server done ... %s", strerror(errno));return true;}void Start(){// 作为一款服务器来说,一定是永远不退出的// 所以该进程一定是一个常驻进程,永远在内存中存在。除非进程终止了char buffer[SIZE];for (;;){struct sockaddr_in peer; // 对端网络的属性信息bzero(&peer, sizeof(peer)); // 初始化socklen_t len = sizeof(peer);// 读取数据ssize_t s = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);if (s > 0){buffer[s] = 0; // 传过来的数据// 1.输出信息// 2.是谁发的uint16_t cli_port = ntohs(peer.sin_port); // 从网络中来的数据要转换成本机字节序std::string cli_ip = inet_ntoa(peer.sin_addr); // 4字节的32位整数IP网络序列->本主机的字符串点分十进制IPprintf("[%s:%d]# %s\n", cli_ip.c_str(), cli_port, buffer);}// 分析数据// 写回数据sendto(_sock, buffer, strlen(buffer), 0, (struct sockaddr*)&peer, len);}}~UdpServer(){if (_sock >= 0) close(_sock);}private:// 服务器必须要有ip地址和端口号uint16_t _port; // 192.168.16.1std::string _ip;int _sock; };
至此,我们的UDP服务端就已经写好了,封装了一下服务端。
编写客户端
// udp_client.cc#include <iostream> #include <string>#include <cstring> #include <cstdio>#include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h>// .udp_client server_ip server_port static void usage(std::string proc) {std::cout << "\nUsage: " << proc << " ip port\n" << std::endl; }int main(int argc, char* argv[]) {if (argc != 3){usage(argv[0]);exit(1);}// 1.创建套接字int sock = socket(AF_INET, SOCK_DGRAM, 0);if (sock < 0){std::cerr << "socket error" << std::endl;exit(2);}// client不需要主动bind,这是一个客户端,普通用户不知道,如果程序员写了bind,那么一定bind了一个固定的端口号// 如果这个端口号被其他进程占用了呢,所以就让OS自动随机选择端口号std::string message;struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(atoi(argv[2]));server.sin_addr.s_addr = inet_addr(argv[1]);char buffer[1024];while (true){std::cout << "请输入:";std::getline(std::cin, message);// 当client首次发送消息给服务器的时候,OS会自动给client bind他的IP和PORTsendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));// 读取struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&temp, &len);if (s > 0){std::cout << "Server echo# " << buffer << std::endl;}}return 0; }
首先就来说一下客户端绑定的问题,网络通信中IP地址和端口号一定要的,服务器是给别人提供服务的,所以就必须要告诉别人服务器的IP和端口号。因为服务器是一个死循环的进程,选定的端口号不能轻易改变,所以服务端必须要bind。客户端不需要绑定的原因就是他在访问服务器的时候,只需要确定此时的端口号唯一,如果他绑定了,那么客户端就只能通过这个端口号访问服务器,所以写客户端的时候就不要bind,当调用sendto的时候操作系统会自动给当前客户端获取一个唯一端口号。
写好了之后就可以启动服务端和客户端了。
// udp_server.cc#include "udp_server.hpp" #include <memory> #include <cstdlib>// .udp_server ip port void usage(std::string proc) {std::cout << "\nUsage: " << proc << " ip port\n" << std::endl; }int main(int argc, char* argv[]) {if (argc != 3){usage(argv[0]);exit(1);}std::string ip = argv[1];uint16_t port = atoi(argv[2]);std::unique_ptr<UdpServer> svr(new UdpServer(port, ip));svr->initServer();svr->Start();return 0; }
我们通过netstat命令查看网络状态,netstat的选项:
- -n:直接使用IP地址,而不通过域名服务器。
- -l :显示监控中的服务器的Socket。
- -t :显示TCP传输协议的连线状况。
- -u:显示UDP传输协议的连线状况。
- -p:显示正在使用Socket的程序识别码和程序名称。
我们的服务端流程,通过设置本机IP和固定端口号初始化UdpServer,创建套接字,再bind将IP和port进行强关联,编辑本机的sockaddr;运行服务器,recvfrom读取数据,接受客户端的sockaddr信息,处理过后再调用sendto给客户端发送回去。
客户端流程,设置要访问的服务端IP和端口号,编辑服务端sockaddr,再调用sendto向服务端发送信息,最后读取服务端的信息。
使用netstat -nlup查看当前网络信息。
绑定INADDR_ANY
客户端和服务端在本机中已经可以实现通信了,如果想要在网络中通过客户端访问服务端绑定自己的公网IP,但是我现在用的是一个云服务器,它的IP地址并不是真正的公网IP,所以不能被绑定,让外网访问就要绑定0,系统中提供了一个宏就是INADDR_ANY,它的值就是0。
一个服务器中,可能放了多张网卡,那么就会有多个IP地址,但是端口号只有一个,服务器在接收数据的时候,多张网卡都收到了数据,如果bind的时候是指定IP地址的,那就只能从这个IP地址中接收数据,如果bind的是INADDR_ANY,只要是发送给指定端口号的,就可以从不同的IP地址接收交给服务端。
// 2. bind绑定,将ip和port在内核中和当前进程强关联 struct sockaddr_in local; bzero(&local, sizeof(local)); local.sin_family = AF_INET; // 主机到网络的转换,port是16位的使用host to net short local.sin_port = htons(_port); // 把IP地址从点分十进制字符串变成4字节32位整数,再变成网络序列 local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
实现聊天功能
我们现在想要实现一个聊天功能,一个客户端向服务端发送信息,服务端收到信息,并把这个用户对应的信息保存起来,再把消息从服务端发送回客户端;此时我们再来一个用户,一样的操作,这时候两个客户端应该可以看到两个人发送的信息。
// 添加一个成员变量 std::unordered_map<std::string, struct sockaddr_in> _users; // IP-PORT : sockaddr_in// 修改一下Start成员函数char buffer[SIZE]; char key[64]; // 将ip-port写到key中 for (;;) {// ...ssize_t s = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);if (s > 0){// ...snprintf(key, sizeof(key), "%s-%u", cli_ip.c_str(), cli_port);logMessage(NORMAL, "key: %s", key);auto it = _users.find(key); // 把信息写到map中if (it == _users.end()){logMessage(NORMAL, "add new user: %s", key);_users.insert({key, peer});}}// 写回数据for (auto& iter : _users){std::string sendMessage = key;sendMessage += "# ";sendMessage += buffer; logMessage(NORMAL, "push message to %s", iter.first.c_str());sendto(_sock, sendMessage.c_str(), sendMessage.size(), 0, (struct sockaddr*)&iter.second, sizeof(iter.second));} }
想法很美好,但是现实往往和想象中的不一样,一开始还行,后面打印的都是什么东西,原因就是IO被阻塞了,就是当我们getline拿用户输入的数据的时候,后面的sendto和recvfrom都不会执行,所以现在就可以使用多线程,一个线程发数据,另一个线程负责收数据。
这就有一个要注意的点,不管是读数据还是写数据都要用sock,如果使用多线程就要把sock设置成全局的,或者再把客户端封装成一个类,成员变量对于整个类也是全局的。那会不会有线程安全的问题呢,这也是没有的,因为在多线程之前就要创建出socket,而线程只是用这个socket,并不会修改它,所以可以并发访问。
我们再把之前已经封装好的线程拿过来,这样sock会直接传入ThreadData中,所以也就不需要把sock定义成全局的。
#include "thread.hpp"// port、ip uint16_t serverport = 0; std::string serverip;// .udp_client ip port static void usage(std::string proc) {std::cout << "\nUsage: " << proc << " ip port\n" << std::endl; }static void *udpSend(void *args) {// 拿到sockint sock = *(int *)((ThreadData *)args)->args_;std::string name = ((ThreadData *)args)->name_;// 填充服务端信息std::string message;struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());while (true){// 输入数据,发送std::cerr << "请输入:";std::getline(std::cin, message);if (message == "quit")break;sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));}return nullptr; }static void *udpRecv(void *args) {int sock = *(int *)((ThreadData *)args)->args_;std::string name = ((ThreadData *)args)->name_;char buffer[1024];while (true){struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *)&temp, &len);if (s > 0){buffer[s] = 0;std::cout << buffer << std::endl;}}return nullptr; }int main(int argc, char *argv[]) {if (argc != 3){usage(argv[0]);exit(1);}// 1.创建套接字int sock = socket(AF_INET, SOCK_DGRAM, 0);if (sock < 0){std::cerr << "socket error" << std::endl;exit(2);}serverport = atoi(argv[2]);serverip = argv[1];std::unique_ptr<Thread> sender(new Thread(1, udpSend, (void *)&sock)); // 发送线程std::unique_ptr<Thread> recver(new Thread(2, udpRecv, (void *)&sock)); // 接收线程sender->start();recver->start();sender->join();recver->join();close(sock);return 0; }
至此多线程就写好了,虽然用的socket都是同一个,但是没有读写冲突的情况,因为UDP是全双工的,可以同时进行收和发,不会受到干扰。
我们在目录下使用mkfifo创建两个管道文件client1和client2,将客户端输出重定向到管道文件,再使用cat输出重定向,这就好像类似于一个输入框和一个显示框。
相关文章:

网络——套接字编程UDP
目录 端口号 源端口号和目的端口号 认识TCP协议和UDP协议 网络字节序 socket编程接口 socket常见接口 sockaddr结构 UDP socket bind recvfrom sendto 编写客户端 绑定INADDR_ANY 实现聊天功能 端口号 在这之前我们已经说过源IP地址和目的IP地址,还有…...
FPGA_AD9361
1.集成12位DAC和ADC的一款器件,2个输入模拟通道和2个输出模拟通道 2.• TX频段:47 MHz至6.0 GHz • RX频段:70 MHz至6.0 GHz 3.SPI配置成LVDS或CMOS接口,也可以还可以选择FDD(频分双工——全双工,操作时需…...

探讨Java代码混淆加固工具
摘要 本篇博客将介绍几种常用的Java代码混淆工具,如ProGuard、Allatori Java Obfuscator、VirboxProtector、ipaguard和DashO。我们将深入探讨它们的特点、功能以及在保护Java应用程序安全方面的作用。此外,还将强调在使用Java代码混淆工具时需要注意的安…...

Linux——du, df命令查看磁盘空间使用情况
一、实现原理: df 命令的全称是Disk Free ,显而易见它是统计磁盘中空闲的空间,也即空闲的磁盘块数。它是通过文件系统磁盘块分配图进行计算出的。 du 命令的全称是 Disk Used ,统计磁盘有已经使用的空间。它是直接统计各文件各目…...

数据库实验(一)SQL Server触发器
目录 触发器的定义 触发器和存储过程的区别 触发器的优点 触发器的作用 触发器的分类 DML触发器 DDL触发器 登录触发器 触发器的工作原理 inserted表 deleted表 创建触发器 编程要求 测试要求: 实验代码: 触发器的定义 触发器是建立在触…...
添加网址到主页
基于localStorage的网址收藏夹-CSDN博客 为了通过安卓菜单添加网址到主页中,调试了几个小时,主要踩了几个坑。 1.localStorage 通过域名隔离,需要加载主页才能读写。 2.WebView 可以不显示,但是 JS 代码要放在 window.onload 中…...
消息中间件如何实现高可用
消息中间件实现高可用的方式有很多种,常见的方法包括: 集群部署:通过在多台服务器上部署消息中间件实例,构成一个集群,提高整体系统的可用性。当一台机器出现故障时,其他机器可以继续提供服务。主从复制&a…...

Hbase 王者荣耀数据表 HBase常用Shell命令
大数据课本: HBase常用Shell命令 在使用具体的Shell命令操作HBase数据之前,需要首先启动Hadoop,然后再启动HBase,并且启动HBase Shell,进入Shell命令提示符状态,具体命令如下: $ cd /usr/local…...

家用智能洗地机哪个牌子好?4款型号让你解锁高效省力生活体验
在今天的社会中,随着生活节奏的加快,人们对于家庭清洁的需求不断增加。传统的清洁方法已经无法满足现代家庭的需求。因此,洗地机作为一种高效、方便的清洁工具,已经成为了许多家庭首选的清洁设备。然而,在市场上&#…...

Linux--进程(1)
目录 前言 1.冯诺依曼体系结构 2. 操作系统(Operator System)--第一个被加载的软件 3.进程 3.1基本概念 3.2Linux中的PCB 3.3通过系统调用创建子进程-fork初识 fork:创建一个子进程 为什么要创建子进程? fork的原理: 进一步了解fo…...

Qt登录页面
#include "mywidget.h" #include "ui_mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent), ui(new Ui::MyWidget) {ui->setupUi(this);//接收动图QMovie *mv new QMovie(":/pictrue/luori.gif");ui->loglab->setMovie(…...

软件工程-第8章 软件测试
8.1 软件测试目标域软件测试过程模型 8.2 软件测试技术 8.3 静态分析技术-程序正确性证明 8.4 软件测试步骤 8.5 本章小结...

专业135+总分400+重庆邮电大学801信号与系统考研经验重邮电子信息与通信工程,真题,大纲,参考书。
今年分数出来还是比较满意,专业801信号与系统135,总分400,没想到自己也可以考出400以上的分数,一年的努力付出都是值得的,总结一下自己的复习心得,希望对大家复习有所帮助。专业课:(…...

主干网络篇 | YOLOv8改进之在主干网络中引入密集连接卷积网络DenseNet
前言:Hello大家好,我是小哥谈。DenseNet(密集连接卷积网络)是一种深度学习神经网络架构,它在2017年由Gao Huang等人提出。DenseNet的核心思想是通过密集连接(dense connection)来促进信息的流动和共享。在传统的卷积神经网络中,每个层的输入只来自于前一层的输出。而在…...
lavarel的php程序是顺序执行,用pdo mysql连接池好像没有什么用啊。没有办法挂起等待啊,为什么要用连接池,应用场景是什么
Laravel 的 PHP 程序确实是基于请求-响应模式,每个请求都是顺序执行的。这意味着一旦一个请求开始处理,它会按照代码的顺序执行,直到完成并返回响应。因此,从表面上看,使用 PDO 或 MySQL 连接池在 Laravel 中可能看起来…...
spring maven项目 实时接口请求次数及时间发送到grafana监控_亲测成功
spring maven项目 实时接口请求次数及时间发送到grafana监控_亲测成功 说明: spring项目使用aop方式拿到请求接口uri,算出从请求到响应的耗时, 然后使用statsd包发送udp数据给grafana去展示. 完全不影响代码性能和稳定性,因为使用udp协议发送,就算grafana那边挂了,也不影响项…...

银行数字人民币系统应用架构设计
2019年10月,01区块链联合数字资产研究院发布了《人民币3.0:中国央行数字货币运行框架与技术解析》,从数字货币界定和人民币发展历程出发,区分了央行数字货币与比特币、移动支付等的区别,全面介绍了央行数字货币的发展历…...

流畅的 Python 第二版(GPT 重译)(三)
第五章:数据类构建器 数据类就像孩子一样。它们作为一个起点是可以的,但要作为一个成熟的对象参与,它们需要承担一些责任。 马丁福勒和肯特贝克 Python 提供了几种构建简单类的方法,这些类只是一组字段,几乎没有额外功…...

06-验证浮点数输入
鉴于shell脚本的限制和本事,浮点数(或“实数”)的验证过程乍一看似乎让人望而生畏,不过考虑到浮点数只不过是由小数点分隔的两个整数,再配合能够在脚本中引用其他脚本的能力(validint)ÿ…...

shell实现查询进程号并批量kill(脚本)
问题或需求描述 在shell中,如果你想通过命令行查询出一系列匹配某个关键词的进程,并使用xargs命令批量结束这些进程,可以按照以下步骤操作: # 查询并提取进程号 pgrep -f "关键词" | xargs kill# 或者,如果…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...

全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...

破解路内监管盲区:免布线低位视频桩重塑停车管理新标准
城市路内停车管理常因行道树遮挡、高位设备盲区等问题,导致车牌识别率低、逃费率高,传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法,正成为破局关键。该设备安装于车位侧方0.5-0.7米高度,直接规避树枝遮…...

tauri项目,如何在rust端读取电脑环境变量
如果想在前端通过调用来获取环境变量的值,可以通过标准的依赖: std::env::var(name).ok() 想在前端通过调用来获取,可以写一个command函数: #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...
深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向
在人工智能技术呈指数级发展的当下,大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性,吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型,成为释放其巨大潜力的关键所在&…...
华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)
题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...