基于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能够作为许多网络编程函数的参数,如bindconnectrecvfromsendto等,用于指明地址信息。
  • 扩展性:通过定义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_ptoninet_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 地址中通常表示广播地址。

然而,代码中有一些需要注意的地方:

  1. inet_ntoa的静态缓冲区inet_ntoa 函数使用静态缓冲区来存储转换后的字符串。这意味着如果两个线程同时调用 inet_ntoa,它们可能会覆盖彼此的缓冲区,导致不可预测的结果。因此,在多线程环境中使用 inet_ntoa 是不安全的。
  2. 无限循环:两个线程都包含一个无限循环,这会导致程序永远不会退出,除非被外部因素(如用户终止)中断。
  3. pthread_join:虽然代码中包含了 pthread_join 调用,但由于线程中的无限循环,这些调用实际上永远不会返回,因此 main 函数也永远不会结束。

测试这段代码时,你会看到两个线程分别不停地打印出相同的 IP 地址字符串,但由于 inet_ntoa 的问题,这些字符串可能会被互相覆盖,导致输出变得混乱。

此外,具体的输出取决于操作系统的具体实现和线程调度的行为。在某些情况下,你可能会看到 addr1 和 addr2 交替出现,而在其他情况下,你可能会看到某个地址连续出现多次,然后被另一个地址覆盖。

总的来说,这段代码并不是一个好的示例,因为它在多线程环境中不正确地使用了 inet_ntoa,并且包含了无限循环,这会导致程序行为不可预测且难以管理。

如果你需要在多线程环境中处理 IP 地址,建议使用更安全的函数,如 inet_ntop,并确保正确管理线程的生命周期和同步。

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

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

相关文章

机器学习笔记 - 深度学习遇到超大图像怎么办?使用 xT 对极大图像进行建模论文简读

作为计算机视觉研究人员,在处理大图像时,避免不了受到硬件的限制,毕竟大图像已经不再罕见,手机的相机和绕地球运行的卫星上的相机可以拍摄如此超大的照片,遇到超大图像的时候,我们当前最好的模型和硬件都会达到极限。 所以通常我们在处理大图像时会做出两个次优选择之一:…

lottery-攻防世界

题目 flag在这里要用钱买&#xff0c;这是个赌博网站。注册个账号&#xff0c;然后输入七位数字&#xff0c;中奖会得到相应奖励。 githacker获取网站源码 &#xff0c;但是找到了flag文件但是没用。 bp 抓包发现api.php&#xff0c;并且出现我们的输入数字。 根据题目给的附…

推荐一款很强大的SCADA工业组态软件

可以广泛应用于化工、石化、制药、冶金、建材、市政、环保、电力等几十个行业。 I官网网站:www.hcy-soft.com |体验地址:http://www.byzt.net:60/sm/ 一、产品简介 BY组态是完全自主研发的集实时数据展示、动态交互等一体的全功能可视化平台。帮助物联网、工业互联网、电力能…

怎么防止文件被拷贝,复制别人拷贝电脑文件

怎么防止文件被拷贝&#xff0c;复制别人拷贝电,脑文件 防止文件被拷贝通常是为了保护敏感数据、知识产权或商业秘密不被未经授权的人员获取或传播。以下列出了一系列技术手段和策略&#xff0c;可以帮助您有效地防止文件被拷贝。 1. 终端管理软件&#xff1a; 如安企神、域智…

Node.js创建第一个web服务

如果用PHP来编写后端代码&#xff0c;需要用Apache或者Nginx的服务器,来处理客户的请求响应。对于Node.js时&#xff0c;不仅实现了应用&#xff0c;同时还实现了整个HTTP服务器. 安装 Node Snippets插件&#xff08;编程自带提示&#xff09; console.log(你好nodejs); //表…

MySQL高级篇(B-Tree、Btree)

目录 1、Btree&#xff08;B-Tree&#xff09; 1.1、B-Trees的特点 二叉树缺点&#xff1a;顺序插入时&#xff0c;会形成一个链表&#xff0c;查询性能大大降低。大数据量情况下&#xff0c;层级较深&#xff0c;检索速度慢。红黑树&#xff1a;大数据量情况下&#xff0c;层…

虚拟主机WordPress网站安装教程

一般的企业官网&#xff0c;简站WordPress小编都推荐使用虚拟主机&#xff0c;用虚拟主机搭建一般的WordPress企业官网足够用了。最主要的好处是使用虚拟主机可以省去了主机维护的成本。 下面是以简站WordPress主题在虚拟主机搭建企业官网为例子&#xff0c;写的一个教程&…

多叉树题目:子树中标签相同的结点数

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;子树中标签相同的结点数 出处&#xff1a;1519. 子树中标签相同的结点数 难度 5 级 题目描述 要求 给你一个树&#xff08;即一个连通的无向无环图…

