【网络】:网络编程套接字

目录

源IP地址和目的IP地址

源MAC地址和目的MAC地址 

源端口号和目的端口号

端口号 VS 进程ID

TCP协议和UDP协议

网络字节序

字符串IP和整数IP相互转换

查看当前网络的状态 

socket编程接口

socket常见API

创建套接字

 绑定端口号

发送数据

接收数据 

sockaddr结构


源IP地址和目的IP地址

一、定义

  • 源 IP 地址(Source IP Address):它是发送数据的设备(如计算机、服务器等)在网络中的唯一标识。就像是发送信件时的寄件人地址,在网络通信中,它用于标识数据的来源。
  • 目的 IP 地址(Destination IP Address):这是接收数据的设备在网络中的唯一标识,类似于收件人地址。它告诉网络基础设施,数据最终要传送到哪里。

二、在网络通信中的作用

  • 数据传输导向:在互联网这样庞大的网络环境中,数据通过多个网络节点(如路由器、交换机等)进行传输。
    • 源 IP 地址和目的 IP 地址就像指南针一样,引导数据包从源设备通过复杂的网络路径最终到达目的设备。
    • 例如,当你在浏览器中访问一个网站时,你的计算机(源设备)的源 IP 地址和网站服务器(目的设备)的目的 IP 地址就确定了数据传输的起始点和终点。
  • 路由决策依据:路由器是网络中的关键设备,用于将数据包从一个网络转发到另一个网络。
    • 路由器根据数据包的目的 IP 地址来决定将其转发到哪个下一个网络节点。
    • 它会查询路由表,路由表中记录了目的 IP 地址的网络范围以及对应的转发接口。
    • 例如,如果一个数据包的目的 IP 地址属于某个特定的外部网络,路由器就会将其从连接外部网络的接口转发出去。

三、举例说明

  1. 假设你在自己的家中通过 Wi - Fi 连接的笔记本电脑访问一家在线购物网站。
  2. 你的笔记本电脑(假设其 IP 地址是 192.168.1.10)就是源设备,192.168.1.10 是源 IP 地址。
  3. 在线购物网站的服务器(假设其 IP 地址是 104.27.189.99)是目的设备,104.27.189.99 是目的 IP 地址。
  4. 当你在浏览器中输入网站的网址并发送请求时,数据从你的笔记本电脑(源)出发。
  5. 这个请求数据包包含了源 IP 地址(192.168.1.10)和目的 IP 地址(104.27.189.99)。
  6. 数据包首先到达你家中的无线路由器,路由器根据目的 IP 地址判断这个数据包需要发送到互联网服务提供商(ISP)的网络,然后经过一系列的网络设备(其他路由器等)的转发,最终到达购物网站的服务器(目的)。
  7. 在整个过程中,源 IP 地址和目的 IP 地址始终保持不变,确保数据能够准确地发送和接收。

源MAC地址和目的MAC地址 

  • 源 MAC 地址(Source MAC Address):它是发送数据的网络设备(如计算机网卡、网络交换机端口等)在局域网(LAN)中的物理地址。MAC 地址也被称为硬件地址,是由网络设备制造商固化在设备中的一个 48 位二进制数,通常表示为 12 位的十六进制数,例如 “00 - 1B - 44 - 11 - 3A - 0C”。这个地址在全球范围内是唯一的,用于在局域网环境中标识数据的发送端。
  • 目的 MAC 地址(Destination MAC Address):它是接收数据的网络设备在局域网中的物理地址,同样是一个 48 位的二进制数,用于在局域网中标识数据的目标接收端。

有些数据的传输都是跨局域网的,数据在传输过程中会经过若干个路由器,最终才能到达对端主机。

源MAC地址和目的MAC地址是包含在链路层的报头当中的,而MAC地址实际只在当前局域网内有效,因此当数据跨网络到达另一个局域网时,其源MAC地址和目的MAC地址就需要发生变化,因此当数据达到路由器时,路由器会将该数据当中链路层的报头去掉,然后再重新封装一个报头,此时该数据的源MAC地址和目的MAC地址就发生了变化。

