基于Socket简单的UDP网络程序
⭐小白苦学IT的博客主页
⭐初学者必看:Linux操作系统入门
⭐代码仓库:Linux代码仓库
❤关注我一起讨论和学习Linux系统
1.前言
网络编程前言
网络编程是连接数字世界的桥梁,它让计算机之间能够交流信息,为我们的生活和工作带来便利。从简单的网页浏览到复杂的分布式系统,网络编程无处不在。
然而,网络编程涉及诸多复杂概念和技术,如IP地址、端口号、Socket、TCP/UDP协议等,需要我们深入学习和掌握。同时,网络环境的复杂性、数据安全性等问题也带来了挑战。
但正是这些挑战,让网络编程充满了无限可能。掌握网络编程技术,我们可以开发出各种创新应用,为人们提供更高效、智能的服务。
本文旨在介绍网络编程的Socket编程接口及其技术,分享实用经验,帮助读者打下坚实的网络编程基础。
1.socket编程接口
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);
sockaddr结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要谈的UNIX DomainSocket. 然而, 各种网络协议的地址格式并不相同.

- 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 结构
sockaddr 是一个通用的套接字地址结构,它用于表示各种类型的套接字地址。但是,sockaddr 结构本身并不包含足够的信息来确定地址的类型,因此它通常被更具体的结构(如 sockaddr_in)所替代。sockaddr 结构的主要作用是为不同的地址结构提供一个统一的接口。

- 通用性:
sockaddr是一个通用的套接字地址结构,设计初衷是为了能够表示各种类型的套接字地址,包括IPv4、IPv6以及其他可能的地址类型。这种通用性使得sockaddr能够作为许多网络编程函数的参数,如bind、connect、recvfrom、sendto等,用于指明地址信息。- 扩展性:通过定义
sa_family字段,sockaddr能够区分不同类型的地址结构。这使得在未来引入新的地址类型时,不需要修改现有函数的接口,只需定义新的地址结构并设置相应的sa_family即可。
sockaddr_in 结构
sockaddr_in 是 sockaddr 结构的一个特例,用于表示 IPv4 地址和端口号。它包含了 IP 地址和端口号的信息,以及地址族和协议信息。

- IPv4特化:尽管
sockaddr具有通用性,但在实际编程中,特别是在处理IPv4地址时,直接使用sockaddr结构会显得过于复杂和冗余。sockaddr_in结构是针对IPv4地址设计的,它包含了IPv4地址和端口号等必要信息,并且以更直观和易于操作的方式呈现这些信息。- 便利性:
sockaddr_in提供了专门的字段来存储IPv4地址(sin_addr)和端口号(sin_port),这使得在处理IPv4网络编程任务时更加方便和高效。同时,通过类型转换,sockaddr_in结构可以很容易地转换为sockaddr结构,从而与需要sockaddr参数的函数兼容。
in_addr结构
in_addr 结构用于表示一个 IPv4 地址。它通常与 sockaddr_in 结构一起使用,作为 sin_addr 字段的类型。

