当前位置: 首页 > news >正文

《TCP/IP网络编程》学习笔记 | Chapter 4:基于TCP的服务器端/客户端(1)

《TCP/IP网络编程》学习笔记 | Chapter 4:基于TCP的服务器端/客户端(1)

  • 《TCP/IP网络编程》学习笔记 | Chapter 4:基于TCP的服务器端/客户端(1)
    • 理解TCP和UDP
      • TCP/IP协议栈
      • TCP/IP协议的诞生背景
      • 链路层
      • 网络层
      • TCP/UDP层
      • 应用层
    • 实现基于TCP的服务端/客户端
      • 进入等待连接请求状态
      • 受理客户端连接请求
      • 回顾 Hello World 服务器端
      • TCP客户端的默认函数调用顺序
      • 客户端套接字地址信息
      • 回顾 Hello World 客户端
      • 基于TCP的服务器端/客户端函数调用关系
    • 实现迭代服务器端/客户端
      • 实现迭代服务器端
      • 迭代回声服务器端/客户端
        • echo_server.c
        • echo_client.c
      • 回声客户端存在的问题
    • 基于 Windows 的实现
    • 习题

《TCP/IP网络编程》学习笔记 | Chapter 4:基于TCP的服务器端/客户端(1)

理解TCP和UDP

根据数据传输方式的不同,基于网络协议的套接字一般分为TCP套接字和UDP套接字。因为TCP套接字是面向连接的,因此又称为基于流(stream)的套接字。

TCP是Transmission Control Protocol(传输控制协议)的简写,意为“对数据传输过程的控制”。因此,学习控制方法及范围有助于正确理解TCP套接字。

TCP/IP协议栈

讲解TCP前先介绍TCP所属的TCP/IP协议栈(Stack,层),如图所示:

在这里插入图片描述

从上图可以看出,TCP/IP协议栈共分为四层,可以理解为数据收发分成了四个层次化过程。也就是说,面对“基于互联网的有效数据传输”的命题,并非通过一个庞大的协议解决问题,而是通过层次化方案——TCP/IP协议栈解决,通过TCP套接字收发数据需要借助四层,如图所示:

在这里插入图片描述

反之,通过UDP套接字收发数据时,利用下图的四层协议栈来完成:

在这里插入图片描述

各层可能通过操作系统等软件实现,也可能通过类似NIC的硬件设备实现。

TCP/IP协议的诞生背景

把“通过因特网完成有效数据传输”问题按照不同领域划分成小问题后,出现了多种协议,它们通过层级结构建立紧密联系。

把协议分成多个层次具有的优点:

  1. 协议设计更容易
  2. 为了通过标准化操作设计开放式系统

以多个标准为依据所设计的系统称为开放式系统,我们现在学习的TCP/IP协议栈也属于其中之一。那么开放式系统具有哪些优点呢?比方:路由器用来完成IP层交互任务,某公司原先使用A路由器,可将其替换成B路由器,即便A、B这两种路由器并非同一产商也可以顺利替换,因为所有的路由器生产产商都会按照IP层标准制造。再举个例子,大家的计算机一般都装有网卡(网络接口卡),即便没安装也没关系,网卡很容易买到,因为所有的网卡制造商都会遵守链路层的协议标准,这就是开放式系统的优点。

链路层

链路层是物理链接领域标准化的结果,也是最基本的领域,专门定义LAN、WAN、MAN等网络标准。若两台主机通过网络进行数据进行交换,则需要下图所示的物理连接,链路层就负责这些标准。

在这里插入图片描述

网络层

准备好物理连接后就要传输数据,为了在复杂的网络中传输数据,首先需要考虑路径的选择。向目标传输数据需要经过哪条路径?解决此问题就是IP层,该层使用的协议就是IP。IP本身是面向消息的、不可靠的协议。每次传输数据时会帮我们选择路径,但每次传输时的路径并不一致。如果传输中发生路径错误,则选择其他路径;但如果发生数据丢失或损坏,则无法解决。换言之,IP协议无法应对数据错误。