因此数据在传输的过程中是有两套地址:

  • 一套是源IP地址和目的IP地址,这两个地址在数据传输过程中基本是不会发生变化的(存在一些特殊情况,比如在数据传输过程中使用NET技术,其源IP地址会发生变化,但至少目的IP地址是不会变化的)。
  • 另一套就是源MAC地址和目的MAC地址,这两个地址是一直在发生变化的,因为在数据传输的过程中路由器不断在进行解包和重新封装。

源端口号和目的端口号

两台主机之间通信的目的不仅仅是为了将数据发送给对端主机,而是为了访问对端主机上的某个服务(某个进程)。比如我们在用百度搜索引擎进行搜索时,不仅仅是想将我们的请求发送给对端服务器,而是想访问对端服务器上部署的百度相关的搜索服务。

其实,这种在网络中通信的本质:就是进程间通信,只是在不同的机器上进程间通信 (跨网络的进程间通信),我们把这种通信称做 套接字通信(socket通信)

socket通信本质上就是两个进程之间在进行通信,只不过这里是跨网络的进程间通信。比如逛京东和刷抖音的动作,实际就是手机上的京东进程和抖音进程在和对端服务器主机上的京东服务进程和抖音服务进程之间在进行通信。

因此进程间通信的方式除了管道、消息队列、信号量、共享内存等方式外,还有套接字,只不过前者是不跨网络的,而后者是跨网络的。

那我们是如何去实现我们的 socket通信 的呢? 

  1. 先把数据能够到达对方的机器
  2. 再在机器中能找到指定的进程,同一台机器也可能会同时存在多个正在进行跨网络通信的进程,因此当数据到达对端主机后,必须要通过某种方法找到该主机上对应的服务进程 !

端口号(port)的作用实际就是标识一台主机上的一个进程:

  1. 端口号是传输层协议的内容
  2. 端口号是一个2字节16位的整数
  3. 端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理
  4. 一个端口号只能被一个进程占用

当数据在传输层进行封装时,就会添加上对应源端口号和目的端口号的信息。这时通过源IP地址+源端口号就能够在网络上唯一标识发送数据的进程,通过目的IP地址+目的端口号就能够在网络上唯一标识接收数据的进程,此时就实现了跨网络的进程间通信。

注意:

  • 因为端口号是属于某台主机的,所以端口号可以在两台不同的主机当中重复,但是在同一台主机上进行网络通信的进程的端口号不能重复。
  • 所以,一个进程可以绑定多个端口号,但是一个端口号不能被多个进程同时绑定。

端口号 VS 进程ID

端口号(port)和 进程ID(PID)的作用都是标识一台主机上唯一的一个进程,那在进行socket通信时为什么不直接用 进程ID 呢?

  • 进程ID(PID)是用来标识系统内所有进程的唯一性的,它是属于系统级的概念
  • 端口号(PORT)是用来标识需要对外进行网络数据请求的进程的唯一性的,它是属于网络的概念
  1. 一台机器上可能会有大量的进程,但并不是所有的进程都要进行网络通信,可能有很大一部分的进程是不需要进行网络通信的本地进程,此时PID虽然也可以标识这些网络进程的唯一性,但在该场景下就不太合适了
  2. 进程管理 和 网络 解耦,因为不同平台的 进程ID 的分配可能有所不同,可能会出现不兼容的问题

底层如何通过端口号(PORT)找到对应进程的?

 机器底层采用哈希的方式建立了端口号和进程PID或PCB之间的映射关系,当底层拿到端口号时就可以直接执行对应的哈希算法,然后就能够找到该端口号对应的进程。

TCP协议和UDP协议

网络协议栈是贯穿整个体系结构的,在应用层、操作系统层和驱动层各有一部分。当我们使用系统调用接口实现网络数据通信时,面对的协议层就是传输层,而传输层最典型的两种协议就是TCP协议和UDP协议

