网络基础概念和 socket 编程

news2024/12/23 8:28:38

网络基础概念和 socket 编程

学习目标

  • 了解 OSI 七层模型、TCP/IP 四层模型结构
  • 了解常见的网络协议格式
  • 掌握网络字节序和主机字节序之间的转换
  • 理解 TCP 服务器端通信流程
  • 理解 TCP 客户端通信流程
  • 实现 TCP 服务器端和客户端的代码

推荐一个非常好的学习资料仓库

协议

协议的概念

协议是事先约定好,大家共同遵守的一组规则,如交通信号灯。从应用的角度出发,协议可理解为“规则”,是数据传输和数据的解释的规则,可以简单的理解为各个主机之间进行通信所使用的共同语言。

假设,主机 A、主机 B 双方欲传输文件,规定:

  • 第一次,传输文件名,接收方接收到文件名,应答 OK 给传输方
  • 第二次,发送文件的尺寸,接收方接收到该数据再次应答一个 OK
  • 第三次,传输文件内容,接收方接收数据完成后应答 OK 表示文件内容接收成功
    在这里插入图片描述

由此,无论 A、B 之间传递何种文件,都是通过三次数据传输来完成。A、B 之间形成了一个最简单的数据传输规则。双方都按此规则发送、接收数据。A、B 之间达成的这个相互遵守的规则即为协议。

这种仅在 A、B 之间被遵守的协议称之为原始协议。当此协议被更多的人采用,不断的增加、改进、维护、完善。最终形成一个稳定的、完整的文件传输协议,被广泛应用于各种文件传输过程中。该协议就成为一个标准协议。最早的 ftp 协议就是由此衍生而来。

典型的协议

实际生活中有以下几种常见的协议:

  • 应用层常见的协议有 HTTP 协议、FTP 协议等
    • HTTP:超文本传输协议(Hyper Text Transfer Protocol),是互联网上应用最广泛的一种网络协议
    • FTP:文件传输协议(File Transfer Protocol)
  • 传输层常见的协议有 TCP/UDP 协议
    • TCP:传输控制协议(Transmission Control Protocol),是一种面向连接的、可靠的、基于字节流的传输层通信协议
    • UDP:用户数据协议(User Datagram Protocol),是 OSI 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务
  • 网络层常见的协议有 IP 协议、ICMP 协议、IGMP 协议
    • IP:因特网互联协议(Internet Protocol)
    • ICMP:Internet 控制报文协议(Internet Control Message Protocol),是 TCP/IP 协议族的一个子协议,用于在 IP 主机、路由器之间传递控制消息
    • IGMP:Internet 组管理协议(Internet Group Management Protocol),是因特网协议家族中的一个组播协议,该协议运行在主机和组播路由器之间
  • 网络接口层常见的协议有 ARP 协议、RARP 协议
    • ARP:正向地址解析协议(Address Resolution Protocol),通过已知的 IP,寻找对应主机的 MAC 地址
    • RARP:反向地址转换协议,通过 MAC 地址确定 IP 地址

网络模型

OSI 七层模型