TCP/UDP层

IP层解决数据传输中的路径选择问题,只需照此路径传输数据即可。TCP和UDP层以IP层提供的路径信息为基础完成实际的数据传输,故该层又称传输层。UDP比TCP简单,我们后面还会在讨论,现在只解释TCP。TCP可以保证可靠的数据传输,但它发送数据时以IP层为基础,IP层是面向消息的,是不可靠的,那TCP又是如何保证消息的可靠传输呢?

IP层只关注一个数据包(数据传输的基本单位)的传输过程。因此,即使传输多个数据包,每个数据包也是由IP层实际传输的,也就是说传输顺序及传输本身都是不可靠的。若只利用IP层传输数据,则有可能后发送的数据包比早发生的数据包先到达目标主机。另外,传输的数据包A、B、C中可能只收到A和C,B可能丢失或接收到时已损坏。但若添加TCP协议则会按照如下图的方式进行数据传输:

在这里插入图片描述

我们可以看到,当主机A发送1号数据包给主机B时,必须等到主机B确认1号数据包接收成功,才会接着发送2号数据包,如果主机A发送1号数据包却迟迟收不到主机B回复的接收成功,则会认为是超时,并重新发送一个1号数据包。

应用层

数据传输路径、数据确认过程都被隐藏到套接字内部,只需利用套接字编出程序即可。编写软件过程中,需要根据程序特点决定服务器端和客户端之间的数据传输规定,这便是应用层协议。

网络编程的大部分内容就是设计并实现应用层协议。

实现基于TCP的服务端/客户端

下图给出了TCP服务器端默认的函数调用顺序,大部分TCP服务器端都按照该顺序调用。

在这里插入图片描述

调用socket函数创建套接字,声明并初始化地址信息结构体变量,调用bind函数向套接字分配地址。这两个阶段之前都讨论过了,下面讲解之后的几个过程。

进入等待连接请求状态

我们已调用bind函数给套接字分配了地址,接下来就要通过调用listen函数进入等待连接请求状态。只有调用了listen函数,服务端套接字才能进入可接收连接的状态,换言之,这时,客户端才能调用connect函数(若提前调用则会发生错误)。

#include <sys/socket.h>int listen(int sockfd, int backlog);

成功时返回0,失败时返回-1。

参数:

  • sock:希望进入等待连接请求状态的套接字文件描述符,传递的描述符套接字参数成为服务端套接字(监听套接字)
  • backlog:连接请求等待队列(Queue)的长度,若为5,则队列长度为5,表示最多使5个连接请求进入队列

“服务器端处于等待连接请求状态”是指,客户端请求连接时,服务器端受理连接前一直处于等待状态,当有多个客户端一起发送连接请求时,服务器端套接字只能处理一个连接请求,而其他的连接请求,只能暂时放在请求队列。

在这里插入图片描述

客户端如果向服务器端询问:“请问我是否可以发起连接?”服务器端套接字就会亲切应答:“您好!当然可以,但系统正忙,请到等候室排号等待,准备好后会立即受理您的连接。”同时将连接请求请到等候室。调用listen函数即可生成这种门卫(服务器端套接字),listen函数的第二个参数决定了等候室的大小。等候室称为连接请求等待队列,准备好服务器端套接字和连接请求等待队列后,这种可接收连接请求的状态称为等待连接请求状态。

受理客户端连接请求

调用listen函数后,若有新的连接请求,则应按序受理。受理请求意味着进入可接收数据的状态,这里进入这种状态的所需部件当然还是套接字,可能有人会想使用服务器端套接字,但服务器端套接字已经用于监听,如果将其用于与客户端交换数据,那么谁来监听客户端的连接请求呢?因此需要另外一个套接字,但没必要亲自创建,accept函数将自动创建套接字,并连接到发起请求的客户端。

#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