TCP协议

TCP协议叫做传输控制协议(Transmission Control Protocol),TCP协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。

TCP协议是面向连接的,如果两台主机之间想要进行数据传输,那么必须要先建立连接,当连接建立成功后才能进行数据传输。其次,TCP协议是保证可靠的协议,数据在传输过程中如果出现了丢包、乱序等情况,TCP协议都有对应的解决方法。

UDP协议

UDP协议叫做用户数据报协议(User Datagram Protocol),UDP协议是一种无需建立连接的、不可靠的、面向数据报的传输层通信协议。

使用UDP协议进行通信时无需建立连接,如果两台主机之间想要进行数据传输,那么直接将数据发送给对端主机就行了,但这也就意味着UDP协议是不可靠的,数据在传输过程中如果出现了丢包、乱序等情况,UDP协议本身是不知道的。

既然UDP协议是不可靠的,那为什么还要有UDP协议的存在?

TCP协议可靠是需要我们做更多的工作的,TCP协议虽然是一种可靠的传输协议,但这一定意味着TCP协议在底层需要做更多的工作,因此TCP协议底层的实现是比较复杂的,我们不能只看到TCP协议面向连接可靠这一个特点,我们也要能看到TCP协议对应的缺点。

UDP协议虽然是一种不可靠的传输协议,但这一定意味着UDP协议在底层不需要做过多的工作,因此UDP协议底层的实现一定比TCP协议要简单,UDP协议虽然不可靠,但是它能够快速的将数据发送给对方,虽然在数据在传输的过程中可能会出错。

编写网络通信代码时具体采用TCP协议还是UDP协议,完全取决于上层的应用场景。如果应用场景严格要求数据在传输过程中的可靠性,此时我们就必须采用TCP协议,如果应用场景允许数据在传输出现少量丢包,那么我们肯定优先选择UDP协议,因为UDP协议足够简单。

注意: 一些优秀的网站在设计网络通信算法时,会同时采用TCP协议和UDP协议,当网络流畅时就使用UDP协议进行数据传输,而当网速不好时就使用TCP协议进行数据传输,此时就可以动态的调整后台数据通信的算法。

网络字节序

计算机在存储数据时是有大小端的概念的:

  • 大端模式: 数据的高字节内容保存在内存的低地址处,数据的低字节内容保存在内存的高地址处。
  • 小端模式: 数据的高字节内容保存在内存的高地址处,数据的低字节内容保存在内存的低地址处。

如果编写的程序只在本地机器上运行,那么是不需要考虑大小端问题的,因为同一台机器上的数据采用的存储方式都是一样的

但是如果涉及网络通信,那就必须考虑大小端的问题,否则对端主机识别出来的数据可能与发送端想要发送的数据是相反的。

例如,现在两台主机之间在进行网络通信,其中发送端是小端机,而接收端是大端机。发送端将发送缓冲区中的数据按内存地址从低到高的顺序发出后,接收端从网络中获取数据依次保存在接收缓冲区时,也是按内存地址从低到高的顺序保存的,就会出现想要发送的数据是相反的。

因此TCP/IP协议规定,网络数据流采用大端字节序,即低地址高字节。无论是大端机还是小端机,都必须按照TCP/IP协议规定的网络字节序来发送和接收数据。

  1. 如果发送端是小端,需要先将数据转成大端,然后再发送到网络当中
  2. 如果发送端是大端,则可以直接进行发送
  3. 如果接收端是小端,需要先将接收到数据转成小端后再进行数据识别
  4. 如果接收端是大端,则可以直接进行数据识别

为什么网络字节序采用的是大端?而不是小端?

网络字节序采用的是大端,而主机字节序一般采用的是小端,那为什么网络字节序不采用小端呢?如果网络字节序采用小端的话,发送端和接收端在发生和接收数据时就不用进行大小端的转换了。

