网络编程套接字,Linux下实现echo服务器和客户端

目录

1、一些网络中的名词

1.1 IP地址

1.2 端口号port

1.3  "端口号" 和 "进程ID"

1.4 初始TCP协议

1.5 UDP协议

2、socket编程接口

2.1 socket 常见API

2.2 sockaddr结构

3、简单的网络程序

3.1 udp实现echo服务器和客户端

3.1.1 echo服务器实现

3.1.2 echo客户端实现

3.1.3 运行结果

3.2  tcp实现echo服务器和客户端

3.2.1 多进程的echo服务器

3.2.2 基于线程池tcp的echo服务器

 3.3 代码中的一些函数

3.3.1 地址转换函数

3.3.2 udp使用的的函数

3.3.3 tcp使用的函数

4、结语


1、一些网络中的名词

1.1 IP地址

        IP地址就和我们现实中的地址是一个概念,只不过一个在网络中定位,一个在现实中定位,

        在一台服务器往另一台服务器发送数据的时候,IP数据包的头部中,有两个IP地址,一个是源IP地址,另一个是目的IP地址,

1.2 端口号port

端口号(port)是传输层协议的内容.

        端口号是一个2字节16位的整数;

        端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;

        IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;

        一个端口号只能被一个进程占用.

1.3  "端口号" 和 "进程ID"

        pid 表示唯一一个进程; 此处我们的端口号也是唯一表示一个进程。一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定。

1.4 初始TCP协议

传输层协议

有连接

可靠传输

面向字节流

1.5 UDP协议

传输层协议

无连接

不可靠传输

面向数据报

网络字节序

        在计算机的内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?

        发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;

        接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;

        因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.

        TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.

        不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;

        如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;

        为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

#include <arpa/inet.h>uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

h表示host,n表示network,l表示32位长整数,s表示16位短整数。

例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。

如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;

如果主机是大端字节序,这些  函数不做转换,将参数原封不动地返回。

2、socket编程接口

2.1 socket 常见API

// 创建 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);

2.2 sockaddr结构

        socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及UNIX Domain Socket. 然而, 各种网络协议的地址格式并不相同.

         IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址.

        IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.

        socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数;

sockaddr 结构

struct sockaddr{__SOCKADDR_COMMON (sa_);	/* Common data: address family and length.  */char sa_data[14];		/* Address data.  */};

sockaddr_in 结构

struct sockaddr_in{__SOCKADDR_COMMON (sin_);in_port_t sin_port;			/* Port number.  */struct in_addr sin_addr;		/* Internet address.  *//* Pad to size of `struct sockaddr'.  */unsigned char sin_zero[sizeof (struct sockaddr) -__SOCKADDR_COMMON_SIZE -sizeof (in_port_t) -sizeof (struct in_addr)];};

        虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址.

in_addr结构

struct in_addr{in_addr_t s_addr;};

in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数;

3、简单的网络程序

3.1 udp实现echo服务器和客户端

3.1.1 echo服务器实现