OSI 七层模型是国际标准组织制定的 OSI 理论模型,该模型定义了不同计算机互联的标准, 是设计和描述计算机网络通信的基本框架。七层模型分别是以下几个(从上向下):

  • 应用层:是最靠近用户的 OSI 层,这一层为用户的应用程序(例如电子邮件、文件传输和终端仿真)提供网络服务
  • 表示层:可确保一个系统的应用层所发送的信息可以被另一个系统的应用层读取。例如,PC 程序与另一台计算机进行通信,其中一台计算机使用扩展二一十进制交换码(EBCDIC),而另一台则使用美国信息交换标准码(ASCII)来表示相同的字符。如有必要,表示层会通过使用一种通格式来实现多种数据格式之间的转换
  • 会话层:通过传输层(端口号:传输端口与接收端口)建立数据传输的通路。主要在你的系统之间发起会话或者接受会话请求(设备之间需要互相认识可以是IP也可以是MAC或者是主机名)
  • 传输层:定义了一些传输数据的协议和端口号(WWW 端口 80 等),主要是将从下层接收的数据进行分段和传输,到达目的地址后再进行重组,常常把这一层数据叫做段
  • 网络层:在位于不同地理位置的网络中的两个主机系统之间提供连接和路径选择。Internet 的发展使得从世界各站点访问信息的用户数大大增加,而网络层正是管理这种连接的层
  • 数据链路层:定义了如何让格式化数据以帧为单位进行传输,以及如何让控制对物理介质的访问。这一层通常还提供错误检测和纠正,以确保数据的可靠传输。如:串口通信中使用到的 115200、8、N、1
  • 物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(就是由 1、0 转化为电流强弱来进行传输,到达目的地后再转化为 1、0,也就是我们常说的数模转换与模数转换),这一层的数据叫做比特

在这里插入图片描述

TCP/IP 四层模型

在实际生产开发中,讨论更多的是 TCP/IP 四层模型,这是对七层模型的简化

在这里插入图片描述

TCP/IP 网络协议栈分为应用层(Application)、传输层(Transport)、网络层(Network)和链路层(Link)四层,如下图所示:

在这里插入图片描述

数据通信过程

数据的通信过程本质上在发送方是一个层层打包的过程,在接收方是一个层层解包的过程,这种打包的过程是内核帮我们完成。如下图所示,PC 机 A 通过网络向 PC 机 B 发送数据的过程

在这里插入图片描述

网络应用程序设计模式

目前使用最多的设计模式就两种 C/S 和 B/S:

  • C/S:客户端和服务器模式,需要在通讯两端各自部署客户机和服务器来完成数据通信
    • 优点:客户端在本机上可以保证性能,可以将数据缓存到本地,提高数据的传输效率,提高用户体验效果;客户端和服务端程序都是由同一个开发团队开发,协议选择比较灵活
    • 缺点:服务器和客户端都需要开发,工作量相对较大,调试困难,开发周期长;从用户的角度看,需要将客户端安装到用户的主机上,对用户主机的安全构成威胁
  • B/S:浏览器和服务器模式,只需在一端部署服务器,而另一端使用浏览器即可完成数据传输
    • 优点:无需安装客户端,可以使用标注你的浏览器作为客户端;只需要开发服务器,工作量相对较小;由于采用标准的客户端,所以移植性好,不受平台限制;相对安全,不用安装软件
    • 缺点:由于没有客户端,数据缓冲不尽人意,数据传输有限制,用户体验较差;通信协议选择只能使用 HTTP 协议,协议选择不够灵活

以太网帧

以太网帧格式就是包装在网络接口层(数据链路层)的协议,具体格式如下:

在这里插入图片描述

以 ARP 为例,其协议格式具体如下:
在这里插入图片描述

源 MAC 地址、目的 MAC 地址在以太网首部和 ARP 请求中各出现一次,对于链路层为以太网的情况是多余的,但如果链路层是其它类型的网络则有可能是必要的。硬件类型指链路层网络类型,1 为以太网,协议类型指要转换的地址类型,0x0800 为 IP 地址,后面两个地址长度对于以太网地址和IP地址分别为 6 和 4(字节),op 字段为 1 表示 ARP 请求,op 字段为 2 表示 ARP 应答。