说法一: TCP在Unix时代就有了,以前Unix机器都是大端机,因此网络字节序也就采用的是大端,但之后人们发现用小端能简化硬件设计,所以现在主流的都是小端机,但协议已经不好改了。
说法二: 大端序更符合现代人的读写习惯。

网络字节序与主机字节序之间的转换

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,系统提供了四个函数,可以通过调用以下库函数实现网络字节序和主机字节序之间的转换。

#include <arpa/inet.h>

// 将一个 32 位的主机字节序整数转换为网络字节序
uint32_t htonl(uint32_t hostlong);

// 将一个 16 位的主机字节序整数转换为网络字节序
uint16_t htons(uint16_t hostshort);

// 将一个 32 位的网络字节序整数转换为主机字节序
uint32_t ntohl(uint32_t netlong);

// 将一个 16 位的网络字节序整数转换为主机字节序
uint16_t ntohs(uint16_t netshort);
  1. 函数名当中的h表示主机字节序(host),n表示网络字节序(network),l表示32位长整数,s表示16位短整数
  2. 例如htonl表示将32位长整数从主机字节序转换为网络字节序
  3. 如果主机是小端字节序,则这些函数将参数做相应的大小端转换然后返回
  4. 如果主机是大端字节序,则这些函数不做任何转换,将参数原封不动地返回

字符串IP和整数IP相互转换

IP地址的表现形式有两种:

  • 字符串IP:类似于192.168.1.1这种字符串形式的IP地址,叫做基于字符串的点分十进制IP地址
  • 整数IP:IP地址在进行网络传输时所用的形式,用一个32位的整数来表示IP地址
    • 192.168.1.1为例,192的二进制是11000000168的二进制是101010001的二进制是00000001,连接起来得到11000000101010000000000100000001,转换为十进制就是3232235777

字符串IP和整数IP的相互转换函数

// 字符串IP 转换成 整数IP
in_addr_t inet_addr(const char* cp);

// 整数IP转换成字符串IP
char* inet_ntoa(struct in_addr in);

查看当前网络的状态 

netstat 命令

netstat常用选项说明:

  • -n:直接使用IP地址,而不通过域名服务器。
  • -l:显示监控中的服务器的Socket。
  • -t:显示TCP传输协议的连线状况。
  • -u:显示UDP传输协议的连线状况。
  • -p:显示正在使用Socket的程序识别码和程序名称

netstat命令显示的信息中:

  1. Proto 表示协议的类型
  2. Recv-Q 表示网络接收队列
  3. Send-Q 表示网络发送队列
  4. Local Address 表示本地地址
  5. Foreign Address 表示外部地址
  6. State 表示当前的状态
  7. PID 表示该进程的进程ID
  8. Program name 表示该进程的程序名称。
  9. 其中Foreign Address写成0.0.0.0:*表示任意IP地址、任意的端口号的程序都可以访问当前进程。

socket编程接口

socket常见API

头文件

#include <sys/types.h>
#include <sys/socket.h>

创建套接字:(TCP/UDP,客户端+服务器)

int socket(int domain, int type, int protocol);

绑定端口号:(TCP/UDP,服务器)

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

监听套接字:(TCP,服务器)

int listen(int sockfd, int backlog);

接收请求:(TCP,服务器)

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

建立连接:(TCP,客户端)

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

发送数据:

// 用于面向连接的套接字,将数据发送到已连接的对等方
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

// 用于无连接的套接字或未连接的面向连接套接字,将数据发送到指定地址
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

 接收数据:

// 用于面向连接的套接字,从已连接的对等方接收数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

// 用于无连接的套接字或未连接的面向连接套接字,接收数据并获取发送方地址
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

关闭套接字:

#include <unistd.h>
// 用于关闭套接字,释放资源
int close(int fd);

#include <sys/socket.h>
// 可以更灵活地关闭套接字的某些功能,如只关闭发送或接收功能
int shutdown(int sockfd, int how);

创建套接字

创建套接字(socket)的函数,如下:

int socket(int domain, int type, int protocol);