//udp_server.hpp
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unordered_map>
#include <vector>#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>class udpserver{
public:udpserver(std::string ip, int16_t port):_fd(-1), _ip(ip), _port(port),_users(0){}~udpserver(){if (_fd > 0) {close(_fd);}}void initServer() {_fd = socket(AF_INET, SOCK_DGRAM, 0);if (_fd < 0) {perror("注册socket失败");exit(2);}struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = inet_addr(_ip.c_str());if (bind(_fd, (struct sockaddr*)&local, sizeof(local)) < 0) {perror("绑定失败!");exit(3);}//std::cout << "绑定成功!"<< std::endl;}void startServer(){//准备用来接收客户端发送的消息的缓冲区char buffer[1024];while (1) {//准备用来接收发送消息的客户端信息memset(buffer, '\0', sizeof(buffer));struct sockaddr_in peer;memset(&peer, 0, sizeof(peer));socklen_t len = sizeof(peer);//接收数据,以及接收发送数据的客户端信息//std::cout << "正在接收!" << std::endl;ssize_t recv_size = recvfrom(_fd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);//打印客户端发送来的数据//std::cout << "接收成功!正在打印:" << std::endl;// if (recv_size > 0) {//     buffer[recv_size] = 0;//     std::string ip = inet_ntoa(peer.sin_addr);//     int16_t port = ntohs(peer.sin_port);//     std::cout << "[" << ip << ":" << port << "]:";//     std::cout << buffer << std::endl;// }//处理数据buffer[recv_size] = 0;std::string massage;massage += inet_ntoa(peer.sin_addr);massage += ":";massage += ntohs(peer.sin_port);//_users.insert(make_pair<std::string,struct sockaddr_in>(massage, peer);_users.insert({massage, peer});massage += "#";massage += buffer;//_users.insert(makepair(, peer);//回写数据//sendto(_fd, buffer, strlen(buffer), 0, (struct sockaddr*)&peer, len);for (auto &s : _users) {sendto(_fd, massage.c_str(), massage.size(),0 ,(struct sockaddr*)&(s.second), sizeof(s.second));}}}private:int _fd;std::string _ip;int16_t _port;std::unordered_map<std::string,struct sockaddr_in> _users;
};
//udp_server.cpp
#include "udpserver.hpp"
#include <memory>int main(int argc, char* args[]) {std::string ip;int16_t port = 0;if (argc == 3) {ip = args[1];port = atoi(args[2]);}else if (argc == 2) {ip = "0.0.0.0";port = atoi(args[1]);}else{perror("输入错误!");return 1;}std::unique_ptr<udpserver> server(new udpserver(ip,port));server->initServer();server->startServer();return 0;
}

3.1.2 echo客户端实现