成功时返回创建的套接字文件描述符,失败时返回-1。

参数:

  • sock:服务器套接字的文件描述符
  • addr:保存发起连接请求的客户端地址信息的变量地址值,调用函数后向传递来的地址变量参数填充客户端地址信息
  • addrlen:第二个参数addr结构体的长度,但是存有长度的变量地址。函数调用完成后,该变量即被填入客户端地址长度

accept函数受理连接请求等待队列中待处理的客户端连接请求,函数调用成功时,accept函数内部将产生用于数据I/O的套接字,并返回其文件描述符。需要强调的是,套接字是自动创建的,并自动与发起连接请求的客户端建立连接。

在这里插入图片描述

回顾 Hello World 服务器端

这里,我们重新回顾第一章的hello_server.c。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>void error_handling(char *message);int main(int argc, char *argv[])
{int serv_sock;int clnt_sock;struct sockaddr_in serv_addr;struct sockaddr_in clnt_addr;socklen_t clnt_addr_size;char message[] = "Hello world!";if (argc != 2){printf("Usage: %s <port>\n", argv[0]);exit(1);}serv_sock = socket(AF_INET, SOCK_STREAM, 0);if (serv_sock == -1)error_handling("sock() error");memset(&serv_addr, 0, sizeof(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 (bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)error_handling("bind() error");if (listen(serv_sock, 5) == -1)error_handling("listen() error");clnt_addr_size = sizeof(clnt_addr);clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size);if (clnt_sock == -1)error_handling("accept() error");write(clnt_sock, message, sizeof(message));close(clnt_sock);close(serv_sock);return 0;
}void error_handling(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}

第27行:服务器端实现过程中先要创建套接字,但此时的套接字尚未是真正的服务器端套接字

第31~37行:为了完成套接字地址分配,初始化结构体变量并调用bind函数

第39行:调用accept函数从队列的顶部取出一个连接请求与客户端建立连接,并返回创建的套接字文件描述符。另外,调用accept函数时若等待队列为空,则accept函数不会返回,直到队列中出现新的客户端连接

第47~49行:调用write函数向客户端传输数据,调用close函数关闭连接

TCP客户端的默认函数调用顺序

创建套接字和请求连接就是客户端的全部内容。

在这里插入图片描述

与服务器端相比,区别就在于“请求连接”,它是创建客户端套接字后向服务器端发起的连接请求。服务器端调用listen函数后创建连接请求等待队列,之后客户端即可请求连接。那如何发起连接请求呢?通过connect函数完成:

#include <sys/socket.h>int connect(int sock_fd, struct sockaddr *serv_addr, socklen_t addrlen);

成功时返回0,失败时返回-1。

参数:

  • sock_fd:客户端套接字文件描述符
  • serv_addr:保存目标服务器端地址信息的变量地址值
  • addrlen:以字节为单位传递已传递给第二个结构体参数serv_addr的地址变量长度

客户端调用connect函数后,发生以下情况之一才会返回(完成函数调用):

  • 服务器端接收连接请求
  • 发生断网等异常情况而中断连接请求

需要注意,所谓的“接收连接”并不意味着服务器端调用accept函数,其实是服务器端把连接请求信息记录到等待队列,因此connect函数返回后并不立即进行数据交换。

客户端套接字地址信息

实现服务端必给套接字分配IP地址和端口号,但客户端实现过程未出现,而是创建套接字后立即调用connect函数。网络数据交换必须分配IP和端口号,这是怎么回事呢?

客户端分配地址:

何时:调用connect函数时
何地:操作系统的内核中
如何:IP用计算机(主机)的IP,端口随机

客户端的IP地址和端口在调用connect函数时自动分配,无需调用标记的bind函数进行分配。

回顾 Hello World 客户端

这里,我们再回顾之前的hello_client.c。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>void error_handling(char *message);int main(int argc, char *argv[])
{int sock;struct sockaddr_in serv_addr;char message[30];int str_len;if (argc != 3){printf("Usage: %s <IP> <port>\n", argv[0]);exit(1);}sock = socket(AF_INET, SOCK_STREAM, 0);if (sock == -1)error_handling("sock() error");memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr(argv[1]);serv_addr.sin_port = htons(atoi(argv[2]));if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)error_handling("connect() error!");str_len = read(sock, message, sizeof(message) - 1);if (str_len == -1)error_handling("read() error!");printf("Message from server: %s\n", message);close(sock);return 0;
}void error_handling(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}