参数:

  • domain:创建套接字的域或者叫做协议家族,也就是创建套接字的类型。该参数就相当于struct sockaddr结构的前16个位
    • 如果是本地通信就设置为AF_UNIX
    • 如果是网络通信就设置为AF_INET(IPv4)或 AF_INET6(IPv6)。
  • type:创建套接字时所需的服务类型。其中最常见的服务类型是SOCK_STREAM和SOCK_DGRAM
    • 如果是基于UDP 的网络通信,我们采用的就是SOCK_DGRAM,叫做用户数据报服务,
    • 如果是基于TCP 的网络通信,我们采用的就是SOCK_STREAM,叫做流式套接字,提供的是流式服务。
  • protocol:创建套接字的协议类别。你可以指明为TCP或UDP,但该字段一般直接设置为0就可以了,设置为0表示的就是默认,此时会根据传入的前两个参数自动推导出你最终需要使用的是哪种协议。

返回值:

  • 套接字创建成功返回一个文件描述符,创建失败返回-1,同时错误码会被设置。

socket函数属于什么类型的接口?

网络协议栈是分层的,按照TCP/IP四层模型来说,自顶向下依次是应用层、传输层、网络层和数据链路层。

我们现在所写的代码都叫做用户级代码,也就是说我们是在应用层编写代码,因此我们调用的实际是下三层的接口,而传输层和网络层都是在操作系统内完成的,也就意味着我们在应用层调用的接口都叫做系统调用接口

socket函数是被谁调用的?

socket这个函数是被程序调用的,但并不是被程序在编码上直接调用的,而是程序编码形成的可执行程序运行起来变成进程,当这个进程被CPU调度执行到socket函数时,然后才会执行创建套接字的代码,也就是说socket函数是被进程所调用的

socket函数底层如何实现的

socket函数是被进程所调用的,而每一个进程在系统层面上都有一个进程地址空间PCB(task_struct)、文件描述符表(files_struct)以及对应打开的各种文件。

而文件描述符表里面包含了一个数组fd_array,其中数组中的0、1、2下标依次对应的就是标准输入、标准输出以及标准错误。

当我们调用socket函数创建套接字时,实际相当于我们打开了一个“网络文件”,打开后在内核层面上就形成了一个对应的struct file结构体,同时该结构体被连入到了该进程对应的文件双链表,并将该结构体的首地址填入到了fd_array数组当中下标为3的位置,此时fd_array数组中下标为3的指针就指向了这个打开的“网络文件”,最后3号文件描述符作为socket函数的返回值返回给了用户。

每一个struct file结构体中包含的就是对应打开文件各种信息,比如文件的属性信息、操作方法以及文件缓冲区等

  1. 文件对应的属性在内核当中是由struct inode结构体来维护的
  2. 文件对应的操作方法实际就是一堆的函数指针(比如read*)在内核当中就是由struct file_operations结构体来维护的
  3. 文件缓冲区对于打开的普通文件来说对应的一般是磁盘,但对于现在打开的“网络文件”来说,这里的文件缓冲区对应的就是网卡

 

对于一般的普通文件来说,当用户通过文件描述符将数据写到文件缓冲区,然后再把数据刷到磁盘上就完成了数据的写入操作。

而对于现在socket函数打开的“网络文件”来说,当用户将数据写到文件缓冲区后,操作系统会定期将数据刷到网卡里面,而网卡则是负责数据发送的,因此数据最终就发送到了网络当中。

 绑定端口号

绑定的函数叫做bind,该函数的函数原型如下:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数:

  • sockfd:绑定的文件的文件描述符。也就是我们创建套接字时获取到的文件描述符
  • addr:网络相关的属性信息,包括协议家族、IP地址、端口号等
  • addrlen:传入的addr结构体的长度

返回值:

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

在绑定时需要将网络相关的属性信息(包括协议家族、IP地址、端口号等)填充到一个结构体当中,然后将该结构体作为bind函数的第二个参数进行传入,这实际就是struct sockaddr_in结构体