//udp_client.cpp
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <pthread.h>
#include <cstdio>#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>struct sendData{int _sock;struct sockaddr_in *server;
};void* sending(void *arg) {struct sendData* data = (struct sendData*)arg;int sock = data->_sock;struct sockaddr_in server = *(data->server);while (1) {std::string massage;std::cerr << "请输入内容:" ;std::getline(std::cin, massage);//发送数据sendto(sock, massage.c_str(), massage.size(), 0, (struct sockaddr*)&server, sizeof(server));}
}void* receive(void *arg) {struct sendData* data = (struct sendData*)arg;int sock = data->_sock;char buffer[1024];while (1) {memset(buffer, '\0', sizeof(buffer));struct sockaddr_in from;socklen_t len = sizeof(from);ssize_t recv_size = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&from, &len);if(recv_size < 1) {continue;}buffer[recv_size] = '\0';//printf("[%s:%u]#%s\n",inet_ntoa(from.sin_addr),ntohs(from.sin_port),buffer);std::cout << buffer << std::endl;}
}//客户端,负责给服务端发送消息
int main(int argc, char* args[]) {if (argc != 3) {std::cerr << "请正确输入参数!" << std::endl;exit(1);}std::string ip = args[1];int16_t port = atoi(args[2]);//创建套接字int _sock = socket(AF_INET, SOCK_DGRAM, 0);//这里依然会绑定,但是不需要手动绑定,回自动绑定,在第一次send的时候自动绑定,  if (_sock < 0) {exit(2);}struct sockaddr_in server;server.sin_addr.s_addr = inet_addr(ip.c_str());server.sin_family = AF_INET;server.sin_port = htons(port);socklen_t len = sizeof(server);sendData data;data._sock = _sock;data.server = &server;//创建线程,让线程1负责发送,线程2负责接收pthread_t send,recv;pthread_create(&send,nullptr,sending,(void*)&data);pthread_create(&send,nullptr,receive,(void*)&data);pthread_join(send,nullptr);pthread_join(recv,nullptr);close(_sock);return 0;
}

3.1.3 运行结果

3.2  tcp实现echo服务器和客户端

3.2.1 多进程的echo服务器


#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <signal.h>#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>static void servise(int serviseSock, std::string userip, int16_t userport) {char buffer[1024];while (1) {memset(buffer, 0, sizeof(buffer));size_t s = read(serviseSock,buffer,sizeof(buffer));    if (s > 0) {buffer[s] = '\0';std::cout << userip.c_str() << ":" << userport << "#" << buffer << std::endl;}else if (s == 0) {//表示对方关闭了连接std::cerr << userip << ":" << userport << " shutdowm,me too!" << std::endl;break;}else {std::cerr << "read socket error," << errno << strerror(errno) << std::endl;break;}write(serviseSock, buffer, strlen(buffer));}
}class tcpServer{
public:tcpServer(int16_t port, std::string ip = ""):_ip(ip),_port(port),_listenSock(-1){}~tcpServer(){if (_listenSock > 0) {close(_listenSock);}}void initServer(){//backlog不能太大也不能太小static int gbacklog = 20;//申请描述符_listenSock = socket(AF_INET, SOCK_STREAM, 0);if (_listenSock < 0) {std::cerr << "注册socket失败" << std::endl;exit(2);}//绑定端口号和IP地址struct sockaddr_in local;local.sin_family = AF_INET;local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());local.sin_port = htons(_port);if (bind(_listenSock, (struct sockaddr*)&local, sizeof(local)) < 0) {std::cerr << "绑定ip和端口号失败" << std::endl;exit(3);}//设置监听状态if (listen(_listenSock, gbacklog) < 0) {std::cerr << "设置监听失败" << std::endl;exit(4);}}void start() {//将子进程的信号改为忽略signal(SIGCHLD, SIG_IGN);while (1) {struct sockaddr_in user;socklen_t len = sizeof(user);int serviseSock = accept(_listenSock, (struct sockaddr*)&user, &len);std::string userip = inet_ntoa(user.sin_addr);int16_t userport = ntohs(user.sin_port);//servise(serviseSock,userip,userport);int pid = fork();if (pid == 0) {close(_listenSock);servise(serviseSock,userip,userport);close(serviseSock);exit(0);}close(serviseSock);}}private:std::string _ip;int16_t _port;int _listenSock;
};

        但是我们都知道,在操作系统中,进程是资源分配的基本单位,如果使用多进程的方案的话,就非常的浪费资源,所以,相比之下,使用多线程的方式回更好,我们在实现一个基于线程池的实现方式。

3.2.2 基于线程池tcp的echo服务器

//自己实现的循环队列,当中使用的锁和信号都是自己封装的,这里就不放代码了
//ringqueue.hpp
#include <iostream>
#include <vector>
#include "sem.hpp"
#include "mutex.hpp"template<class T>
class ringqueue {public:ringqueue(int capacity = 10):_ring_queue(capacity),_start(0),_tail(0),_space_sem(capacity),_data_sem(0),_mtx(){}void push(const T &in){_space_sem.p();_mtx.lock();_ring_queue[_start++] = in;_start %= _ring_queue.size();_data_sem.v();_mtx.unlock();}void pop(T & out){_data_sem.p();_mtx.lock();out = _ring_queue[_tail++];_tail %= _ring_queue.size();_space_sem.v();_mtx.unlock();}~ringqueue(){}private:std::vector<T> _ring_queue;int _start;int _tail;sem _space_sem;sem _data_sem;mutex _mtx;
};
//单例模式的线程池
//其中的线程也是自己进行封装的,不做代码展示
//thread_pool.hpp
#include "thread.hpp"
#include "ringQueue.hpp"
#include <ctime>
#include <unistd.h>template <class T>
struct poolData
{Thread* _self;ringqueue<T>* _rq;
};template <class T>
class Pool
{public:static Pool<T>* getpool(int num = 10){if (nullptr == _pool) {pthread_mutex_lock(&mtx);if (nullptr == _pool) {_pool = new Pool<T>(num);}pthread_mutex_unlock(&mtx);}return _pool;}private:Pool(int num) :_consumer(num),_rq(10){}Pool(const Pool& pool) = delete;Pool& operator=(const Pool& pool) = delete;
public:void strat(){poolData<T> condata[_consumer.size()];for (int i = 0; i < _consumer.size(); ++i) {_consumer[i] = new Thread(i);condata[i]._self = _consumer[i];condata[i]._rq = &_rq;_consumer[i]->create(consumer,&condata[i]);}}// 生产者void pushTask(T task){_rq.push(task);}// 消费者static void *consumer(void *args){poolData<T> *pd = (poolData<T>*)args;Thread *self = pd->_self;ringqueue<T> *rq = pd->_rq;std::cout << self->name() << " Successfully started!" << std::endl;while (true) {T t;rq->pop(t);(*t)();delete t;}}~Pool(){for (int i = 0; i < _consumer.size(); ++i) {_consumer[i]->join();delete _consumer[i];}}private:ringqueue<T> _rq;std::vector<Thread*> _consumer;static pthread_mutex_t mtx;static Pool<T>* _pool;
};template<class T>
Pool<T>* Pool<T>::_pool = nullptr;template<class T>
pthread_mutex_t Pool<T>::mtx = PTHREAD_MUTEX_INITIALIZER;
//tcp_server.hpp
#include "thread_pool.hpp"
#include "Task.hpp"#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <signal.h>#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>static void servise(int serviseSock, std::string & userip, int16_t userport) {char buffer[1024];while (1) {memset(buffer, 0, sizeof(buffer));size_t s = read(serviseSock,buffer,sizeof(buffer));    if (s > 0) {buffer[s] = '\0';std::cout << userip.c_str() << ":" << userport << "#" << buffer << std::endl;}else if (s == 0) {//表示对方关闭了连接std::cerr << userip << ":" << userport << " shutdowm,me too!" << std::endl;break;}else {std::cerr << "read socket error," << errno << strerror(errno) << std::endl;break;}write(serviseSock, buffer, strlen(buffer));}close(serviseSock);
}class tcpServer{
public:tcpServer(int16_t port, std::string ip = ""):_ip(ip),_port(port),_listenSock(-1),_pool_ptr(Pool<Task*>::getpool()){}~tcpServer(){if (_listenSock > 0) {close(_listenSock);}}void initServer(){//backlog不能太大也不能太小static int gbacklog = 20;//申请描述符_listenSock = socket(AF_INET, SOCK_STREAM, 0);if (_listenSock < 0) {std::cerr << "注册socket失败" << std::endl;exit(2);}//绑定端口号和IP地址struct sockaddr_in local;local.sin_family = AF_INET;local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());local.sin_port = htons(_port);if (bind(_listenSock, (struct sockaddr*)&local, sizeof(local)) < 0) {std::cerr << "绑定ip和端口号失败" << std::endl;exit(3);}//设置监听状态if (listen(_listenSock, gbacklog) < 0) {std::cerr << "设置监听失败" << std::endl;exit(4);}}void start() {_pool_ptr->strat();while (1) {struct sockaddr_in user;socklen_t len = sizeof(user);int serviseSock = accept(_listenSock, (struct sockaddr*)&user, &len);std::string userip = inet_ntoa(user.sin_addr);int16_t userport = ntohs(user.sin_port);Task *task = new Task(serviseSock, userip, userport, servise);_pool_ptr->pushTask(task);}}private:std::string _ip;int16_t _port;int _listenSock;Pool<Task*>* _pool_ptr;
};
//服务器入口,
//tcp_server.cpp
#include "tcp_server.hpp"
#include <memory>int main(int argc, char* args[]) {std::string ip;int16_t port;if (argc == 2) {ip = "";port = atoi(args[1]);}else if (argc == 3) {ip = args[1];port = atoi(args[2]);}else {std::cerr << "输入错误!" << std::endl;exit(1);}std::unique_ptr<tcpServer> server(new tcpServer(port,ip));server->initServer();server->start();return 0;
}

