Socket编程---UDP篇

目录

一.   UDP协议

二.   Socket编程

2.1   sockaddr家族 

2.2   接口介绍 

三.   服务端实现

四.   服务端调用实现

五.   客户端实现

六.   效果展示


一.   UDP协议

何为UDP协议的含义,上篇粗略提及了一下TCP与UDP的区别:

TCP:

• 传输层协议

• 有连接

• 可靠传输

• 面向字节流

UDP:

• 传输层协议

• 无连接

• 不可靠传输

• 面向数据报

那何为可靠,何为不可靠呢?

 TCP协议是有连接的。如果两台主机想要建立通信,就必须先建立连接,通过三次握手(后续博客会讲到)建立连接,只有当连接成功后,才能进行通信。

TCP可靠性体现在:如果数据在传输过程中出现了丢包等等情况,会有相应的解决方法。

TCP可靠性实现方法:

  1. 确认和重传: TCP 使用确认和重传机制来确保数据的完整性和可靠性。接收方会发送确认(ACK)给发送方,告知已成功接收到数据,如果发送方未收到确认,会重新发送数据。
  2. 序号和顺序控制: TCP 会为每个数据段分配一个序号,并且在接收端按序重组数据,以确保数据包按正确的顺序交付。
  3. 流量控制: TCP 使用滑动窗口协议进行流量控制,确保发送方和接收方之间的数据传输速率合理,避免了数据包的过载和丢失。
  4. 拥塞控制: TCP 还实现了拥塞控制机制,通过动态调整发送速率来避免网络拥塞,以提高整体网络性能和稳定性。

UDP协议是无连接的,也就是会说,UDP通信时,无需等待建立连接,只需拿到对应通信主机的端口号+IP地址,就能唯一确定一个进程,实现通信。

 UDP不可靠机制:

UDP 不提供数据传输的确认、重传、排序等机制。如果发送端发送了数据包,不会收到接收端的确认。因此,如果一个数据包在传输中丢失或损坏,UDP 不会重传数据,接收方也无法得知数据包的丢失。

 但是,并不是说,TCP就是百利而无一害的。前面说了,TCP还有一个特性---面向字节流,这就导致了,目标主机读取到的内容可能并不是完整的源主机发送的内容。后续讲TCP实现的时候会体现出来。

二.   Socket编程

2.1   sockaddr家族 

此处我们再次引用一张图:

  • sockaddr_in结构体是用于跨网络通信的
  • sockaddr_un结构体是用于本地通信的

 为了让套接字的网络通信和本地通信能够使用同一套函数接口,于是就出现了sockaddr结构体,该结构体与sockaddr_insockaddr_un的结构都不相同,但这三个结构体头部的16个比特位都是一样的,这个字段叫做协议簇。

这样好处就是:

  • socket编程涉及到的接口函数传参时,只需统一传sockaddr类型的参数即可。
  • 此时通过sockaddr结构体,将套接字网络通信和本地通信在参数方面统一

注意事项:

在进行网络通信编程时,统一定义的还是sockaddr_in结构体,只不过在调用接口时需要将sockaddr_in结构强转位sockaddr类型。

2.2   接口介绍 

  • socket创建套接字
int socket(int domain, int type, int protocol);

参数说明:

1.domin:创建套接字的域或者叫做协议家族,也就是创建套接字的类型(指定通信所用到的协议家族)

相当于sockaddr结构的前16个位的内容。

如果是本地通信就选择AF_UNIX,如果是网络通信就选择AF_INET(IPV4)或者AF_INET6(IPV6),此处我们UDP通信由于是通过网络通信,所以选择AF_INET即可。后面TCP通信也是选择AF_INET

2.type:创建套接字时所需的服务类型(指定具体的通信协议)

  • 如果是UDP通信,选择SOCK_DGRAM(用户数据报服务),UDP是面向数据报的。
  • 如果是TCP通信,选择SOCK_STREAM(流式套接字),TCP是面向字节流的。

3.protocol:创建套接字的协议类别