struct sockaddr_in结构体当中的成员如下:

  1. sin_family:表示协议家族
    1. 如果是本地通信就设置为 AF_UNIX
    2. 如果是网络通信就设置为 AF_INET(IPv4)或 AF_INET6(IPv6)
  2. sin_port:表示端口号,是一个16位的整数
  3. sin_addr:表示IP地址,是一个32位的整数
    1. sin_addr结构体中就一个成员(s_addr),该成员就是一个32位的整数,IP地址实际就是存储在这个整数当中的

发送数据

  • send 用于面向连接的套接字,将数据发送到已连接的对等方
  • sendto 用于无连接的套接字或未连接的面向连接套接字,将数据发送到指定地址
// 用于面向连接的套接字,将数据发送到已连接的对等方
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

// 用于无连接的套接字或未连接的面向连接套接字,将数据发送到指定地址
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

参数: 

  • sockfd:套接字描述符。
  • buf:指向要发送数据的缓冲区的指针。
  • len:要发送数据的长度。
  • flags:控制发送操作的标志,如 MSG_DONTWAIT (不阻塞)等。
  • dest_addr(仅 sendto):指向接收方地址的指针。
  • addrlen(仅 sendto):接收方地址结构体的长度。

返回值:

  • 成功时:返回实际发送的字节数。这个数字可能小于 len,表示只发送了部分数据。这通常发生在以下情况:
    • 发送缓冲区已满,无法一次性发送全部数据,需要多次调用 send 函数将剩余数据发送完。
  • 失败时:返回 -1

接收数据 

  • recv 用于面向连接的套接字,从已连接的对等方接收数据
  • recvfrom 用于无连接的套接字或未连接的面向连接套接字,接收数据并获取发送方地址 
// 用于面向连接的套接字,从已连接的对等方接收数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

// 用于无连接的套接字或未连接的面向连接套接字,接收数据并获取发送方地址
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

参数: 

  • sockfd:套接字描述符。
  • buf:指向接收数据的缓冲区的指针。
  • len:缓冲区的长度。
  • flags:控制接收操作的标志,如 MSG_DONTWAIT (不阻塞)等。
  • src_addr(仅 recvfrom):指向发送方地址的指针。
  • addrlen(仅 recvfrom):指向发送方地址结构体长度的指针。

返回值:

  • 成功时:返回接收到的字节数,如果连接已经正常关闭,返回值为 0,表示对方已关闭连接。
  • 失败时:返回 -1

sockaddr结构

套接字不仅支持跨网络的进程间通信,还支持本地的进程间通信(域间套接字)。在进行跨网络通信时我们需要传递的端口号和IP地址,而本地通信则不需要,因此套接字提供了sockaddr_in结构体和sockaddr_un结构体:

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

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

注意: 实际我们在进行网络通信时,定义的还是sockaddr_in这样的结构体,只不过在传参时需要将该结构体的地址类型进行强转为sockaddr* 了。

为什么没有用 void* 代替 struct sockaddr* 类型?

我们可以将这些函数的 struct sockaddr* 参数类型改为 void*,此时在函数内部也可以直接指定提取头部的16个比特位进行识别,最终也能够判断是需要进行网络通信还是本地通信,那为什么还要设计出sockaddr这样的结构呢?

实际在设计这一套网络接口的时候C语言还不支持void*,于是就设计出了sockaddr这样的解决方案。并且在C语言支持了void*之后也没有将它改回来,因为这些接口是系统接口,系统接口是所有上层软件接口的基石,系统接口是不能轻易更改的,否则引发的后果是不可想的,这也就是为什么现在依旧保留sockaddr结构的原因。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用 Multer 上传图片到阿里云 OSS

文件上传到哪里更好&#xff1f; 上传到服务器本地 上传到服务器本地&#xff0c;这种方法在现今商业项目中&#xff0c;几乎已经见不到了。因为服务器带宽&#xff0c;磁盘 IO 都是非常有限的。将文件上传和读取放在自己服务器上&#xff0c;并不是明智的选择。 上传到云储存…