假设现在 PC 机 A 向 PC 机 B 发送请求,简单的流程如下:

  • PC 机 A 将本机的 MAC 地址填入源地址,以广播的方式发送 ARP 报文,因此数据报文的格式为
    • 以太网首部(14字节):0000: ff ff ff ff ff ff 00 05 5d 61 58 a8 08 06 —— 目的主机采用广播地址,源主机的 MAC 地址是00:05:5d:61:58:a8,上层协议类型 0x0806 表示 ARP
    • ARP 帧(28 字节):
      • 0000: 00 01 —— 硬件类型 0x0001 表示以太网
      • 0010: 08 00 06 04 00 01 00 05 5d 61 58 a8 c0 a8 00 37 —— 协议类型 0x0800 表示 IP 协议,硬件地址(MAC地址)长度为 6,协议地址(IP地址)长度为 4,op 为 0x0001 表示请求目的主机的 MAC 地址,源主机 MAC 地址为 00:05:5d:61:58:a8,源主机 IP 地址为 c0 a8 00 37(192.168.0.55)
      • 0020: 00 00 00 00 00 00 c0 a8 00 02 —— 目的主机 MAC 地址全 0 待填写,目的主机 IP 地址为 c0 a8 00 02(192.168.0.2)
    • 填充位(18 字节):由于以太网规定最小数据长度为46字节,ARP帧长度只有28字节,因此有18字节填充位,填充位的内容没有定义,与具体实现相关
      • 0020: 00 77 31 d2 50 10
      • 0030: fd 78 41 d3 00 00 00 00 00 00 00 00
  • PC 机 B 收到 ARP 数据报文后,发送应答
    • 以太网首部(14字节):0000: 00 05 5d 61 58 a8 00 05 5d a1 b8 40 08 06 —— 目的主机的 MAC 地址是00:05:5d:61:58:a8,源主机的 MAC 地址是 00:05:5d:a1:b8:40,上层协议类型 0x0806 表示 ARP
    • ARP 帧(28 字节):
      • 0000: 00 01 —— 硬件类型 0x0001 表示以太网
      • 0010: 08 00 06 04 00 02 00 05 5d a1 b8 40 c0 a8 00 02 —— 协议类型 0x0800 表示 IP 协议,硬件地址(MAC地址)长度为 6,协议地址(IP地址)长度为 4,op 为 0x0002 表示应答,源主机 MAC 地址为 00:05:5d:a1:b8:40,源主机 IP 地址为 c0 a8 00 02(192.168.0.2)
      • 0020: 00 05 5d 61 58 a8 c0 a8 00 37 —— 目的主机MAC地址为 00:05:5d:61:58:a8,目的主机 IP 地址为c0 a8 00 37(192.168.0.55)
    • 填充位(18 字节):
      • 0020: 00 77 31 d2 50 10
      • 0030: fd 78 41 d3 00 00 00 00 00 00 00 00

其他的数据包格式也以差不多的方式进行发送和应答。

注意:通过 IP 地址可以确定同一网段中唯一的一台主机,主机使用端口号来区分不同的应用程序。

socket 编程

传统的进程间通信借助内核提供的IPC机制进行,但是只能限于本机通信,若要跨机通信,就必须使用网络通信(本质上借助内核-内核提供了 socket 伪文件的机制实现通信——实际上是使用文件描述符),这就需要用到内核提供给用户的 socket API 函数库。

网络字节序

在进行网络通信时,一定要注意数据的字节序问题,如果不使用同一的字节序,发送/接收的数据可能是错误的。网络字节序分为两种:

  • 大端字节序:低地址存放高位数据,高地址存放低位数据
  • 小段字节序:低地址存放低位数据,高地址存放高位数据

在这里插入图片描述

如何确定本机上是大端还是小段,代码测试实例如下:

#include <stdio.h>

union {
  short sval;
  char cval[sizeof(short)];
} u1;

union {
  int ival;
  char cval[sizeof(int)];
} u2;

int main() {
  u1.sval = 0x0102;
  // cval[0] 中存放的是 0x01 则是大端字节序,否则则是小端字节序
  printf("u1.sval = %#x, u1.cval[0] = %#x, u1.cval[1] = %#x\n", u1.sval, u1.cval[0], u1.cval[1]);

  u2.ival = 0x01020304;
  printf("u2.ival = %#x, u2.cval[0] = %#x, u2.cval[1] = %#x, u2.cval[2] = %#x, u2.cval[3] = %#x\n", u2.ival, u2.cval[0], u2.cval[1], u2.cval[2], u2.cval[3]);

  return 0;
}