第23行:创建准备连接服务器端的套接字,此时创建的是TCP套接字

第27~30行:结构体变量serv_addr中初始化IP和端口信息。初始化值为目标服务器端套接字的IP和端口信息

第32行:调用connect函数向服务器端发送连接请求

第35行:完成连接后,接收服务器端传输的数据

第40行:接收数据后调用close函数关闭套接字,结束与服务器端的连接

基于TCP的服务器端/客户端函数调用关系

前面讲解了TCP服务器端/客户端的实现顺序,实际上二者并非相互独立,让我们画一下它们之间的交互过程。

在这里插入图片描述

服务器端创建套接字后连续调用bind、listen函数进入等待状态,客户端通过调用connect函数发起连接请求,需要注意的是,客户端只能等到服务器端调用listen函数后才能调用connect函数。同时要清楚,客户端调用connect前,服务器端可能先调用了accept函数。当然,此时服务器端在调用accept函数时进入了阻塞状态,直到客户端调用connect函数为止。

实现迭代服务器端/客户端

回声服务器端/客户端:服务器端将客户端传输的字符串数据原封不动地传回客户端。

实现迭代服务器端

何为迭代服务器端?

设置好等待队列后,应向所有客户端提供服务,在受理完一个客户端请求连接后,还需要再受理其他的请求连接。

迭代服务器端的函数调用顺序:

在这里插入图片描述

从上图看出,调用accept函数后,紧接着调用I/O相关的read、write函数,然后调用close函数。这并非针对服务器端套接字,而是针对accept函数调用时所创建的套接字。

调用close函数就意味着结束了针对某一客户端的服务,此时如果还想服务于其他客户端,就要重新调用accept函数。

目前,我们的服务器端套接字同一时刻只能服务于一个客户端连接,将来学完进程和线程后,就可以编写同时服务多个客户端的服务器端了。

迭代回声服务器端/客户端

接下来创建迭代回声服务器端及与之配套的回声客户端,首先整理一下程序的基本运行方式:

  • 服务器端在同一时刻只与一个客户端相连,并提供回声服务
  • 服务器端依次向五个客户端提供服务并退出
  • 客户端接收用户输入的字符串并发送到服务器端
  • 服务器端将接收到的字符串回传给客户端,即“回声”
  • 服务器端与客户端之间的字符串回声一直执行到客户端输入Q为止
echo_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#define BUF_SIZE 1024
void error_handling(char *message);int main(int argc, char *argv[])
{int serv_sock, clnt_sock;char message[1024];int str_len, i;struct sockaddr_in serv_adr, clnt_adr;socklen_t clnt_adr_sz;if (argc != 2){printf("Usage: %s <port>\n", argv[0]);exit(1);}serv_sock = socket(PF_INET, SOCK_STREAM, 0);if (serv_sock == -1)error_handling("socket() error");memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)error_handling("bind() error");if (listen(serv_sock, 5) == -1)error_handling("listen() error");clnt_adr_sz = sizeof(clnt_adr);for (i = 0; i < 5; i++){clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_sz);if (clnt_sock == -1)error_handling("accept() error");elseprintf("Connected client %d \n", i + 1);while ((str_len = read(clnt_sock, message, BUF_SIZE)) != 0)write(clnt_sock, message, str_len);close(clnt_sock);}close(serv_sock);return 0;
}void error_handling(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}