【端云一体化】云函数的使用

前言 为丰富HarmonyOS对云端开发的支持、实现端云联动&#xff0c;DevEco Studio以Cloud Foundation Kit&#xff08;云开发服务&#xff09;为底座、在传统的“端开发”基础上新增“云开发”能力&#xff0c;开发者在创建工程时选择合适的云开发工程模板&#xff0c;即可在De…

YARN 架构组件及原理

一、YARN 体系架构 YARN&#xff08;Yet Another Resource Negotiator&#xff0c;另一种资源协调者&#xff09; 是 Hadoop 2.0 中的资源管理系统&#xff0c;它的基本设计思想是将 MRv1 中的 JobTracker拆分成了两个独立的服务 &#xff1a;一个全局的资源管理器 ResourceMa…

C# GDI+的DrawString无法绘制Tab键的现象

【啰嗦2句】 现在用C#的人很少了吧&#xff1f;GDI更少了吧&#xff1f;所以这个问题估计也冷门。没关系&#xff0c;分享给特定需要的人也不错。 【问题现象】 工作中开发了一个报告编辑器&#xff0c;实现图文排版等功能&#xff0c;用着没什么问题&#xff0c;直到有一天…

最近在盘gitlab.0.先review了一下docker

# 正文 本猿所在产品的代码是保存到了一个本地gitlab实例上&#xff0c;实例是别的同事搭建的。最近又又又想了解一下&#xff0c;而且已经盘了一些了&#xff0c;所以写写记录一下。因为这个事儿没太多的进度压力&#xff0c;索性写到哪儿算哪儿&#xff0c;只要是新了解到的…

春秋云镜——initial

初步认识内网渗透流程 thinkphp外网打点 打开环境后尝试登陆无果&#xff0c;用fscan扫一下看看 fscan.exe -h 39.99.224.87 发现是think PHP漏洞 补充&#xff1a; fscan&#xff1a;一款内网综合扫描工具&#xff0c;方便一键自动化、全方位漏扫扫描。支持主机存活探测、端…

【C++】string的关系运算与比较分析

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;基础知识&#xff1a;C 中的 string 关系运算器1. 关系运算器概述2. 字符串比较的本质 &#x1f4af;代码解析与扩展代码例一&#xff1a;相等比较代码解析输出 代码例二&a…

Qt C++读写NFC标签NDEF网址URI

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?spma21dvs.23580594.0.0.1d292c1biFgjSs&ftt&id615391857885 #include "mainwindow.h" #include "ui_mainwindow.h" #include <QDebug> #include "QLibrary" …

NVIDIA Clara平台助力医学影像处理:编程案例与实践探索(上)

一、引言 1.1 研究背景与意义 在现代医学领域,医学影像技术已然成为疾病诊断、治疗方案制定以及疗效评估的关键支柱。从早期的 X 射线成像,到如今的计算机断层扫描(CT)、磁共振成像(MRI)、正电子发射断层扫描(PET)等先进技术,医学影像为医生提供了直观、精准的人体内…

【硬件介绍】Type-C接口详解

一、Type-C接口概述 Type-C接口特点&#xff1a;以其独特的扁头设计和无需区分正反两面的便捷性而广受欢迎。这种设计大大提高了用户的使用体验&#xff0c;避免了传统USB接口需要多次尝试才能正确插入的问题。Type-C接口内部结构&#xff1a;内部上下两排引脚的设计虽然可能不…

【数据结构】第1天之Java中的数据结构

前言 众所周知&#xff0c;程序数据结构算法&#xff0c;可见数据结构的重要性。 在Java中&#xff0c;数据结构通常指的是Java集合框架中的类和接口。 Java集合框架提供了一套标准的数据结构&#xff0c;例如列表、集合、映射表等&#xff0c;以及相应的实现类。 今天要分享的…