此处可以指明是UDP通信还是TCP通信,但是一般设置为0,表示默认。系统会自动根据前两个参数推导出是UDP通信还是TCP通信。

 返回值:

成功的话会返回打开的文件描述符。

注意事项:

  • socket函数属于系统调用接口。
  • socket函数是被进程调用的。
  • bind绑定
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

 参数说明:

1.sockfd:绑定的文件的文件描述符

我们调用socket函数后,会返回一个文件描述符,这里就需要绑定这个文件描述符。通信就依赖于这个文件描述符。sockfd是全双工的,既可以收数据,又可以发数据。

2.addr:一个包含自身网络信息的结构体

我们需要确定绑定的IP和端口号,才能通信。

3.addrlen:传入的addr结构体的长度

用sizeof求得即可。

返回值说明:

成功绑定0会被返回,失败-1会被返回,错误码会被设置。

  • 本机端口序列与网络端口序列互相转换函数
//htonl:表示将长整型的本机端口序列转换为长整型的网络端口序列。
uint32_t htonl(uint32_t hostlong);//htons:表示将短整型的本机端口序列转换为短整型的网络端口序列。
uint16_t htons(uint16_t hostshort);//ntohl:表示将长整型的网络端口序列转换为长整型的本机端口序列。
uint32_t ntohl(uint32_t netlong);//ntohs:表示将短整型的网络端口序列转换为短整型的本机端口序列。
uint16_t ntohs(uint16_t netshort);

参数说明:

hostlong代表本机端口序列                                                        netlong代表网络端口序列

uint32_t即长整型                                                                       uint16_t即短整型

  • recvfrom读取数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

参数说明:

1.sockfd:对应操作的文件描述符

表示从该文件描述符索引的文件当中读取数据。

2.buf: 读取数据的存放位置


3.len: 期望读取数据的字节数


4.flags:读取的方式

一般设置为0,表示阻塞读取。


5.src_addr:对端网络相关的属性信息

包括协议簇、IP地址、端口号等。


6.addrlen:期望读取的src_addr结构体的长度

代表实际读取到的src_addr结构体的长度,这是一个输入输出型参数。

返回值说明:

成功实际读到的字节数会被返回,失败-1会被返回,错误码会被设置。

  • sendto发送数据
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);

参数说明:

1.sockfd:对应操作的文件描述符

表示将数据写入到文件描述符索引的文件当中。

2.buf: 待写入数据的存放位置


3.len: 期望写入数据的字节数


4.flags:写入的方式

一般设置为0,表示阻塞写入。


5.dest_addr:对端网络相关的属性信息

包括协议簇、IP地址、端口号等。


6.addrlen:期望读取的dest_addr结构体的长度

代表实际读取到的dest_addr结构体的长度,这是一个输入输出型参数。

返回值说明:

成功实际写入的字节数会被返回,失败-1会被返回,错误码会被设置。 

三.   服务端实现

我们将服务端封装成一个类,并封装对应步骤在类函数中。

class UdpServer
{
public:UdpServer(uint16_t port): _sockfd(defaultfd), _port(port),_isrunning(false){}~UdpServer(){}private:int _sockfd;uint16_t _port; // 服务器所用的端口号bool _isrunning;
};

此处为什么服务端不需要IP地址呢?

这是因为服务器不好绑定一个具体的IP地址,不利于服务的广度。我们此处不设置具体的IP地址,就是为了任何IP都能连接服务器。

 服务端初始成员函数:

void InitServer()
{// 1.创建udp socket 套接字...必须要做的_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(FATAL, "socket error,%s,%d\n", strerror(errno), errno);exit(SOCKET_ERROR);}LOG(INFO, "socket create success,sockfd: %d\n", _sockfd);// 2.1 填充sockaddr_in结构struct sockaddr_in local;     // struct sockaddr_in 系统提供的数据类型,local是变量,用户栈上开辟空间bzero(&local, sizeof(local)); // 清空local.sin_family = AF_INET;local.sin_port = htons(_port); // port要经过网络传输给对面,即port先到网络,所以要将_port,从主机序列转化为网络序列//a.字符串风格的点分十进制的IP地址转成4字节IP//b.将主机序列转成网络序列//in_addr_t inet_addr(const char* cp)->同时完成a&b//local.sin_addr.s_addr =inet_addr(_ip.c_str());local.sin_addr.s_addr=INADDR_ANY;//htonl(INADDR_ANY)// 2.2 bind sockfd和网络信息(IP(?)+Port)int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(n<0){LOG(FATAL, "bind error,%s,%d\n", strerror(errno), errno);exit(BIND_ERROR);}LOG(INFO, "socket bind success\n");}

大概步骤就是通过socket函数创建出套接字,然后填充一个sockaddr_in结构体,最后用这个结构体绑定创建的套接字。

此处LOG函数是仿照日常生活中网络服务的思路,一些操作都有着对应的日志记录,所以创建一个日志文件,实现日志功能。

#pragma once//日志
#include<iostream>
#include<fstream>
#include<cstdio>
#include<string>
#include<ctime>
#include<unistd.h>
#include<sys/types.h>
#include<stdarg.h>
#include<pthread.h>
#include"LockGuard.hpp"using namespace std;bool gIsSave=false;
const string logname="log.txt";void SaveFile(const string& filename,const string& message)
{ofstream out(filename,ios::app);if(!out.is_open()){return;}out<<message;out.close();
}//1.日志是有等级的
enum Level
{DEBUG=0,INFO,WARNING,ERROR,FATAL
};string LevelToString(int level)
{switch(level){case DEBUG: return "Debug";break;case INFO: return "Info";break;case WARNING: return "Warning";break;case ERROR: return "Error";break;case FATAL: return "Fatal";break;default: return "Unknown";break;}
}string GetTimeString()
{time_t curr_time=time(nullptr);struct tm* format_time=localtime(&curr_time);if(format_time==nullptr) return "None";char time_buffer[64];snprintf(time_buffer,sizeof(time_buffer),"%d-%d-%d %d:%d:%d",format_time->tm_year+1900,format_time->tm_mon+1,format_time->tm_mday,format_time->tm_hour,format_time->tm_min,format_time->tm_sec);return time_buffer;
}pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;//2.日志是由格式的
// 日志等级 时间 代码所在的文件名/行数 日志的内容...
void LogMessage(string filename,int line,bool issave,int level,const char* format,...)
{string levelstr=LevelToString(level);string timestr=GetTimeString();pid_t selfid=getpid();//可变参数部分处理char buffer[1024];va_list arg;va_start(arg,format);vsnprintf(buffer,sizeof(buffer),format,arg);va_end(arg);LockGuard lockguard(&lock);string message;message="["+timestr+"]"+"["+levelstr+"]"+"[pid: "+to_string(selfid)+"]"+"["+filename+"]"+"["+to_string(line)+"]"+buffer+"\n";if(!issave){cout<<message;}else{SaveFile(logname,message);}
}void Test(int num,...)
{va_list arg;va_start(arg,num);while(true){int data=va_arg(arg,int);cout<<"data: "<<data<<endl;num--;}va_end(arg);//arg==NULL
}//C99新特性 __VA_ARGS__
#define LOG(level,format,...) do {LogMessage(__FILE__,__LINE__,gIsSave,level,format,##__VA_ARGS__);} while(0)
#define EnableFile() do {gIsSave=true;} while(0)
#define EnableScreen() do {gIsSave=false;} while(0)

此处我们引进了锁来保护临界资源,也是仿照C++中RAII的思路,创建了一个出了作用域自动销毁的锁。内容如下:

#ifndef __lock_GUARD_HPP__
#define __lock_GUARD_HPP__#include<iostream>
#include<pthread.h>class LockGuard
{
public:LockGuard(pthread_mutex_t* mutex):_mutex(mutex){pthread_mutex_lock(_mutex);//构造加锁}~LockGuard(){pthread_mutex_unlock(_mutex);}
private:pthread_mutex_t* _mutex;
};#endif

服务端启动成员函数:

我们可以发现现实生活中,一些软件好像啥时候都能用。比如今天晚上我失眠了。诶!打开抖音刷刷。好像不管我们几点失眠,抖音都能刷视频。所以我们见微知著,服务器应该是一直运行的,这样不论客户端什么时候去访问都能得到回应。

void Start()
{//一直运行,直到管理者不想运行了,服务器都是死循环_isrunning=true;while(true){char buffer[1024];struct sockaddr_in peer;socklen_t len=sizeof(peer);//1.我们要让server先收数据ssize_t n=recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);if(n>0){buffer[n]=0;InetAddr addr(peer);LOG(DEBUG,"get message from [%s:%d]: %s\n",addr.Ip().c_str(),addr.Port(),buffer);//2.我们要将server收到的数据,发回给对方sendto(_sockfd,buffer,strlen(buffer),0,(struct sockaddr*)&peer,len);}}_isrunning=false;
}

大概思路就是,我们需要先通过recvfrom函数接收客户端发来的消息,然后再通过sendto函数发回给客户端。

我们发现这个函数里面出现了一个新的东西,即InetAddr,这其实是一个结构体,我们来看看吧!

#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>using namespace std;class InetAddr
{
private:void GetAddress(string* ip,uint16_t* port){*port=ntohs(_addr.sin_port);//网络字节序转为主机字节序*ip=inet_ntoa(_addr.sin_addr);//将网络字节序IP转为点分式十进制IP}
public:InetAddr(const struct sockaddr_in &addr):_addr(addr){GetAddress(&_ip,&_port);}string Ip(){return _ip;}uint16_t Port(){return _port;}~InetAddr(){}
private:struct sockaddr_in _addr;string _ip;uint16_t _port;
};

这个结构体重包含了sockaddr_in结构对象,以及IP地址和端口号。

那么服务端代码合起来就是:

#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <stdlib.h>
#include "Log.hpp"
#include"InetAddr.hpp"using namespace std;enum
{SOCKET_ERROR = 1,BIND_ERROR,USAGE_ERROR
};const static int defaultfd = -1;class UdpServer
{
public:UdpServer(uint16_t port): _sockfd(defaultfd), _port(port),_isrunning(false){}void InitServer(){// 1.创建udp socket 套接字...必须要做的_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(FATAL, "socket error,%s,%d\n", strerror(errno), errno);exit(SOCKET_ERROR);}LOG(INFO, "socket create success,sockfd: %d\n", _sockfd);// 2.1 填充sockaddr_in结构struct sockaddr_in local;     // struct sockaddr_in 系统提供的数据类型,local是变量,用户栈上开辟空间bzero(&local, sizeof(local)); // 清空local.sin_family = AF_INET;local.sin_port = htons(_port); // port要经过网络传输给对面,即port先到网络,所以要将_port,从主机序列转化为网络序列//a.字符串风格的点分十进制的IP地址转成4字节IP//b.将主机序列转成网络序列//in_addr_t inet_addr(const char* cp)->同时完成a&b//local.sin_addr.s_addr =inet_addr(_ip.c_str());local.sin_addr.s_addr=INADDR_ANY;//htonl(INADDR_ANY)// 2.2 bind sockfd和网络信息(IP(?)+Port)int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(n<0){LOG(FATAL, "bind error,%s,%d\n", strerror(errno), errno);exit(BIND_ERROR);}LOG(INFO, "socket bind success\n");}void Start(){//一直运行,直到管理者不想运行了,服务器都是死循环_isrunning=true;while(true){char buffer[1024];struct sockaddr_in peer;socklen_t len=sizeof(peer);//1.我们要让server先收数据ssize_t n=recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);if(n>0){buffer[n]=0;InetAddr addr(peer);LOG(DEBUG,"get message from [%s:%d]: %s\n",addr.Ip().c_str(),addr.Port(),buffer);//2.我们要将server收到的数据,发回给对方sendto(_sockfd,buffer,strlen(buffer),0,(struct sockaddr*)&peer,len);}}_isrunning=false;}~UdpServer(){}private:int _sockfd;uint16_t _port; // 服务器所用的端口号bool _isrunning;
};

四.   服务端调用实现

由于我们将服务端封装成了一个类,所以需要创建出对象,然后调用初识函数以及开始函数。

#include<iostream>
#include<memory>
#include"UdpServer.hpp"
#include"Log.hpp"
using namespace std;void Usage(string proc)
{cout<<"Usage:\n\t"<<proc<<" local_port\n"<<endl;
}// ./udpserver port
int main(int argc,char *argv[])
{if(argc!=2){Usage(argv[0]);exit(USAGE_ERROR);}EnableScreen();//string ip=argv[1];uint16_t port=stoi(argv[1]);unique_ptr<UdpServer> usvr=make_unique<UdpServer>(port);usvr->InitServer();usvr->Start();return 0;
}

思路就是,创建一个对象,依次调用两个成员函数,即可创建出服务端。

五.   客户端实现

客户端的创建前面一部分还是跟服务端一样,即创建套接字,构建目标主机结构体信息,即构建sockaddr_in结构体信息。唯一不同的是,客户端是不需要显示bind的。

为什么呢?

为了防止客户端的端口冲突。如果我们直接将客户端绑定了一个确定的端口号,其他人是不知道的,每个人都绑定一个确定的端口号,势必会有冲突。

那什么时候绑定呢?

客户端首次发送消息数据的时候,操作系统会给客户端随机分配端口号,以防端口冲突。

void Usage(string proc)
{cout<<"Usage:\n\t"<<proc<<" serverip serverport\n"<<endl;
}// ./udpclient serverip serverport
int main(int argc,char *argv[])
{if(argc!=3){Usage(argv[0]);exit(1);}string serverip=argv[1];uint16_t serverport=stoi(argv[2]);//1.创建socketint sockfd=socket(AF_INET,SOCK_DGRAM,0);if(sockfd<0){cerr<<"socket error"<<endl;}//2.client一定要bind,client也有自己的ip和port,但是不建议显示(和server一样用bind函数)bind//a.那如何bind呢?当udp client首次发送数据的时候,os会自动随机的给client进行bind--为什么?要bind,必然要和port关联!防止client port冲突//b.什么时候bind?首次发送数据的时候//构建目标主机的socket信息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());
}

下面就可以直接与服务端通信了。

string message;
//3.直接通信即可
while(true)
{cout<<"Please Enter# ";getline(cin,message);sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));struct sockaddr_in peer;socklen_t len=sizeof(peer);char buffer[1024];ssize_t n=recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);if(n>0){buffer[n]=0;cout<<"server echo# "<<buffer<<endl;}
}

通信时,首先通过sendto将要发送的信息发送到服务端,服务端收到后会将信息发送回来,然后再用recvfrom函数收到信息即可。

客户端整体代码如下:

#include<iostream>
#include<string>
#include<cstdio>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>using namespace std;void Usage(string proc)
{cout<<"Usage:\n\t"<<proc<<" serverip serverport\n"<<endl;
}// ./udpclient serverip serverport
int main(int argc,char *argv[])
{if(argc!=3){Usage(argv[0]);exit(1);}string serverip=argv[1];uint16_t serverport=stoi(argv[2]);//1.创建socketint sockfd=socket(AF_INET,SOCK_DGRAM,0);if(sockfd<0){cerr<<"socket error"<<endl;}//2.client一定要bind,client也有自己的ip和port,但是不建议显示(和server一样用bind函数)bind//a.那如何bind呢?当udp client首次发送数据的时候,os会自动随机的给client进行bind--为什么?要bind,必然要和port关联!防止client port冲突//b.什么时候bind?首次发送数据的时候//构建目标主机的socket信息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());string message;//3.直接通信即可while(true){cout<<"Please Enter# ";getline(cin,message);sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));struct sockaddr_in peer;socklen_t len=sizeof(peer);char buffer[1024];ssize_t n=recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);if(n>0){buffer[n]=0;cout<<"server echo# "<<buffer<<endl;}}return 0;
}

六.   效果展示

最后服务端和客户端通信的效果如下:

可以看到服务端在创建套接字成功,bind成功之后开始工作。

客户端发送消息,服务端收到消息,并返还给客户端。 


总结:

好了,到这里今天的知识就讲完了,大家有错误一点要在评论指出,我怕我一人搁这瞎bb,没人告诉我错误就寄了。

祝大家越来越好,不用关注我(疯狂暗示)

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

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

相关文章

2024最新盘点:主流的仓库管理软件有哪些?

本文将盘点10款主流的仓库管理软件&#xff0c;为企业选型仓库管理软件提供参考。 库存数据不准确、订单处理效率低下、仓库作业流程不规范&#xff1f;在数据管理与分析上遇到困难&#xff0c;企业发展和竞争力受阻&#xff1f;物流行业高速发展&#xff0c;而仓储管理却遇到重…

【Python入门】第1节 基础语法

&#x1f4d6;第1节 基础语法 ✅字面量✅注释✅变量✅数据类型&#x1f9ca;数据类型转换 ✅标识符✅运算符✅字符串扩展&#x1f9ca;字符串的三种定义方式&#x1f9ca;字符串拼接&#x1f9ca;字符串格式化&#x1f9ca;格式化的精度控制&#x1f9ca;字符串格式化方式2&…

Windows bat脚本学习六(十六进制与十进制互转)

一、十六进制转十进制 十六进制数转十进制数相对比较简单&#xff0c;可以直接通过0x来实现。 见如下代码&#xff1a; echo off chcp 65001set taaset /a hex0x%t% echo data%hex%pause 结果&#xff1a; 二、十进制转十六进制 这个转化比较麻烦&#xff0c;没有简便的方式转…

通过 GitHub Actions 执行数据库 Schema 变更工作流

原文地址 https://www.bytebase.com/docs/tutorials/github-ci/ 教程库&#xff1a;https://github.com/bytebase/github-action-example 开发者们喜欢将 Schema 变更脚本与应用程序代码一起保存在 Git 中&#xff0c;这样变更脚本就能像应用程序代码一样接受审核和版本控制&…

哪款直流电能表可在电信基站、直流充电桩、太阳能光伏用

安科瑞徐赟杰18706165067 在电信基站、直流充电桩、太阳能光伏等应用场合中&#xff0c;可使用DJSF1352-RN导轨式直流电能表&#xff0c;此表带有双路直流输入&#xff0c;该系列仪表可测量直流系统中的电压、电流、功率以及正反向电能等。可计量总电能&#xff0c;又可计量规…

ev录屏损坏修复

ev录屏应该不正常关闭&#xff0c;录屏损坏 淘宝买了一个软件&#xff0c;修复成功&#xff0c;需找一个当时时间段的正常录屏学习&#xff0c;然后高级修复。整体花费5毛钱

记录一下idea的一些使用技巧和遇到的异常(持续更新)

技巧 自己的模板——live template 有些代码在项目中通常会被用到或会被重复使用&#xff0c;可以自己写一个模板存起来&#xff0c;要用的时候用快捷键生成就可以了。 在这里选择生效范围 现在&#xff0c;就有我们自己的模板了&#xff0c;一回车就自动生成 idea的全局配置…

人工智能 | 结对编程助手GithubCopilot

简介 GitHub Copilot 是一款 AI 结对程序员&#xff0c;可帮助您更快、更少地编写代码。它从注释和代码中提取上下文&#xff0c;以立即建议单独的行和整个函数。GitHub Copilot 由 GitHub、OpenAI 和 Microsoft 开发的生成式 AI 模型提供支持。它可作为 Visual Studio Code、…

win系统安装mysql,使用mysqldump,pycharm使用mysqldump,避坑

文章目录 下载mysql的win客户端设置系统环境变量验证是否可用pycharm使用mysqldump异常问题排查 下载mysql的win客户端 官网下载地址如果下载旧版本&#xff0c;需自行到Archives里面找 本人使用的是mysql5.7&#xff0c;找到相应版本后&#xff0c;点击Download下载 设置系统…

[Java]SpringBoot登录认证流程详解

登录认证 登录接口 1.查看原型 2.查看接口 3.思路分析 登录核心就是根据用户名和密码查询用户信息,存在则登录成功, 不存在则登录失败 4.Controller Slf4j RestController public class LoginController {Autowiredprivate EmpService empService;/*** 登录的方法** param …

数仓基础(七):离线与实时数仓区别和建设思路

文章目录 离线与实时数仓区别和建设思路 一、离线数仓与实时数仓区别 二、实时数仓建设思路 离线与实时数仓区别和建设思路 一、离线数仓与实时数仓区别 离线数据与实时数仓区别如下&#xff1a; 对比方面 离线数仓 实时数仓 架构选择 传统大数据架构 Kappa架构 建设…

数据结构(Java)实现:栈和队列

文章目录 1. 栈的模拟实现1.1 普通栈的模拟实现1.2 泛型栈的模拟实现 2. 栈的介绍3. 栈的使用4. 栈的应用场景4.1 改变元素的序列4.2 将递归转换为循环4.3 使用栈解题 5. 栈的链表实现6. 队列概念7. 队列的使用8. 模拟队列的实现8.1 链式队列8.2 顺序队列 9. 双端队列 1. 栈的模…

Python编码系列—Python中的安全密码存储与验证:实战指南

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

livox MID-360调试(解决ip设置问题)

整体的调试思路参考大疆Livox Mid360 使用指南_mid360中的imu内参-CSDN博客这篇博客。 但是在调试过程中出现了ip地址设置不对导致的报错&#xff1a; 1.livox viewer中看不到点云数据 2.livox SDK2 bind error。 joeyjoey:~/slam/Livox-SDK2/build/samples/livox_lidar_qu…

基于PLC的电热水器的水箱水位控制系统(论文+源码)

1总体方案设计 本设计基于PLC的电热水器的水箱水位控制系统的整体结构如图2.1所示&#xff0c;系统采用S7-1200 PLC为控制器&#xff0c;可以实现电热水器水箱中的水位、水温检测&#xff0c;并且用户可以设定目标水位和水温&#xff0c;在自动模式下&#xff0c;当水位低于低水…

mysql 8.0 的 建表 和八种 建表引擎实例

文章目录 MySQL 8.0 中&#xff0c;主要有以下四种常见的建表引擎一、InnoDB 引擎建表注意点建表知识点 二、MyISAM 引擎建表使用场景 三、Memory 引擎使用场景 四、Archive 引擎五、BLACKHOLE 引擎一、特点二、适用场景三、注意事项 六、MRG_MyISAM 引擎MRG_MyISAM 和 MyISAM …

uniapp组件中的emit声明触发事件

emit解析 在 uniapp 中&#xff0c;emit 主要用于组件间通信&#xff0c;特别是在子组件需要向父组件或者其他组件发送消息的时候。具体用途包括&#xff1a; 子传父数据&#xff1a;子组件通过 $emit 触发一个事件&#xff0c;并携带参数&#xff0c;父组件监听这个事件并对参…

Oracle 网络安全产品安全认证检索

自2023年7月1日起&#xff0c;国家网信办、工业和信息化部、公安部、国家认证认可监督管理委员会统一公布和更新网络关键设备和网络安全专用产品清单。列入《网络关键设备和网络安全专用产品目录》的网络安全专用产品应当按照《信息安全技术网络安全专用产品安全技术要求》等相…

笔记:应用Visual Studio Profiler分析CPU使用情况

一、目的&#xff1a;应用Visual Studio Profiler分析CPU使用情况 使用 Visual Studio Profiler 分析 CPU 使用情况可以帮助你识别性能瓶颈&#xff0c;优化代码&#xff0c;提高应用程序的响应速度。 二、实现 以下是如何使用 Visual Studio Profiler 分析 CPU 使用情况的详…

保存json时,保存成自己喜欢的格式的方法(而不是直接保存成格式化的json文档)

保存json时&#xff0c;不是直接保存成格式化的json文档的格式的方法 前言&#xff0c;博主是如何把格式话的json格式保存成自己喜欢的json格式的保存成格式化的json文档的格式&#xff1a;带缩进格式全部保存成一行每条数据保存成一行&#xff1a; 保存成自己喜欢的格式碎碎念…