在网络传输中使用的是大端字节序,如果机器用的是小端法,则需要进行大小端的转换。一般使用以下 4 个函数:

#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,s 表示 shortl 表示 long

socket 编程的 API 函数

socket 函数

socket 函数用来创建一个网络套接字

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

/**
  * @param:
  *   domain: 协议版本,有 AF_INET 表示 IPV4,AF_INET6 表示 IPV6 等
  *   type: 协议类型,主要有 SOCK_STREAM 和 SOCK_DGRAM,分别表示 TCP 和 UDP
  *   protocol: 一般填 0, 表示使用对应类型的默认协议
  *  @return: 成功返回大于 0 的文件描述符,失败返回 -1
  */
int socket(int domain, int type, int protocol);

当调用 socket 函数以后,返回一个文件描述符,内核会提供与该文件描述符相对应的读和写缓冲区,同时还有两个队列,分别是请求连接队列和已连接队列。
在这里插入图片描述

bind 函数

bind 函数将 socket 创建的文件描述符与 IP,port 绑定

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

/**
  * @param:
  *   sockfd: 调用 socket 函数返回的文件描述符
  *   addr: 本地服务器的 IP 地址和 PORT
  *   addrlen: addr 变量的占用的内存大小
  *  @return: 成功返回 0,失败返回 -1
  */
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockaddr 是一个保存 IP 和 PORT 的结构体,其原型如下:

struct sockaddr {
  sa_family_t sa_family;
  char sa_data[14];
}

由于 sockaddr 在保存地址数据的时候比较繁琐,有了 sockaddr_in 结构,其结构原型如下:

struct sockaddr_in {
  sa_family_t    sin_family; /* address family: AF_INET */
  in_port_t      sin_port;   /* port in network byte order */
  struct in_addr sin_addr;   /* internet address */
};

/* Internet address. */
struct in_addr {
  uint32_t       s_addr;     /* address in network byte order */
};

在实际的使用中,使用更多的是 sockaddr_in 结构类型,传参时进行类型转换即可,这两个结构体的大小是一样的。

在这里插入图片描述

listen 函数

listen 函数将套接字由主动改为被动

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

/**
  * @param:
  *   sockfd: 调用 socket 函数返回的文件描述符
  *   backlog: 同时请求连接的最大来凝结个数(进入连接队列)
  *  @return: 成功返回 0,失败返回 -1
  */
int listen(int sockfd, int backlog);
accept 函数

accept 函数从连接队列中获取一个连接,如何连接队列中没有连接则会阻塞等待

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

/**
  * @param:
  *   sockfd: 调用 socket 函数返回的文件描述符
  *   addr: 传出参数,保存客户端的地址信息
  *   addrlen: 传入传出参数,addr 变量所占内存空间大小
  *  @return: 成功返回获取连接客户端的文件描述符,失败返回 -1
  */
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

内核会负责将请求队列中的连接拿到已连接队列中。

connect 函数

connect 函数是主动向指定的 IP 和 PORT 地址发送连接请求。

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

/**
  * @param:
  *   sockfd: 调用 socket 函数返回的文件描述符
  *   addr: 服务端的地址信息
  *   addrlen: addr 变量所占内存空间大小
  *  @return: 成功返回 0,失败返回 -1
  */
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

在发送请求连接前,需要确定服务器的地址信息,其中包括以点分十进制表示的 IP 地址,将其转换成网络字节序的 IP 地址方式使用以下的函数:

#include <arpa/inet.h>

/**
  * @description: 将 IPV4 或 IPV6 的地址从点分十进制的 IP 转换为网络字节序
  * @param:
  *   af: AF_INET 或 AF_INET6
  *   src: 字符串形式的点分十进制的 IP 地址
  *   dst: 存放转换后的变量的地址
  *  @return: 成功返回指向 dst 的指针,失败返回 NULL
  */