在这个结构中,s_addr 是一个无符号长整数,表示 IPv4 地址。在实际使用中,我们通常不会直接操作这个长整数,而是使用诸如 inet_pton 和 inet_ntop 这样的函数来将点分十进制格式的 IP 地址(如 "192.168.1.1")转换为 in_addr 结构,或者将 in_addr 结构转换为点分十进制格式的字符串。
- IPv4地址表示:
in_addr结构专门用于表示IPv4地址。它通过一个无符号长整数(s_addr)来存储IPv4地址,这种表示方式在网络编程中非常常见。尽管IPv4地址通常以点分十进制的形式表示(如192.168.1.1),但在内部处理和网络传输时,它们通常被转换为这种整数形式。- 转换方便:
in_addr结构使得在点分十进制格式和内部整数格式之间转换IPv4地址变得相对简单。通过调用如inet_pton和inet_ntop这样的函数,可以轻松实现这两种格式之间的转换,从而方便网络编程中的地址处理。
总结一下就是:
sockaddr是一个通用的套接字地址结构,用于表示各种类型的地址。sockaddr_in是sockaddr的一个特例,用于表示 IPv4 地址和端口号。in_addr用于表示 IPv4 地址。
这三种结构的存在是为了满足不同网络编程需求和提高编程效率。sockaddr提供了通用性和扩展性,sockaddr_in则针对IPv4地址提供了更直观和便利的操作方式,而in_addr则专门用于表示和转换IPv4地址。在实际编程中,根据具体需求选择合适的结构进行处理,可以提高代码的可读性和可维护性。
2.简单UDP的echo服务器(代码实现)
封装 UdpSocket
UdpServer.hpp
默认ip用 0.0.0.0
端口:8080
对udp服务器进行封装:
#pragma once
#include "Log.hpp"
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<errno.h>
#include<functional>using func_t = std::function<std::string (const std::string&,uint16_t &,const std::string&)>;uint16_t defaultport = 8080;
std::string defaultip = "0.0.0.0";
const int size = 1024;enum
{SOCKET_ERR = 1,BIND_ERR};class UdpServer
{
public:UdpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip):_sockfd(-1), _port(port), _ip(ip),_isrunning(false){}void Init(){//1.创建udp socket_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){log.LogMessage(FATAL, "socket create error,_sockfd: %d", _sockfd);exit(SOCKET_ERR);}log.LogMessage(INFO, "socket create success, _sockfd: %d ", _sockfd);//2.bind socketstruct sockaddr_in local;bzero(&local,sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);//需要保证我的端口号是网络字节序列,因为该端口号是要给对方发送的。local.sin_addr.s_addr = inet_addr(_ip.c_str()); //1.string->uint32_t 2.uint32_t 必须是网络序列的//local.sin_addr.s_addr = htonl(INADDR_ANY);if(bind(_sockfd,(const struct sockaddr *)&local,sizeof(local))<0){log.LogMessage(FATAL,"bind error , error: %d, error string : %s",errno,strerror(errno));exit(BIND_ERR);}log.LogMessage(INFO,"bind success , error: %d, error string : %s",errno,strerror(errno));}void Run(func_t func){_isrunning = true;char inbuffer[size];while(_isrunning){struct sockaddr_in client;socklen_t len = sizeof(client);std::cout<<"server is run!!!"<<std::endl;ssize_t n = recvfrom(_sockfd,inbuffer,sizeof(inbuffer) - 1,0,(struct sockaddr *)&client,&len);if(n<0){log.LogMessage(WARNING,"recvfrom error, errno: %d ,errno string : %s",errno,strerror(errno));continue;}uint16_t clientport = ntohs(client.sin_port);std::string clientip = inet_ntoa(client.sin_addr);inbuffer[n] = 0;//充当了一次数据的处理std::string info = inbuffer;std::string echo_string = func(info,clientport,clientip);sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&client,len);}}~UdpServer() {if(_sockfd>0) close(_sockfd);}private:int _sockfd;//网络文件描述符std::string _ip;//字符串类型ip地址uint16_t _port;//服务器进程的端口号 bool _isrunning;
};
Main.cc
#include"UdpServer.hpp"
#include<memory>
#include<iostream>
#include<cstdio>
#include<vector>void Usage(std::string proc)
{std::cout<<"\n\rUsage: "<<proc<<" port[1024+]\n"<<std::endl;
}std::string Handler(const std::string& str,uint16_t & clientport,const std::string& clientip)
{std::cout<<"[ ip: "<< clientip<<" port: "<<clientport<<" ]# ";std::string res = "server get a message: ";res+=str;std::cout<<res<<std::endl;return res;
}bool SafeCheck(const std::string & cmd)
{std::vector<std::string> key_word = {"rm","mv","cp","kill","sudo","unlink","uninstall","yum","top"};for(auto &word:key_word){auto pos = cmd.find(word);if(pos!=std::string::npos) return false;}return true;
}std::string ExcuteCommand(const std::string & cmd)
{if(!SafeCheck(cmd)) return "bad man";FILE* fp = popen(cmd.c_str(),"r");if(nullptr == fp){perror("popen error");return "error";}std::string result;char buffer[4096];while(true){char * getc = fgets(buffer,sizeof(buffer),fp);if(nullptr == getc){break;}result+=buffer;}pclose(fp);return result;}int main(int argc,char* argv[])
{if(argc!=2){Usage(argv[0]);exit(1);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<UdpServer> svr(new UdpServer(port));svr->Init();svr->Run(Handler); return 0;
}
UdpClient.cc(客户端代码)
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace std;void Usage(std::string proc)
{std::cout << "\n\rUsage: " << proc << " serverip serverport\n"<< std::endl;
}// ./udpclient serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(0);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);struct sockaddr_in server;bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport); //?server.sin_addr.s_addr = inet_addr(serverip.c_str());socklen_t len = sizeof(server);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){cout << "socker error" << endl;return 1;}// client 要bind吗?要!只不过不需要用户显示的bind!一般有OS自由随机选择!// 一个端口号只能被一个进程bind,对server是如此,对于client,也是如此!// 其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以!// 系统什么时候给我bind呢?首次发送数据的时候string message;char buffer[1024];while (true){cout << "Please Enter@ ";getline(cin, message);cout<<message<<endl;// std::cout << message << std::endl;// 1. 数据 2. 给谁发sendto(sockfd, message.c_str(), message.size(), 0, (const sockaddr *)&server, len);struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);if(s > 0) {buffer[s] = 0;cout << buffer << endl;}}close(sockfd);return 0;
}
日志类:
#pragma once#include <iostream>
#include <cstdarg>
#include <ctime>
#include <string>
#include <unistd.h>
#include <fstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>enum
{DEBUG = 0,INFO,WARNING,ERROR,FATAL
};enum
{Screen = 10,Onefile,Classfile
};std::string LevelToString(int level)
{switch (level){case DEBUG:return "Debug";case INFO:return "Info";case WARNING:return "Warning";case ERROR:return "Error";case FATAL:return "Fatal";default:return "Unknown";}
}const int defaultstyle = Screen;
const std::string default_filename = "log.";
const std::string logdir="log";class Log
{
public:Log():style(defaultstyle),filename(default_filename){mkdir(logdir.c_str(),0775);}void Enable(int sty){style = sty;}std::string TimestampToLocalTime(){time_t curr = time(nullptr);struct tm *currtime = localtime(&curr);char time_buffer[128];snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",currtime->tm_year + 1900, currtime->tm_mon, currtime->tm_mday, currtime->tm_hour,currtime->tm_min, currtime->tm_sec);return time_buffer;}void WriteLog(const std::string &levelstr, const std::string &message){switch (style){case Screen:std::cout << message<<std::endl;break;case Onefile:WriteLogToOnefile("all", message);break;case Classfile:WriteLogToClassfile(levelstr, message);break;default:break;}}void WriteLogToOnefile(const std::string &logname, const std::string &message){umask(0);int fd = open(logname.c_str(),O_CREAT | O_WRONLY | O_APPEND,0666);if(fd<0)return;write(fd,message.c_str(),message.size());close(fd);// std::ofstream out(logname);// if (!out.is_open())// return;// out.write(message.c_str(), message.size());// out.close();}void WriteLogToClassfile(const std::string &levelstr, const std::string &message){std::string logname = logdir;logname+="/";logname+=filename;logname += levelstr;WriteLogToOnefile(logname, message);}void LogMessage(int level, const char *format, ...) // 类c的日志接口{char rightbuffer[1024];va_list args;va_start(args, format);vsnprintf(rightbuffer, sizeof(rightbuffer), format, args);va_end(args);char leftbuffer[1024];std::string curtime = TimestampToLocalTime();std::string levelstr = LevelToString(level);std::string idstr = std::to_string(getpid());snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%s][%s]",levelstr.c_str(), curtime.c_str(), idstr.c_str());std::string logInfo = leftbuffer;logInfo += rightbuffer;WriteLog(levelstr, logInfo);}~Log() {}private:int style;std::string filename;
};Log log;class Conf
{
public:Conf(){log.Enable(Screen);}~Conf(){}
};Conf conf;
Makefile
.PHONY:all
all:udpserver udpclientudpserver:Main.ccg++ -o $@ $^ -g -std=c++11
udpclient:UdpClient.ccg++ -o $@ $^ -g -std=c++11.PHONY:clean
clean:rm -rf udpserver udpclient
运行结果:

实现了客户端,服务端双方交互,当然我们这只是简单的进行数据处理,其实还可以通过实现其他功能,这里可以发挥自己的想象去写。
地址转换函数
这里只介绍基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP 地址,但是我们通常用点分十进制的字符串表示IP 地址,以下函数可以在字符串表示 和in_addr表示之间转换;
字符串转in_addr的函数:

in_addr转字符串的函数:

其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void*addrptr。
关于inet_ntoa
inet_ntoa这个函数返回了一个char*, 很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果. 那么是否需要调用者手动释放呢?

man手册上说, inet_ntoa函数, 是把这个返回结果放到了静态存储区. 这个时候不需要我们手动进行释放.
那么问题来了, 如果我们调用多次这个函数, 会有什么样的效果呢? 参见如下代码:
#include<stdio.h>
#include<netinet/in.h>
#include<arpa/inet.h>int main()
{struct sockaddr_in addr1;struct sockaddr_in addr2;addr1.sin_addr.s_addr=0;addr2.sin_addr.s_addr=0xffffffff;char* ptr1 = inet_ntoa(addr1.sin_addr);char* ptr2 = inet_ntoa(addr2.sin_addr);printf("ptr1: %s,ptr2: %s\n",ptr1,ptr2);return 0;
}
运行结果:

因为inet_ntoa把结果放到自己内部的一个静态存储区, 这样第二次调用时的结果会覆盖掉上一次的结果
- 如果有多个线程调用 inet_ntoa, 是否会出现异常情况呢?
- 在APUE中, 明确提出inet_ntoa不是线程安全的函数;
- 但是在centos7上测试, 并没有出现问题, 可能内部的实现加了互斥锁;
- 在多线程环境下, 推荐使用inet_ntop, 这个函数由调用者提供一个缓冲区保存结果, 可以规避线程安全问题;
如果测试如下代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
void *Func1(void *p)
{struct sockaddr_in *addr = (struct sockaddr_in *)p;while (1){char *ptr = inet_ntoa(addr->sin_addr);printf("addr1: %s\n", ptr);}return NULL;
}
void *Func2(void *p)
{struct sockaddr_in *addr = (struct sockaddr_in *)p;while (1){char *ptr = inet_ntoa(addr->sin_addr);printf("addr2: %s\n", ptr);}return NULL;
}
int main()
{pthread_t tid1 = 0;struct sockaddr_in addr1;struct sockaddr_in addr2;addr1.sin_addr.s_addr = 0;addr2.sin_addr.s_addr = 0xffffffff;pthread_create(&tid1, NULL, Func1, &addr1);pthread_t tid2 = 0;pthread_create(&tid2, NULL, Func2, &addr2);pthread_join(tid1, NULL);pthread_join(tid2, NULL);return 0;
}
运行结果:

这段代码试图创建两个线程,
Func1和Func2,它们分别无限循环地打印两个sockaddr_in结构的 IP 地址。这两个sockaddr_in结构,addr1和addr2,被初始化为具有特定的sin_addr.s_addr值。
addr1.sin_addr.s_addr被初始化为0,这在 IPv4 地址中通常表示一个未指定的地址,或者说是无效的地址。
addr2.sin_addr.s_addr被初始化为0xffffffff,这在 IPv4 地址中通常表示广播地址。然而,代码中有一些需要注意的地方:
- inet_ntoa的静态缓冲区:
inet_ntoa函数使用静态缓冲区来存储转换后的字符串。这意味着如果两个线程同时调用inet_ntoa,它们可能会覆盖彼此的缓冲区,导致不可预测的结果。因此,在多线程环境中使用inet_ntoa是不安全的。- 无限循环:两个线程都包含一个无限循环,这会导致程序永远不会退出,除非被外部因素(如用户终止)中断。
- pthread_join:虽然代码中包含了
pthread_join调用,但由于线程中的无限循环,这些调用实际上永远不会返回,因此main函数也永远不会结束。测试这段代码时,你会看到两个线程分别不停地打印出相同的 IP 地址字符串,但由于
inet_ntoa的问题,这些字符串可能会被互相覆盖,导致输出变得混乱。此外,具体的输出取决于操作系统的具体实现和线程调度的行为。在某些情况下,你可能会看到
addr1和addr2交替出现,而在其他情况下,你可能会看到某个地址连续出现多次,然后被另一个地址覆盖。总的来说,这段代码并不是一个好的示例,因为它在多线程环境中不正确地使用了
inet_ntoa,并且包含了无限循环,这会导致程序行为不可预测且难以管理。如果你需要在多线程环境中处理 IP 地址,建议使用更安全的函数,如
inet_ntop,并确保正确管理线程的生命周期和同步。
相关文章:
基于Socket简单的UDP网络程序
⭐小白苦学IT的博客主页 ⭐初学者必看:Linux操作系统入门 ⭐代码仓库:Linux代码仓库 ❤关注我一起讨论和学习Linux系统 1.前言 网络编程前言 网络编程是连接数字世界的桥梁,它让计算机之间能够交流信息,为我们的生活和工作带来便利…...
计算机思维
计算机思维是一种运用计算机科学的基础概念和方法来解决问题、设计系统和理解人类行为的思维方式。它包括以下几个方面: 1. 抽象和建模:将复杂的现实问题抽象为计算机可以处理的模型,通过定义对象、属性和关系来构建问题的逻辑结构。 2. 算法…...
如何判断一个linux机器是物理机还是虚拟机
https://blog.csdn.net/qq_32262243/article/details/132571117 第一种方式:dmesg命令 [rootnshqae01adm03 ~]# dmesg | grep -i hypervisor [ 0.000000] Hypervisor detected: Xen PV [ 1.115297] VPMU disabled by hypervisor. 在我的机器上 dmesg也是能够用来判…...
python用requests的post提交data数据以及json和字典的转换
环境:python3.8.10 python使用requests的post提交数据的时候,代码写法跟抓包的headers里面的Content-Type有关系。 (一)记录Content-Type: application/x-www-form-urlencoded的写法。 import requestsurlhttps://xxx.comheade…...
【Datax分库分表导数解决方法】MySQL_to_Hive
Datax-MySQL_to_Hive-分库分表-数据同步工具 简介: 本文档介绍了一个基于Python编写的工具,用于实现分库分表数据同步的功能。该工具利用了DataX作为数据同步的引擎,并通过Python动态生成配置文件,并调用DataX来执行数据同步任务…...
Vue2 —— 学习(一)
目录 一、了解 Vue (一)介绍 (二)Vue 特点 (三)Vue 网站 1.学习: 2.生态系统: 3.团队 二、搭建 Vue 开发环境 (一)安装与引入 Vue 1.直接引入 2.N…...
Windows Server 2008添加Web服务器(IIS)、WebDAV服务、网络负载均衡
一、Windows Server 2008添加Web服务器(IIS) (1)添加角色,搭建web服务器(IIS) (2)添加网站,关闭默认网页,添加默认文档 在客户端浏览器输入服务器…...
SpringMVC转发和重定向
转发和重定向 1. View Resolver Spring MVC 中的视图解析器(View Resolver)负责解析视图。可以通过在配置文件中定义一个 View Resolver 来配置视图解析器: 配置文件版:spring-web.xml <!-- for jsp --> <bean class&q…...
勒索病毒最新变种.rmallox勒索病毒来袭,如何恢复受感染的数据?
导言: 随着信息技术的飞速发展,网络安全问题日益突出,其中勒索病毒便是近年来备受关注的网络安全威胁之一。在众多勒索病毒中,.rmallox勒索病毒以其独特的传播方式和强大的加密能力,给广大用户带来了极大的困扰。本文…...
复试专业课问题
1、数据结构:详细描述归并排序的过程 归并排序是用分治思想,分治模式在每一层递归上有三个步骤: 分解(Divide):将n个元素分成个含n/2个元素的子序列。解决(Conquer):用…...
比特币革命:刚刚开始
作者:Marius Farashi Tasooji 编译:秦晋 要充分理解比特币及其含义,首先必须理解什么是价值,什么是货币。以及是什么赋予资产价值? 这个问题看似愚蠢,但实际上非常有趣。我们的生活是由我们消费或出售的物品…...
淘宝店商家电话提取软件操作经验
淘宝爬虫工具是一种用于自动化获取淘宝网站数据的程序。以下是一个简单的淘宝爬虫工具的代码示例: import requests from bs4 import BeautifulSoupdef get_taobao_data(keyword):url fhttps://s.taobao.com/search?q{keyword}headers {User-Agent: Mozilla/5.0…...
【进阶六】Python实现SDVRPTW常见求解算法——遗传算法(GA)
基于python语言,采用经典蚁群算法(ACO)对 带硬时间窗的需求拆分车辆路径规划问题(SDVRPTW) 进行求解。 目录 往期优质资源1. 适用场景2. 代码调整2.1 需求拆分2.2 需求拆分后的服务时长取值问题 3. 求解结果4. 代码片段…...
【Android】App通信基础架构相关类源码解析
应用通信基础架构相关类源码解析 这里主要对Android App开发时,常用到的一些通信基础类进行一下源码的简单分析,包括: Handler:处理器,与某个Looper(一个线程对应一个Looper)进行关联。用于接…...
06-kafka配置
生产者配置 NAMEDESCRIPTIONTYPEDEFAULTVALID VALUESIMPORTANCEbootstrap.servershost/port列表,用于初始化建立和Kafka集群的连接。列表格式为host1:port1,host2:port2,…,无需添加所有的集群地址,kafka会根据提供的地址发现其他的地址&…...
Git、TortoiseGit、SVN、TortoiseSVN 的关系和区别
Git、TortoiseGit、SVN、TortoiseSVN 的关系和区别 (一)Git(分布式版本控制系统):(二)SVN(集中式版本控制系统)(三)TortoiseGit一、下载安装 git二、安装过程…...
4月5日排序算法总结(1)
冒泡排序 利用每趟都确定出一个最大值或者最小值 如果需要排一个从小到大的数组,那么我们每一趟都要确定一个最大值放在最后,一共有n个数,我们最多需要排列n-1趟就可以了,我们可以改进自己的代码,利用一个flag标记&a…...
Pandas追加写入文件的时候写入到了第一行
# 原代码 def find_money(file_path, account, b_account, money, type_word, time):file pd.read_excel(file_path)with open(money.csv, a, newline, encodingutf-8) as f:for i in file.index:省略中间的代码if 省略中间的代码:file.loc[[i]].to_csv(f,indexFalse)find_sam…...
04---webpack编写可维护的构建配置
01 构建配置抽离成npm包; 意义:通用性: 业务开发者无需关注构建配置 统一团队构建脚本可维护性:构建配置合理的拆分 质量:冒烟测试 单元测试 持续集成构建配置管理的可选方案:1 通过多个配置文件管理不同…...
【云计算】云数据中心网络(一):VPC
云数据中心网络(一):VPC 1.什么是 VPC2.VPC 的组成2.1 虚拟交换机2.2 虚拟路由器 3.VPC 网络规划3.1 VPC 数量规划3.2 交换机数量规划3.3 地址空间规划3.4 不同规模企业地址空间规划实践 4.VPC 网络高可靠设计4.1 单地域单可用区部署4.2 单地…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...
Yolov8 目标检测蒸馏学习记录
yolov8系列模型蒸馏基本流程,代码下载:这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中,**知识蒸馏(Knowledge Distillation)**被广泛应用,作为提升模型…...