第37~47行:为处理5个客户端连接而添加的循环语句。共调用五次accept函数,依次向五个客户端提供服务

第44、45行:实际完后回声服务的代码,原封不动地传输读取的字符串

第46行:针对连接客户端的套接字调用close函数,向连接的相应套接字发送EOF。换言之,客户端套接字若调用close函数,则第44行的循环条件变为false,因此执行第46行代码

第48行:向5个客户端提供服务后关闭服务器端套接字并终止程序

echo_client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#define BUF_SIZE 1024
void error_handling(char *message);int main(int argc, char *argv[])
{int sock;char message[1024];int str_len;struct sockaddr_in serv_adr;if (argc != 3){printf("Usage: %s <IP> <port>\n", argv[0]);exit(1);}sock = socket(PF_INET, SOCK_STREAM, 0);if (sock == -1)error_handling("socket() error");memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = inet_addr(argv[1]);serv_adr.sin_port = htons(atoi(argv[2]));if (connect(sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)error_handling("connect() error");elseputs("Connected..........");while (1){fputs("Input message(Q to quit):", stdout);fgets(message, BUF_SIZE, stdin);if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))break;write(sock, message, strlen(message));str_len = read(sock, message, BUF_SIZE - 1);message[str_len] = 0;printf("Message from server: %s", message);}close(sock);return 0;
}void error_handling(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}

第29行:调用connect函数。若调用该函数引起的连接请求被注册到服务器端等待队列,则connect函数将完成正常调用。因此,即使通过第30行代码输出了连接提示字符串,如果服务器尚未调用accept函数,也不会真正建立服务关系

第44行:调用close函数向相应套接字发送EOF(EOF即意味着中断连接)

回声客户端存在的问题

下面是echo_client.c的代码:

        write(sock, message, strlen(message));str_len = read(sock, message, BUF_SIZE - 1);message[str_len] = 0;printf("Message from server: %s", message);

以上的代码有个错误假设:每次调用read、write函数时都会以字符串为单位执行实际的I/O操作。但是别忘了,TCP不存在数据边界。因此,多次调用write函数传递字符串有可能一次性传递到服务端,此时,客户端有可能从服务端收到多个字符串,这不是我们希望看到的结果

还要考虑另外一种情况:字符串太长,需要分两次数据包发送,客户端有可能在尚未收到全部数据包时就调用read函数。这些都是TCP特性的问题,我们将在下一章给出解决的办法。

朴素的解决方法:可以提前确定接收数据的大小。若之前传输了20字节长的字符串,则在接收时循环调用read函数读取20个字节。

基于 Windows 的实现

习题

相关文章:

《TCP/IP网络编程》学习笔记 | Chapter 4:基于TCP的服务器端/客户端(1)

《TCP/IP网络编程》学习笔记 | Chapter 4&#xff1a;基于TCP的服务器端/客户端&#xff08;1&#xff09; 《TCP/IP网络编程》学习笔记 | Chapter 4&#xff1a;基于TCP的服务器端/客户端&#xff08;1&#xff09;理解TCP和UDPTCP/IP协议栈TCP/IP协议的诞生背景链路层网络层T…...

深入解析gdb -p 与gdb attach 的区别与使用场景

摘要&#xff1a;本文将详细对比gdb -p 与gdb attach 这两个命令的使用方法、场景及优缺点&#xff0c;帮助读者更好地理解并运用这两个调试工具。 一、引言 在Linux系统中&#xff0c;GDB&#xff08;GNU Debugger&#xff09;是一款功能强大的调试工具&#xff0c;广泛应用…...

C语言 | Leetcode C语言题解之第542题01矩阵

题目&#xff1a; 题解&#xff1a; /*** Return an array of arrays of size *returnSize.* The sizes of the arrays are returned as *returnColumnSizes array.* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().*/ type…...

论文阅读笔记:Image Processing GNN: Breaking Rigidity in Super-Resolution