3.2.3 运行结果

 3.3 代码中的一些函数

3.3.1 地址转换函数

        本节基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位  的IP 地址但是我们通常用点分十进制的字符串表示IP 地址,以下函数可以在字符串表示  和in_addr表示之间转换;

字符串与in_addr的一些函数:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
in_addr_t inet_network(const char *cp);
char *inet_ntoa(struct in_addr in);
struct in_addr inet_makeaddr(int net, int host);
in_addr_t inet_lnaof(struct in_addr in);
in_addr_t inet_netof(struct in_addr in);

3.3.2 udp使用的的函数

发送函数sendto:

#include <sys/types.h>
#include <sys/socket.h>ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);

接收函数recvfrom:

#include <sys/types.h>
#include <sys/socket.h>ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

3.3.3 tcp使用的函数

发送函数

#include <sys/types.h>
#include <sys/socket.h>ssize_t send(int sockfd, const void *buf, size_t len, int flags);
#include <unistd.h>ssize_t write(int fd, const void *buf, size_t count);

接收函数

#include <unistd.h>ssize_t read(int fd, void *buf, size_t count);
#include <sys/types.h>
#include <sys/socket.h>ssize_t recv(int sockfd, void *buf, size_t len, int flags);

4、结语

        本文中若有错误,请私信或评论指出,谢谢!

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

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