JDK下载及安装说明

1&#xff0e;JDK下载 访问oracle官网&#xff1a;http://www.oracle.com 在首页点击Downloads&#xff0c;进入oracle软件下载页。 在下载页面&#xff0c;点击Java。 选择Java (JDK) for Developers&#xff0c;点击。 在 Java SE Downloads 页面&#xff0c;点击中间的DO…

app上架-您的应用存在最近任务列表隐藏风险活动的行为,不符合华为应用市场审核标准。

上架提示 您的应用存在最近任务列表隐藏风险活动的行为&#xff0c;不符合华为应用市场审核标准。 修改建议&#xff1a;请参考测试结果进行修改。 请参考《审核指南》第2.19相关审核要求&#xff1a;https://developer.huawei.com/consumer/cn/doc/app/50104-02 造成原因 …

后端说处理了跨域但没有生效

场景&#xff1a; 常见的跨域报错&#xff0c;一般都是由后端进行setHeader/*什么的。但是现在这种情况就是后端说他们做了处理。但是我这边请求还是报错。 withCredentials: with-credentials用来设置是否发送cookie&#xff0c;如果为true就会在跨域请求时候携带cookie&…

libVLC 提取视频帧

在前面的文章中&#xff0c;我们使用libvlc_media_player_set_hwnd设置了视频的显示的窗口。 libvlc_media_player_set_hwnd(vlc_mediaPlayer, (void *)ui.widgetShow->winId()); 如果我们想要提取每一帧数据&#xff0c;将数据保存到本地&#xff0c;该如何操作呢&#x…

Golang 开发实战day09 - package Scope

&#x1f3c6;个人专栏 &#x1f93a; leetcode &#x1f9d7; Leetcode Prime &#x1f3c7; Golang20天教程 &#x1f6b4;‍♂️ Java问题收集园地 &#x1f334; 成长感悟 欢迎大家观看&#xff0c;不执着于追求顶峰&#xff0c;只享受探索过程 Golang 教程09 - package Sc…

算法练习第12天|● 239. 滑动窗口最大值● 347.前 K 个高频元素

239.滑动窗口的最大值 力扣原题 题目描述&#xff1a; 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例 1&#xff…

影响力营销与AI的结合:Kompas.ai在搭桥角色中的独特价值

在数字化营销的新时代&#xff0c;影响力营销已经成为品牌建立信任和提升市场影响力的有效手段。通过与关键意见领袖&#xff08;KOL&#xff09;的合作&#xff0c;品牌能够利用KOL的信誉和影响力来扩大其市场覆盖范围和提升品牌认知度。然而&#xff0c;寻找与品牌价值观相契…

Linux 性能分析工具大全

vmstat--虚拟内存统计 vmstat&#xff08;VirtualMeomoryStatistics&#xff0c;虚拟内存统计&#xff09;是 Linux 中监控内存的常用工具,可对操作系统的虚拟内存、进程、CPU 等的整体情况进行监视。vmstat 的常规用法&#xff1a;vmstat interval times 即每隔 interval 秒采…

rsync+inotify实时同步 和 GFS分布式文件系统概述

目录 一、rsyncinotify实时同步 1.1.实时同步的优点 1.2.Linux内核的inotify机制 1.3.发起端配置rsyncInotify 1.4.配置远程登陆 1.4.1.修改rsync源服务器配置192.168.190.101 ​编辑 1.4.2.配置server 192.168.190.102 二、GFS 2.1.GlusterFS简介 2.2.GlusterFS特点…

Flutter学习11 - Future 与 FutureBuilder

1、Future 可以利用 Future 实现异步调用 1.1、Future 的两种形式 自定义一个结果类 class Response {String _data;Response(this._data); }自定义方法实现 Future Future<Response> testFuture() {var random Random();int randomNumber random.nextInt(10);if …

DIY自己的AI

一、开源AI大语言模型 目前开源的AI大语言模型(LLM)已经非常的多了&#xff0c;以下是收集的一些LLM&#xff1a; LLaMA LLaMA&#xff08;Large Language Model Meta AI&#xff09;&#xff1a;LLaMA是由MetaAI的Facebook人工智能实验室&#xff08;FAIR&#xff09;发布的…

大鼠尾静脉注射仪和小鼠尾静脉注射仪的区别

ZL-02B大鼠可视尾静脉注射仪是用于大鼠尾注射的一款仪器&#xff0c;以往给大鼠注射都是靠盲打&#xff0c;靠经验&#xff0c;对科研新手来说极其困难&#xff0c;有了大鼠尾静脉注射仪&#xff0c;可以大大提高注射效率&#xff0c;该仪器可以显示出尾部血管位置&#xff0c;…