论文阅读笔记&#xff1a;Image Processing GNN: Breaking Rigidity in Super-Resolution 1 背景2 创新点3 方法4 模块4.1 以往SR模型的刚性4.2 图构建4.2.1 度灵活性4.2.2 像素节点灵活性4.2.3 空间灵活性 4.3 图聚合4.4 多尺度图聚合模块MGB4.5 图聚合层GAL 5 效果5.1 和SOTA…...

前端介绍|基础入门-html+css+js

文章目录 本课程有什么&#xff1f;前端是什么&#xff1f;1. **前端概述**2. **前端的工作职责**3. **前端技术栈**6. **前端开发工具**7. **HTML、CSS、JS的关系** 本课程有什么&#xff1f; 本套课程是零基础入门保姆级课程&#xff0c;课程主要内容包含&#xff1a; HTML…...

[WSL][桌面][X11]WSL2 Ubuntu22.04 安装Ubuntu桌面并且实现GUI转发(Gnome)

1. WSL安装 这里不再赘述&#xff0c;WSL2支持systemd&#xff0c;如果你发现其没有systemd相关指令&#xff0c;那么你应该看看下面这个 https://blog.csdn.net/noneNull0/article/details/135950369 但是&#xff0c;Ubuntu2204用不了这个脚本&#xff0c;比较蛋疼。 – …...

PMC如何根据实际情况调整生产作业计划?

面对原材料价格波动、市场需求突变、供应链不确定性增加等多重挑战&#xff0c;PMC人员如何根据实际情况迅速调整生产作业计划&#xff0c;成为了决定企业能否稳健前行的关键。今天&#xff0c;天行健企业管理咨询公司就来深入探讨&#xff0c;PMC高手们是如何在复杂多变的环境…...

unity中 骨骼、纹理和材质关系

在Unity和游戏开发中&#xff0c;骨骼&#xff08;Skeleton&#xff09;、纹理&#xff08;Texture&#xff09;和材质&#xff08;Material&#xff09;是角色和物体渲染的关键组成部分&#xff0c;它们各自的作用和关系密切关联&#xff0c;通常共同工作来实现一个模型的最终…...

18、论文阅读:AOD-Net:一体化除雾网络

AOD-Net: All-in-One Dehazing Network 前言介绍相关工作物理模型传统方法深度学习方法 建模与扩展变换后的公式网络设计与高级特征任务相结合 除雾评价数据集和实现 前言 该论文提出了一种基于卷积神经网络&#xff08;CNN&#xff09;的图像去雾模型&#xff0c;称为 All-in…...

Hadoop生态圈框架部署(五)- Zookeeper完全分布式部署

文章目录 前言一、Zookeeper完全分布式部署&#xff08;手动部署&#xff09;1. 下载Zookeeper2. 上传安装包2. 解压zookeeper安装包3. 配置zookeeper配置文件3.1 创建 zoo.cfg 配置文件3.2 修改 zoo.cfg 配置文件3.3 创建数据持久化目录并创建myid文件 4. 虚拟机hadoop2安装并…...

【机器学习】聚类算法分类与探讨

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…...

MySQL中distinct与group by之间的性能进行比较

在 MySQL 中&#xff0c;DISTINCT 和 GROUP BY 都是用于去重或汇总数据的常用 SQL 语法。尽管它们在某些情况下能产生相同的结果&#xff0c;但它们的内部工作方式和性能表现可能有所不同。理解这两者的差异&#xff0c;对于选择正确的语法非常重要&#xff0c;尤其是在处理大量…...

计算机视觉读书系列(1)——基本知识与深度学习基础

研三即将毕业&#xff0c;后续的工作可能会偏AI方向的计算机视觉方面&#xff0c;因此准备了两条线来巩固计算机视觉基础。 一个是本系列&#xff0c;阅读经典《Deep Learning for Vision System》&#xff0c;做一些总结跑一些例子&#xff0c;也对应本系列文章 二是OpenCV实…...