相关文章

Arrays.copyOf 和System.arraycopy?深拷贝和浅拷贝?

Arrays.copyOf 和 System.arraycopy 1&#xff09;二者有何不同&#xff1f; System.arraycopy()方法 System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length); 需主动创建目标对象dest可定义起始元素&#xff0c;灵活拷贝元素比较重要的一点&…

华为三层交换机与路由器对接上网

华为三层交换机与路由器对接上网

昇腾Ascend TIK自定义算子开发教程(概念版)

一、参考资料 【2023 CANN训练营第一季】Ascend C算子开发入门&#xff08;中&#xff09; 二、重要说明 TIK2编程范式把算子核内的处理程序&#xff0c;分成多个流水任务&#xff0c;任务之间通过队列&#xff08;Queue&#xff09;进行通信和同步&#xff0c;并通过统一的…

目标检测笔记(十三): 使用YOLOv5-7.0版本对图像进行目标检测完整版(从自定义数据集到测试验证的完整流程))

文章目录 一、目标检测介绍二、YOLOv5介绍2.1 和以往版本的区别 三、代码获取3.1 视频代码介绍 四、环境搭建五、数据集准备5.1 数据集转换5.2 数据集验证 六、模型训练七、模型验证八、模型测试九、评价指标 一、目标检测介绍 目标检测&#xff08;Object Detection&#xff…

2023国赛高教社杯数学建模C题思路分析

1 赛题 在生鲜商超中&#xff0c;一般蔬菜类商品的保鲜期都比较短&#xff0c;且品相随销售时间的增加而变差&#xff0c; 大部分品种如当日未售出&#xff0c;隔日就无法再售。因此&#xff0c; 商超通常会根据各商品的历史销售和需 求情况每天进行补货。 由于商超销售的蔬菜…

【AWS】如何用SSH连接aws上的EC2实例(虚拟机)?

目录 0.环境 1.连接结果示例 2.SSH连接思路 3.具体步骤 1&#xff09;安装并运行ssh服务 2&#xff09;启动ssh服务 3&#xff09;在AWS上找到正在运行的EC2实例&#xff0c;并且根据提供的ssh连接语句进行连接 0.环境 windows 11 64位 前提&#xff1a; 有aws账户&…

学生信息系统(python实现)

#codingutf-8 import os.path filenamestudent.txtdef menm():#菜单界面print(学生管理系统)print(-----------------------------功能菜单-----------------------------)print(\t\t\t\t\t\t1.录入学生信息)print(\t\t\t\t\t\t2.查找学生信息)print(\t\t\t\t\t\t3.删除学生信息…

list【2】模拟实现(含迭代器实现超详解哦)

模拟实现list 引言&#xff08;实现概述&#xff09;list迭代器实现默认成员函数operator* 与 operator->operator 与 operator--operator 与 operator!迭代器实现概览 list主要接口实现默认成员函数构造函数析构函数赋值重载 迭代器容量元素访问数据修改inserterasepush_ba…

堆排序详解

堆&#xff1a;是一种特殊的完全二叉树&#xff0c;一般通过顺序表存储&#xff0c;分为大堆和小堆两类。 大堆&#xff1a;父节点的值恒大于子节点的值。 小堆&#xff1a;父节点的值恒小于子节点的值。 创建堆&#xff0c;可以使得根节点成为整个堆中保存最大或最小的值的…

基于jeecg-boot的flowable流程历史记录显示修改