int inet_pton(int af, const char *src, void *dst);

/**
  * @description: 将 IPV4 或 IPV6 的地址从网络字节序转换为点分十进制的 IP
  * @param:
  *   af: AF_INET 或 AF_INET6
  *   src: 网络的整形的 IP 地址
  *   dst: 转换后的 IP 地址,一般为字符串数组
  *   size: dst 的长度
  *  @return: 成功返回指向 dst 的指针,失败返回 NULL
  */
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

服务器端和客户端的开发流程

使用 socket 的 API 函数编写服务端和客户端程序的步骤图示

在这里插入图片描述

服务器端的实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>

#define BUFFERSIZE 1024

int main(int argc, char *argv[]) {
  if (2 != argc) {
    fprintf(stderr, "Usage: %s <port>\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  // 创建套接字
  int sfd = socket(AF_INET, SOCK_STREAM, 0);
  if (-1 == sfd) {
    perror("socket() error");
    exit(EXIT_FAILURE);
  }

  // 绑定 IP 和 PORT
  struct sockaddr_in serv_addr;
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  serv_addr.sin_port = htons(atoi(argv[1]));
  if (-1 == bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) {
    perror("bind() error");
    close(sfd);
    exit(EXIT_FAILURE);
  }

  // 将套接字由主动态变为被动态
  if (-1 == listen(sfd, 2)) {
    close(sfd);
    perror("listen() error");
    exit(EXIT_FAILURE);
  }

  // 从连接获得一个连接,没有连接则阻塞等待,连接队列取完则退出
  struct sockaddr_in clnt_addr;
  socklen_t addr_len = sizeof(clnt_addr);
  int cfd = accept(sfd, (struct sockaddr *)&clnt_addr, &addr_len);
  if (-1 == cfd) {
    close(sfd);
    perror("accept() error");
    exit(EXIT_FAILURE);
  }

  int rlen;
  char message[BUFFERSIZE] = {0};
  // 开始读取和发送数据
  while (1) {
    memset(message, 0, BUFFERSIZE);
    rlen = read(cfd, message, BUFFERSIZE);
    if (0 == rlen) {
      printf("client %d is disconnected\n", cfd);
      break;
    } else if (0 > rlen) {
      perror("read() error");
      break;
    }

    printf("READ: %s", message);
    write(cfd, message, rlen);
  }

  close(cfd);
  close(sfd);

  return 0;
}
客户端的实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>

#define BUFFERSIZE 1024

int main(int argc, char *argv[]) {
  if (3 != argc) {
    fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
    exit(EXIT_FAILURE);
  }
  
  // 创建套接字
  int cfd = socket(AF_INET, SOCK_STREAM, 0);
  if (-1 == cfd) {
    perror("socket() error");
    exit(EXIT_FAILURE);
  }

  // 向服务器端发送连接请求
  struct sockaddr_in clnt_addr;
  clnt_addr.sin_family = AF_INET;
  inet_pton(AF_INET, argv[1], &clnt_addr.sin_addr.s_addr);
  clnt_addr.sin_port = htons(atoi(argv[2]));
  if (-1 == connect(cfd, (struct sockaddr *)&clnt_addr, sizeof(clnt_addr))) {
    close(cfd);
    perror("connect() error");
    exit(EXIT_FAILURE);
  }

  char message[BUFFERSIZE] = {0};
  while (1) {
    memset(message, 0, BUFFERSIZE);
    printf("Please input message(q/Q to quit): ");
    fgets(message, BUFFERSIZE-1, stdin);
    if (!strcmp(message, "Q\n") || !strcmp(message, "q\n"))
      break;

    int wlen = write(cfd, message, sizeof(message));
    printf("WRITE: %s", message);
    int rlen = read(cfd, message, BUFFERSIZE);
    if (rlen < 0) {
      perror("read() error");
      break;
    }

    printf("READ: %s", message);
  }

  close(cfd);

  return 0;
}

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

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

相关文章

RPA自动化流程机器人有哪些优势?

在数字化快速推进的大背景下&#xff0c;人工智能正在以前所未有的速度改变着生活和生产方式&#xff0c;而RPA自动化流程机器人作为其中一种最重要的革命性的技术&#xff0c;已经成为企业数字化中不可或缺的重要力量&#xff0c;让员工加速从“重复性工作”中摆脱出来。 金智…

OceanBase技术解析: 执行器中的自适应技术

在《OceanBase 数据库源码解析》这本书中&#xff0c;对于执行器的探讨还不够深入&#xff0c;它更多地聚焦于执行器的并行处理机制。因此&#xff0c;通过本文与大家分享OceanBase执行器中几种典型的自适应技术&#xff0c;作为对书中执行器部分的一个补充。 提升数据库分析性…

[Redis][主从复制][下]详细讲解

目录 1.复制1.全量复制2.1部分复制2.2复制积压缓冲区3.实时复制 2.总结 1.复制 1.全量复制 什么时候进行全量复制&#xff1f; 首次和主节点进行数据同步主节点不方便进行部分复制的时候 全量复制流程&#xff1a; 从节点发送psync命令给主节点进⾏数据同步&#xff0c;由于是…

①EtherCAT转Modbus485RTU网关多路同步高速采集无需编程串口服务器

EtherCAT转Modbus485RTU网关多路同步高速采集无需编程串口服务器https://item.taobao.com/item.htm?ftt&id798036415719 EtherCAT 串口网关 EtherCAT 转 RS485 型号&#xff1a; 1路总线EC网关 MS-A2-1011 2路总线EC网关 MS-A2-1021 4路总线EC网关 MS-A2-1041 技…

arcgis for js实现阴影立体效果

效果 实现 主要通过effect属性实现 代码: (这里以GeoJSON图层为例, 代码复制即可用) <!DOCTYPE html> <html lang"zn"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge&quo…

PHP 于小项目:从鉴权说起

PHP 于小项目&#xff1a;从鉴权说起 在当今这个开发技术多样化的时代&#xff0c;选择合适的开发语言和框架常常决定了项目的效率与成败。对于个人开发者&#xff0c;特别是那些进行小型、短期项目的人来说&#xff0c;PHP 是一种特别友好的选择。本文将通过介绍 PHP 实现鉴权…

YOLOv9改进,YOLOv9主干网络替换为GhostNetV3(2024年华为提出的轻量化架构,全网首发),助力涨点

摘要 GhostNetV3 是由华为诺亚方舟实验室的团队发布的,于2024年4月发布。 摘要:紧凑型神经网络专为边缘设备上的应用设计,具备更快的推理速度,但性能相对适中。然而,紧凑型模型的训练策略目前借鉴自传统模型,这忽略了它们在模型容量上的差异,可能阻碍紧凑型模型的性能…

仓库场景物品检测分类系统源码分享

仓库场景物品检测分类检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Comp…

显示adb报错,uniapp安装自定义基座

uni-app,uniCloud,serverless,真机运行常见问题,第1步 HX中没有运行到手机的菜单,第2步 电脑是否能检测到手机,第3步 电脑与手机是否建立信任调试关系,3.1 Android设备信任,3.2 iOS设备信任,第4步 HBuilderX检测手机,4.1 检测Android手https://uniapp.dcloud.net.cn/tutorial/r…

PHP爬虫APP程序:打造智能化数据抓取工具

在信息爆炸的时代&#xff0c;数据的重要性日益凸显。PHP作为一种广泛使用的服务器端脚本语言&#xff0c;因其强大的功能和灵活性&#xff0c;成为开发爬虫程序的理想选择。本文将探讨如何使用PHP构建一个爬虫APP程序&#xff0c;以及其背后的思维逻辑和实现步骤。 什么是PHP爬…

【高分系列卫星简介——高分七号卫星(GF-7)】

高分七号卫星&#xff08;GF-7&#xff09; 高分七号&#xff08;GF-7&#xff09;卫星是中国高分辨率对地观测系统&#xff08;高分专项&#xff09;的重要组成部分&#xff0c;具有显著的技术突破和广泛的应用价值。以下是对高分七号卫星的详细介绍&#xff1a; 一、基本信息…

word2vector训练代码详解

目录 1.代码实现 2.知识点 1.代码实现 #导包 import math import torch from torch import nn import dltools #加载PTB数据集 &#xff0c;需要把PTB数据集的文件夹放在代码上一级目录的data文件中&#xff0c;不用解压 #批次大小、窗口大小、噪声词大小 batch_size, ma…

堆的数组实现

目录 一、堆 二叉树的顺序结构 堆的概念及结构 1.概念 2.堆的分类 (1)大堆 (2)小堆 二、利用数组(顺序结构)实现堆的过程 1.利用数组实现堆的思路 2.堆是用数组实现的&#xff0c;在数组中通过双亲找自己左右孩子、通过左右孩子找自己双亲的思路 2.1.思路 2.2.孩子与…

认知杂谈84《菜鸟的自我修炼:知易行难与行难知易》

内容摘要&#xff1a; 理解与行动之间的差距是日常生活的常见挑战。"知易行难"体现在理解简单但执行困难&#xff0c;例如知道蔬菜有益但难以坚持食用。而"行难知易"则是开始时困难但后来容易的任务&#xff0c;如学习骑自行车。 这种差异源于心理惰性和习…

使用 Llama-index 实现的 Agentic RAG-Router Query Engine

前言 你是否也厌倦了我在博文中经常提到的老式 RAG(Retrieval Augmented Generation | 检索增强生成) 系统&#xff1f;反正我是对此感到厌倦了。但我们可以做一些有趣的事情&#xff0c;让它更上一层楼。接下来就跟我一起将 agents 概念引入传统的 RAG 工作流&#xff0c;重新…

OnlyOffice 打开文档时提示下载失败

OnlyOffice 下载失败问题 问题概述 OnlyOffice前端界面出现“下载失败” 问题定位&#xff08;0&#xff1a;docker内不能够访问&#xff09; 很常见的一种情况是后端服务地址错误&#xff0c;在docker内无法访问。 请在docker容器中确定这个地址是可以访问的&#xff0c;鉴…

electron 设置界面右下角打开

功能需求场景 写一个可以下载各种平台的小工具&#xff0c;需要右下角打开方便做其它事情 实现基础 要在屏幕的右下角设置窗口&#xff0c;可以调整mainWindow的创建参数&#xff0c;特别是通过使用x和y坐标来定位窗口 &#xff1b; 需要获取屏幕的尺寸&#xff0c;并据此计算…

不透明物体的投射和接收阴影

1、Fallback的作用 新建一个材质球&#xff0c;将其的Shader设置为之前编写的多种光源综合实现Shader 并将该材质球赋值给较大的立方体使用&#xff0c;我们会发现该立方体不再投射阴影也不再接受阴影 &#xff08;1&#xff09;不投射阴影的原因 该Shader中没有LightMo…

Rust编程的if选择语句

【图书介绍】《Rust编程与项目实战》-CSDN博客 《Rust编程与项目实战》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 (jd.com) Rust编程与项目实战_夏天又到了的博客-CSDN博客 Rust语言实现选择结构时&#xff0c;根据某种条件的成立与否而采用不同的程序段进行…

【Kubernetes】日志平台EFK+Logstash+Kafka【实战】

一&#xff0c;环境准备 &#xff08;1&#xff09;下载镜像包&#xff08;共3个&#xff09;&#xff1a; elasticsearch-7-12-1.tar.gz fluentd-containerd.tar.gz kibana-7-12-1.tar.gz &#xff08;2&#xff09;在node节点导入镜像&#xff1a; ctr -nk8s.io images i…