怎么查看navicat的数据库密码

步骤1:打开navicat连接数据库工具&#xff0c;顶部的文件栏-导出结果-勾选导出密码-导出 步骤2&#xff1a;导出结果使用NotePad或文本打开&#xff0c;找到&#xff0c;数据库对应的的Password"995E66F64A15F6776“”的值复制下来 <Connection ConnectionName"…...

webrtc前端播放器完整案例

https://download.csdn.net/download/jinhuding/89961792...

GORM优化器和索引提示

在使用 GORM 进行数据库操作时&#xff0c;优化器和索引提示可以帮助你提高查询性能。GORM 提供了一些方法来利用这些特性。 优化器提示 优化器提示&#xff08;Optimizer Hints&#xff09;是数据库系统提供的功能&#xff0c;用于指导查询优化器如何处理查询。不同的数据库…...

linux驱动-i2c子系统框架学习(1)

可以将整个 I2C 子系统用下面的框图来描述&#xff1a; 可以将上面这一 I2C 子系统划分为三个层次&#xff0c;分别为用户空间、内核空间和硬件层&#xff0c;内核空间就包括 I2C 设备驱动层、I2C 核心层和 I2C 适配器驱动层&#xff0c; 本篇主要内容就是介绍 I2C 子系统框架中…...

元戎启行嵌入式面试题及参考答案

介绍下 CAN 通信原理 控制器局域网(CAN)是一种串行通信协议,主要用于汽车、工业自动化等领域的电子控制单元(ECU)之间的通信。 其通信原理是基于多主站架构。在总线上,多个节点(设备)都可以主动发起通信。CAN 协议使用差分信号来传输数据,通过两条信号线 CAN_H 和 CAN…...

【EasyExcel】EasyExcel导出表格包含合计行、自定义样式、自适应列宽

目录 0 EasyExcel简介1 Excel导出工具类设置自定义表头样式设置自适应列宽添加合计行 2 调用导出工具类导出Excel表3 测试结果 0 EasyExcel简介 在数据处理和报表生成的过程中&#xff0c;Excel是一个非常常用的工具。特别是在Java开发中&#xff0c;EasyExcel库因其简单高效而…...

es数据同步(仅供自己参考)

数据同步的问题分析&#xff1a; 当MySQL进行增删改查的时候&#xff0c;数据库的数据有所改变&#xff0c;这个时候需要修改es中的索引库的值&#xff0c;这个时候就涉及到了数据同步的问题 解决方法&#xff1a; 1、同步方法&#xff1a; 当服务对MySQL进行增删改的时候&…...

docker详细操作--未完待续

docker介绍 docker官网: Docker&#xff1a;加速容器应用程序开发 harbor官网&#xff1a;Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台&#xff0c;用于将应用程序及其依赖项&#xff08;如库、运行时环…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

Java如何权衡是使用无序的数组还是有序的数组

在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装&#xff1b;只需暴露 19530&#xff08;gRPC&#xff09;与 9091&#xff08;HTTP/WebUI&#xff09;两个端口&#xff0c;即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

css3笔记 (1) 自用

outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size&#xff1a;0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格&#xff…...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)

在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马&#xff08;服务器方面的&#xff09;的原理&#xff0c;连接&#xff0c;以及各种木马及连接工具的分享 文件木马&#xff1a;https://w…...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP

编辑-虚拟网络编辑器-更改设置 选择桥接模式&#xff0c;然后找到相应的网卡&#xff08;可以查看自己本机的网络连接&#xff09; windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置&#xff0c;选择刚才配置的桥接模式 静态ip设置&#xff1a; 我用的ubuntu24桌…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA

浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求&#xff0c;本次涉及的主要是收费汇聚交换机的配置&#xff0c;浪潮网络设备在高速项目很少&#xff0c;通…...

【Linux】Linux 系统默认的目录及作用说明

博主介绍&#xff1a;✌全网粉丝23W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...