更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; https://gitee.com/nbacheng/nbcio-boot 前端代码&#xff1a;https://gitee.com/nbacheng/nbcio-vue.git 在线演示&#xff08;包括H5&#xff09; &#xff1a; http://122.227.135.243:9888 历…

一文搞定接口幂等性架构设计方案

幂等性介绍 现如今很多系统都会基于分布式或微服务思想完成对系统的架构设计。那么在这一个系统中&#xff0c;就会存在若干个微服务&#xff0c;而且服务间也会产生相互通信调用。那么既然产生了服务调用&#xff0c;就必然会存在服务调用延迟或失败的问题。当出现这种问题&a…

系列四、Nginx的常用命令和配置文件

一、常用命令 1.1、查看nginx的版本号 ./nginx -v 1.2、启动nginx cd /usr/local/nginx/sbin./nginx 1.3、停止nginx cd /usr/local/nginx/sbin./nginx -s stop 1.4、重新加载nginx 说明&#xff1a;该命令用于修改配置文件后&#xff0c;在不重启nginx的情况下使配置文…

FPGA通信—千兆网(UDP)软件设计

一、PHY引脚功能描述 引脚功能描述1CLK25 CLK125:内部PLL生成的125MHz参考时钟&#xff0c;如MAC未使用125MHe时钟&#xff0c;则此引脚应保持浮动&#xff0c; 2 4 63 GND 接地3REG OUT开关压器&#xff0c;1.05V输出 5 6 8 9 11 12 14 15 MDI[0] MDI[0]- MDI[1] MDI[1…

学习笔记-BNF、EBNF、ABNF语法格式描述规范

目标是确认一些c/cpp的语法细节&#xff0c;需要看cpp语法定义文件。 考虑从c的语法定义文件开始确认。 考虑实现一个简化的语言定义和编译器&#xff0c;为后续的实际需求做自定义扩展。 参考网页&#xff1a; https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_f…

高可用Kuberbetes部署Prometheus + Grafana

概述 阅读官方文档部署部署Prometheus Grafana GitHub - prometheus-operator/kube-prometheus at release-0.10 环境 步骤 下周官方github仓库 git clone https://github.com/prometheus-operator/kube-prometheus.git git checkout release-0.10 进入工作目录 cd kube…

二、[mysql]之Explain讲解与实战

目录 一、了解Explain1.Explain介绍 二、Explain相关字段1.partitions2.filtered3.SHOW WARNINGS命令 三、Explain比较重要字段1.id2.select_type3.table4.type5.possible_keys6.key7.key_len8.ref9.rows10.Extra 四、索引优化实战&#xff08;遵循原则&#xff09;1.全值匹配2…

python关闭指定进程以excel为例

先说下环境&#xff1a; Excel版本&#xff1a; Python2.7.13和Python3.10.4并存。 2、打开两个excel工作簿 看进程是这样的&#xff1a; 3、用python编程kill进程 # -*- coding: utf-8 -*- import os proc_nameEXCEL.EXE if __name__ __main__:os.system(taskkill /im {} /…

【vue2第十六章】VueRouter 声明式导航(跳转传参)、路由重定向、页面未找到的提示页面404、vue路由模式设置

声明式导航(跳转传参) 在一些特定的需求中&#xff0c;跳转路径时我们是需要携带参数跳转的&#xff0c;比如有一个搜索框&#xff0c;点击搜索的按钮需要跳转到另外一个页面组件&#xff0c;此时需要把用户输入的input框的值也携带到那页面进行发送请求&#xff0c;请求数据。…

python 随机生成emoji表情

问答板块觉得比较有意思的问题 当时搜了些网上的发现基本都不能用&#xff0c;不知道是版本的问题还是咋的就开始自己研究 python随机生成emoji 问题的产生解决官网文档数据类型实现思路实现前提&#xff1a;具体实现&#xff1a; 其他常见用法插入 Emoji 表情&#xff1a;解析…

【ES6】Class中this指向

先上代码&#xff1a; 正常运行的代码&#xff1a; class Logger{printName(name kexuexiong){this.print(hello ${name});}print(text){console.log(text);} }const logger new Logger(); logger.printName("kexueixong xiong");输出&#xff1a; 单独调用函数p…