Open FPV VTX开源之默认MAVLink设置

Open FPV VTX开源之默认MAVLink设置 1. 源由2. 准备3. 连接4. 安装5. 配置6. 测试6.1 启动wfb-ng服务6.2 启动wfb-ng监测6.3 启动QGroundControl6.4 观察测试结果 7. 总结8. 参考资料9. 补充9.1 telemetry_tx异常9.2 DEBUG串口部分乱码9.3 PixelPilot软件问题9.4 偶尔启动卡住 …

Spring Boot 和微服务:快速入门指南

&#x1f496; 欢迎来到我的博客&#xff01; 非常高兴能在这里与您相遇。在这里&#xff0c;您不仅能获得有趣的技术分享&#xff0c;还能感受到轻松愉快的氛围。无论您是编程新手&#xff0c;还是资深开发者&#xff0c;都能在这里找到属于您的知识宝藏&#xff0c;学习和成长…

Redis 为什么要引入 Pipeline机制?

在 Redis 中有一种 Pipeline&#xff08;管道&#xff09;机制&#xff0c;其目的是提高数据传输效率和吞吐量。那么&#xff0c;Pipeline是如何工作的&#xff1f;它又是如何提高性能的&#xff1f;Pipeline有什么优缺点&#xff1f;我们该如何使用 Pipeline&#xff1f; 1、…

Cesium小知识:粒子系统的参数详解

Cesium 的粒子系统通过 ParticleSystem 类提供了一套丰富的参数来控制粒子的生成、行为和外观。以下是这些参数的详细说明,帮助你更好地理解和使用 Cesium 的粒子系统。 基本参数 image (String) - 粒子图像的URL路径。这个图像是每个粒子在渲染时使用的纹理。 startColor (Co…

【数据结构-堆】力扣1834. 单线程 CPU

给你一个二维数组 tasks &#xff0c;用于表示 n​​​​​​ 项从 0 到 n - 1 编号的任务。其中 tasks[i] [enqueueTimei, processingTimei] 意味着第 i​​​​​​​​​​ 项任务将会于 enqueueTimei 时进入任务队列&#xff0c;需要 processingTimei 的时长完成执行。 现…

OSPF - 2、3类LSA(Network-LSA、NetWork-Sunmmary-LSA)

前篇博客有对常用LSA的总结 2类LSA&#xff08;Network-LSA&#xff09; DR产生泛洪范围为本区域 作用:  描述MA网络拓扑信息和网络信息&#xff0c;拓扑信息主要描述当前MA网络中伪节点连接着哪几台路由。网络信息描述当前网络的 掩码和DR接口IP地址。 影响邻居建立中说到…

【强化学习】演员评论家Actor-Critic算法(万字长文、附代码)

&#x1f4e2;本篇文章是博主强化学习&#xff08;RL&#xff09;领域学习时&#xff0c;用于个人学习、研究或者欣赏使用&#xff0c;并基于博主对相关等领域的一些理解而记录的学习摘录和笔记&#xff0c;若有不当和侵权之处&#xff0c;指出后将会立即改正&#xff0c;还望谅…

【2024年华为OD机试】 (C卷,100分)- 密钥格式化(Java JS PythonC/C++)

一、问题描述 题目描述 给定一个非空字符串 S&#xff0c;其被 N 个‘-’分隔成 N1 的子串&#xff0c;给定正整数 K&#xff0c;要求除第一个子串外&#xff0c;其余的串每 K 个用‘-’分隔&#xff0c;并将小写字母转换为大写。 输入描述 正整数 K 和‘-’分割的字符串&a…

基于单片机的指纹密码锁

【摘要】 本设计是一款基于单片机的指纹识别电子密码锁系统。该系统以STC89C52单片机作为模块核心同时结合ZFM-60指纹模块实现录取指纹并存储指纹数据的功能&#xff0c;并且通过HS12864-15C液晶显示比对流程及比对结果&#xff0c;该指纹电子密码锁通过直流继电器与发光二极管…