《TCP/IP网络编程》学习笔记 | Chapter 9:套接字的多种可选项

《TCP/IP网络编程》学习笔记 | Chapter 9:套接字的多种可选项

  • 《TCP/IP网络编程》学习笔记 | Chapter 9:套接字的多种可选项
    • 套接字可选项和 I/O 缓冲大小
      • 套接字多种可选项
      • getsockopt & setsockopt
      • SO_SNDBUF & SO_RCVBUF
    • SO_REUSEADDR
      • 发生地址绑定错误(Binding Error)
      • TIME_WAIT 状态
      • 地址再分配
    • TCP_NODELAY
      • Nagle 算法
      • Nagle 算法的优缺点
      • 禁用 Nagle 算法
    • 基于 Windows 的实现
      • 基于 Windows 的套接字类型示例程序
      • 基于 Windows 的套接字 I/O 缓冲大小示例程序
      • 基于 Windows 的 TCP_NODELAY 示例程序
    • 习题
      • (1)下列关于Time-wait状态的说法错误的是?
      • (2)TCP_NODELAY可选项与Nagle算法有关,可通过它禁用Nagle算法。请问何时应考虑禁用Nagle算法?结合收发数据的特性给出说明。

《TCP/IP网络编程》学习笔记 | Chapter 9:套接字的多种可选项

套接字可选项和 I/O 缓冲大小

套接字多种可选项

前文关于套接字的描述仅仅是使用其默认套接字特性来进行数据通信,这对于简单的使用场景来说似乎是可以的,然而实际工作场景中的确需要配置相关套接字选项来满足一些特殊需求。下图所示是一些常用的套接字可选配置选项。

在这里插入图片描述

从图中可以看出,套接字可选项是分层的。IPPROTO_IP层可选项是IP协议相关事项,IPPROTO_TCP层可选项是TCP协议相关事项,SOL_SOCKET层是套接字相关的通用可选项。

getsockopt & setsockopt

针对上文所描述的套接字可选项,可分别通过getsockopt函数和setsockopt函数来进行读取(Get)和设置(Set)(有些选项可能仅支持一种操作)。

读取套接字可选项:

#include <sys/socket.h>int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);

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

参数:

  • sock:要查看的套接字的文件描述符。
  • level:要查看的可选项的协议层。
  • optname:要查询的可选项的名称。
  • optval:指向一个缓冲区,该缓冲区将接收查询的选项值。
  • optlen:向optval缓冲区传递的缓冲大小。调用成功后,它会被设置为选项值的实际长度。

更改套接字可选项:

#include <sys/socket.h>int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);

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

参数:

  • sock:要更改的套接字的文件描述符。
  • level:要更改的可选项的协议层。
  • optname:要更改的可选项的名称。
  • optval:要更改的选项信息的缓冲选项值。
  • optlen:向optval传递的可选项信息的字节数。

示例程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>void error_handling(char *message);int main(int argc, char *argv[])
{int tcp_sock, udp_sock;int sock_type;socklen_t optlen;int state;optlen = sizeof(sock_type);tcp_sock = socket(PF_INET, SOCK_STREAM, 0);udp_sock = socket(PF_INET, SOCK_DGRAM, 0);printf("SOCK_STREAM: %d \n", SOCK_STREAM);printf("SOCK_DGRAM: %d \n", SOCK_DGRAM);state = getsockopt(tcp_sock, SOL_SOCKET, SO_TYPE, (void *)&sock_type, &optlen);if (state)error_handling("getsockopt() error!");printf("Socket type one: %d \n", sock_type);state = getsockopt(udp_sock, SOL_SOCKET, SO_TYPE, (void *)&sock_type, &optlen);if (state)error_handling("getsockopt() error!");printf("Socket type two: %d \n", sock_type);return 0;
}void error_handling(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}

本程序分别生成TCP、UDP套接字,输出创建TCP、UDP套接字时传入的SOCK_STREAM、SOCK_DGRAM。获取套接字类型信息,如果是TCP套接字,将获得SOCK_STREAM常数值1;如果是UDP套接字,则获得SOCK_DGRAM的常数值2。

运行结果:

在这里插入图片描述

上述示例给出了调用getsockopt函数查看套接字信息的方法,另外,用于验证套接字类型的SO_TYPE是典型的只读可选项,即套接字类型只能在创建时决定,以后不能再更改。

SO_SNDBUF & SO_RCVBUF

前文中我们提到套接字的输入输出缓冲区,而SO_SNDBUF 和SO_RCVBUF便是与套接字缓冲区大小相关的两个可选项。通过这两个选项我们可以获取当前套接字的输入输出缓冲区大小,或者设置相应缓冲区的大小。如下是这两个选项使用的相关示例代码。

get_buf.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>void error_handling(char *message);int main(int argc, char *argv[])
{int sock;int snd_buf, rcv_buf, state;socklen_t len;sock = socket(PF_INET, SOCK_STREAM, 0);len = sizeof(snd_buf);state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void *)&snd_buf, &len);if (state)error_handling("getsockopt() error");len = sizeof(rcv_buf);state = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void *)&rcv_buf, &len);if (state)error_handling("getsockopt() error");printf("Input buffer size: %d \n", rcv_buf);printf("Outupt buffer size: %d \n", snd_buf);return 0;
}void error_handling(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}

运行结果:

在这里插入图片描述

注:每个人的电脑系统不同,运行结果可能会有差异。

set_buf.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>void error_handling(char *message);int main(int argc, char *argv[])
{int sock;int snd_buf = 1024 * 3, rcv_buf = 1024 * 3;int state;socklen_t len;sock = socket(PF_INET, SOCK_STREAM, 0);state = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void *)&rcv_buf, sizeof(rcv_buf));if (state)error_handling("setsockopt() error!");state = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void *)&snd_buf, sizeof(snd_buf));if (state)error_handling("setsockopt() error!");len = sizeof(snd_buf);state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void *)&snd_buf, &len);if (state)error_handling("getsockopt() error!");len = sizeof(rcv_buf);state = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void *)&rcv_buf, &len);if (state)error_handling("getsockopt() error!");printf("Input buffer size: %d \n", rcv_buf);printf("Output buffer size: %d \n", snd_buf);return 0;
}void error_handling(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}

运行结果:

在这里插入图片描述

从运行结果可以看出,对于缓冲大小的设置并非完全生效。实际上这些设置只是传递了我们的要求,而最终的生效值操作系统会根据当前环境做出设置,不过配置值的大小趋势和我们期望的一致。

SO_REUSEADDR

发生地址绑定错误(Binding Error)

我们之前介绍了回声服务器端/客户端的实现。其中服务器端代码稍作改变如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#define TRUE 1
#define FALSE 0void error_handling(char *message);int main(int argc, char *argv[])
{int serv_sock, clnt_sock;char message[30];int option, str_len;socklen_t optlen, clnt_adr_sz;struct sockaddr_in serv_adr, clnt_adr;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");/*optlen=sizeof(option);option=TRUE;setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);*/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)))error_handling("bind() error ");if (listen(serv_sock, 5) == -1)error_handling("listen error");clnt_adr_sz = sizeof(clnt_adr);clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_sz);while ((str_len = read(clnt_sock, message, sizeof(message))) != 0){write(clnt_sock, message, str_len);write(1, message, str_len);}close(clnt_sock);return 0;
}void error_handling(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}

客户端通过输入“Q”消息,或是通过CTRL+C终止程序,两种方式客户端都会执行close函数向服务器端传递FIN消息,经过四次挥手后断开连接。

现在考虑另一种情况,如果服务器端和客户端在已建立连接的状态下,向服务器端执行CTRL+C终止程序,会发生什么?

这种情况,服务器端会主动向客户端发送FIN消息断开连接并退出程序。此时,如果再次以相同端口号启动服务器端则会发生错误(bind()报错:“Address already in use”),通常需要等待1~4分钟才能再次运行服务器端。

客户端主动发送FIN消息断开连接,不影响客户端或服务器端的再次运行;而服务器端主动发送FIN消息断开连接,则会影响服务器端的再次运行,为什么会出现这种现象呢?

TIME_WAIT 状态

四次挥手过程:

在这里插入图片描述

从图中可以看出,主动断开连接的主机(先发送FIN消息)会经过TIME_WAIT的状态,持续时间为2MSL(Maximum Segment Lifetime,最长分节生命期,时间为30s~2min)。而处于TIME_WAIT状态时,相应的端口号是正在使用状态,因此,若服务器端先断开连接则无法立即重新运行。

无论是服务器端还是客户端,套接字都会有TIME_WAIT过程。先断开连接的套接字必然会经过TIME_WAIT过程。与服务器端不同,客户端由于每次运行都会动态分配端口号,因此不受TIME_WAIT状态的影响。

TIME_WAIT状态的存在有两个理由:

  • 可靠地实现TCP全双工连接的终止
  • 允许老的重复分节在网络中消逝

第一个理由可以假设上述四次握手过程最终的ACK丢失了来解释。主机B将重新发送它的最终那个FIN,因此主机A必须维护状态信息,以允许它重新发送最终那个ACK。如果主机A不维护状态信息,它将以一个RST(另外一种类型的TCP分节)消息来响应,该分节将被主机B解释为一个错误消息。如果TCP打算执行所有必要的工作以彻底终止某个连接上两个方向的数据流(即全双工关闭),那么它必须正确处理连接终止序列4个分节中任何一个分节丢失的情况。本例也说明了为什么执行主动关闭的那一端需要处于TIME_WAIT状态,因为它可能不得不重传最终那个ACK。

为理解存在TIME_WAIT状态的第二个理由,我们假设在12.106.32.254的1500端口和206.168.112.219的21端口之间有一个TCP连接。我们关闭这个连接,过一段时间后在相同的IP地址和端口之间建立另一个连接。后一个连接称为前一个连接的化身(incarnation),因为它们的IP地址和端口号都相同。TCP必须防止来自某个连接的老的重复分组在该连接已终止后再现,从而被误解成属于同一连接的某个新的化身。为做到这一点,TCP将不给处于TIME_WAIT状态的连接发起新的化身。既然TIME_WAIT状态的持续时间是MSL的2倍,这就足以让某个方向上的分组最多存活MSL秒即被丢弃,另一个方向上的应答最多存活MSL秒也被丢弃。通过实施这个规则,我们就能保证每成功建立一个TCP连接时,来自该连接先前化身的老的重复分组都已在网络中消逝了(单向传输一个分节的最长生命周期是MSL,TIME-WAIT状态的2MSL是考虑了一次双向信息交互的最长时间。比如最后的ACK丢失后,来自对端重发的FIN消息也会在2MSL内消逝)。

地址再分配

从上文的描述来看,TIME_WAIT状态在可靠通信过程中似乎起到了重要的作用,但它也有其自身的缺点。

比如下图的情况,收到FIN消息的主机A发送ACK消息至主机B并启动Time-wait定时器,如果网络状态不好致使ACK消息不断丢失,则主机B重传FIN消息,收到FIN消息的主机A将重启Time-wait定时器,TIME-WAIT状态可能一直持续下去。

在这里插入图片描述

另一种情况,考虑正在工作中的服务器突然故障停机而需要快速重启,这时由于TIME_WAIT状态则必须等几分钟,也会带来严重的影响。

针对以上TIME_WAIT状态所带来的影响,可以通过配置可选项SO_REUSEADDR来解决。默认情况下,SO_REUSEADDR选项处于关闭状态(值为0,假),即无法分配处于TIME_WAIT状态下套接字端口。因此,我们需要将该选项置为1(真)即可。

int opt_val = 1;    
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&opt_val, sizeof(opt_val));

SO_REUSEADDR可选项有效解决了以上问题,UNP中也有这么一句描述:“所有TCP服务器都应该指定本套接字选项,以允许服务器在这种情形下被重新启动”。同时我们也应该意识到SO_REUSEADDR其实无视了TIME_WAIT状态的一些作用,此时如果收到一些不期望的数据(旧连接的分片)可能会导致服务程序混乱,不过这种可能性极低。

TCP_NODELAY

Nagle 算法

Nagle算法的出现是为了防止因数据包过多而导致的网络过载,它应用于TCP层,其作用如下图所示。

在这里插入图片描述

不难看出,只有收到前一数据的ACK消息时,Nagle算法才会发送下一数据。TCP套接字默认使用Nagle算法,因此可以最大限度地进行缓冲,直到收到ACK。

上图的演示中,使用Nagle算法发送一个字符串消息需要传递4个数据包,而不使用Nagle算法则需要传递10个数据包,对网络流量(Traffic,网络负载或混杂程度)产生了较大的影响。当然,上图的演示只是一种极端的情况(特定场景下,字符串中的字符需要间隔一定的时间来传输至缓冲区),实际程序中将字符串传输至缓冲区并非逐个字符进行的。

根据数据传输的特性,网络流量未受太大影响时,不使用Nagle算法反而更快。典型的场景就是“传输大文件数据”,此时即使不使用Nagle算法,也会在填满缓冲区时传输数据。这种情况并没有增加数据包的数量,反而由于无需等待ACK而可以连续传输,大大提高了传输速度。

是否使用Nagle算法,需要根据使用与否对网络流量影响的差别大小确定。通常情况,不使用Nagle算法可以提高传输速度。但为了保证网络流量,在未准确判断数据特性时不应该禁止Nagle算法。

Nagle 算法的优缺点

优点:

  • 减少网络拥塞: 通过减少小包的数量,可以降低网络拥塞,特别是在高延迟网络中。
  • 提高带宽利用率: 通过发送更大的数据包,可以更有效地利用可用带宽。

缺点:

  • 增加交互式应用的延迟: 对于需要快速响应的交互式应用(如远程登录或在线游戏),Nagle算法可能会导致感知上的延迟,因为它延迟了小数据包的发送。
  • 与某些应用协议不兼容: 一些应用协议依赖于小数据包的快速传输,Nagle算法可能会干扰这些协议的正常工作。

禁用 Nagle 算法

如果有必要,就应禁用Nagle算法。

Nagle 算法使用与否在网络流量上差别不大。若使用了Nagle 算法,传输速度更慢。

禁止Nagle算法的方法就是将套接字可选项TCP_NODELAY改为1(真):

int opt_val = 1;    
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *)&opt_val, sizeof(opt_val));

也可以查看Nagle算法的设置状态:

int opt_val;
socklen_t opt_len = sizeof(opt_val);  
getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *)&opt_val, &opt_len);

如果正在使用Nagle算法,opt_val = 0;否则为 1。

基于 Windows 的实现

套接字可选项及其相关内容与操作系统无关。

#include <winsock2.h>int getsockopt(SOCKET sock, int level, int optname, char *optval, int *optlen);
int setsockopt(SOCKET sock, int level, int optname, const char *optval, int optlen);

成功时返回0,失败时返回 SOCKET_ERROR。

基于 Windows 的套接字类型示例程序

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>void ErrorHanding(char *message);int main(int argc, char *argv[])
{WSADATA wsaData;SOCKET tcp_sock, udp_sock;int sock_type;int opt_len;int state;if (argc != 2){printf("Usage : %s <addr>\n", argv[0]);exit(1);}if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)ErrorHanding("WSAStartup() error!");tcp_sock = socket(PF_INET, SOCK_STREAM, 0);udp_sock = socket(PF_INET, SOCK_DGRAM, 0);printf("SOCK_STREAM: %d \n", SOCK_STREAM);printf("SOCK_DGRAM: %d \n", SOCK_DGRAM);opt_len = sizeof(sock_type);state = getsockopt(tcp_sock, SOL_SOCKET, SO_TYPE, (void *)&sock_type, &opt_len);if (state)ErrorHanding("getsockopt() error!");printf("Socket type one: %d \n", sock_type);state = getsockopt(udp_sock, SOL_SOCKET, SO_TYPE, (void *)&sock_type, &opt_len);if (state)ErrorHanding("getsockopt() error!");printf("Socket type two: %d \n", sock_type);closesocket(tcp_sock);closesocket(udp_sock);WSACleanup();return 0;
}void ErrorHanding(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}

编译:

gcc sock_type_win.c -lwsock32 -o sockTypeWin

运行结果:

C:\Users\81228\Documents\Program\TCP IP Project\Chapter 9>sockTypeWin
Usage : sockTypeWin <addr>C:\Users\81228\Documents\Program\TCP IP Project\Chapter 9>sockTypeWin 9190
SOCK_STREAM: 1
SOCK_DGRAM: 2
Socket type one: 1
Socket type two: 2

基于 Windows 的套接字 I/O 缓冲大小示例程序

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>void ErrorHanding(char *message);
void ShowSocketBufSize(SOCKET sock);int main(int argc, char *argv[])
{WSADATA wsaData;SOCKET sock;int sndBuf, rcvBuf, state;if (argc != 2){printf("Usage : %s <addr>\n", argv[0]);exit(1);}if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)ErrorHanding("WSAStartup() error!");sock = socket(PF_INET, SOCK_STREAM, 0);ShowSocketBufSize(sock);sndBuf = rcvBuf = 3 * 1024;state = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&sndBuf, sizeof(sndBuf));if (state == SOCKET_ERROR)ErrorHanding("setsockopt() error!");state = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&rcvBuf, sizeof(rcvBuf));if (state == SOCKET_ERROR)ErrorHanding("setsockopt() error!");ShowSocketBufSize(sock);closesocket(sock);WSACleanup();return 0;
}void ShowSocketBufSize(SOCKET sock)
{int sndBuf, rcvBuf, state, len;len = sizeof(sndBuf);state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&sndBuf, &len);if (state == SOCKET_ERROR)ErrorHanding("getsockopt() error!");len = sizeof(rcvBuf);state = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&rcvBuf, &len);if (state == SOCKET_ERROR)ErrorHanding("getsockopt() error!");printf("Input buffer size: %d\n", rcvBuf);printf("Output buffer size: %d\n", sndBuf);
}void ErrorHanding(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}

编译:

gcc buf_win.c -lwsock32 -o bufWin

运行结果:

C:\Users\81228\Documents\Program\TCP IP Project\Chapter 9>bufWin 9190
Input buffer size: 65536
Output buffer size: 65536
Input buffer size: 3072
Output buffer size: 3072

基于 Windows 的 TCP_NODELAY 示例程序

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>void ErrorHanding(char *message);int main(int argc, char *argv[])
{WSADATA wsaData;SOCKET sock;int opt_val;int opt_len;int state;if (argc != 2){printf("Usage : %s <addr>\n", argv[0]);exit(1);}if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)ErrorHanding("WSAStartup() error!");sock = socket(PF_INET, SOCK_STREAM, 0);opt_len = sizeof(opt_val);state = getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&opt_val, &opt_len);if (state)ErrorHanding("getsockopt() error!");printf("TCP_NODELAY value: %d \n", opt_val);opt_val = 0;opt_len = sizeof(opt_val);state = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&opt_val, opt_len);if (state)ErrorHanding("setsockopt() error!");opt_len = sizeof(opt_val);state = getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&opt_val, &opt_len);if (state)ErrorHanding("getsockopt() error!");printf("TCP_NODELAY value: %d \n", opt_val);closesocket(sock);WSACleanup();return 0;
}void ErrorHanding(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}

编译:

gcc tcp_nodelay_win.c -lwsock32 -o nagleWin

运行结果:

C:\Users\81228\Documents\Program\TCP IP Project\Chapter 9>nagleWin 9190
TCP_NODELAY value: 6356992
TCP_NODELAY value: 0

习题

(1)下列关于Time-wait状态的说法错误的是?

a. Time-wait状态只在服务器端的套接字中发生。
b. 断开连接的四次握手过程中,先传输FIN消息的套接字将进入Time-wait状态。
c. Time-wait状态与断开连接的过程无关,而与请求连接过程中SYN消息的传输顺序有关。
d. Time-wait状态通常并非必要,应尽可能通过更改套接字可选项防止其发生。

答:a、c、d。

(2)TCP_NODELAY可选项与Nagle算法有关,可通过它禁用Nagle算法。请问何时应考虑禁用Nagle算法?结合收发数据的特性给出说明。

根据数据传输的特性,网络流量未受太大影响时,可以考虑禁用Nagle算法。

典型的场景就是“传输大文件数据”,此时即使不使用Nagle算法,也会在填满缓冲区时传输数据。这种情况并没有增加数据包的数量,反而由于无需等待ACK而可以连续传输,大大提高了传输速度。

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

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

相关文章

D66【python 接口自动化学习】- python基础之数据库

day66 SQL-DQL-排序分页 学习日期&#xff1a;20241112 学习目标&#xff1a;MySQL数据库-- 135 SQL-DQL-排序分页 学习笔记&#xff1a; 结果查询 结果分页限制 总结 排序和分页限制的语法 2. 关键字总结&#xff1a;

Groovy有哪些官方文档和教程可以学习?

Groovy Language Documentation&#xff1a; 这是Groovy的官方文档&#xff0c;提供了详细的语言规范、教程和指南。您可以从这里下载JavaDoc和在线文档&#xff0c;以及获取关于如何开始使用Groovy的指导。[Groovy Language Documentation][1] Groovy官方教程&#xff1a; 官…

LeetCode 3.无重复字符的最长子串

LeetCode 3.无重复字符的最长子串 思路&#x1f9d0;&#xff1a; 使用滑动窗口哈希表&#xff0c;哈希表映射每一个字符串&#xff0c;左右指针表示当前区间&#xff0c;当出现一个字符串那么就将哈希表1&#xff0c;右指针移动&#xff0c;当哈希表对应位置大于1时&#xff0…

验证码处理在自动化测试中的应用

在进行自动化测试时&#xff0c;处理验证码是一项常见的挑战&#xff0c;特别是图形验证码。每次刷新都会生成新的验证码&#xff0c;因此我们可以采用以下两种方法来获取验证码&#xff1a; 获取验证码图片链接&#xff1a;例如 src"http://example.com/getcaptcha/123&q…

云计算在智能交通系统中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 云计算在智能交通系统中的应用 云计算在智能交通系统中的应用 云计算在智能交通系统中的应用 引言 云计算概述 定义与原理 发展历…

全球碳循环数据集(2000-2023)包括总初级生产力、生态系统净碳交换和生态系统呼吸变量

全球碳循环数据集&#xff08;2000-2023&#xff09; 数据介绍 PFTs_XGB FLUX 是一个基于 XGBOOST 机器学习模型的全球碳循环数据集。该数据集通过对全球植被功能类型&#xff08;PFTs&#xff09;的分类&#xff0c;结合了 FLUXNET、AmeriFlux 和 ICOS 通量站点的现场观测数据…

Kafka经典面试题

1、kafka消息发送的流程&#xff1f; producer发送过程中启动两个线程 一个main线程 一个sender线程&#xff0c;在main线程中先创建一个双端队列&#xff08;RecordAccumlator、producerbatch&#xff09;&#xff0c;main将我们需要发送的东西经过拦截器&#xff0c;序列化&a…

HTB:Photobomb[WriteUP]

目录 连接至HTB服务器并启动靶机 使用nmap对靶机进行端口开放扫描 再次使用nmap对靶机开放端口进行脚本、服务扫描 使用ffuf进行简单的子域名扫描 使用浏览器直接访问该域名 选取一个照片进行下载&#xff0c;使用Yakit进行抓包 USER_FLAG&#xff1a;a9afd9220ae2b5731…

蓝桥杯c++算法学习【1】之枚举与模拟(卡片、回文日期、赢球票、既约分数:::非常典型的比刷例题!!!)

别忘了请点个赞收藏关注支持一下博主喵&#xff01;&#xff01;&#xff01; 关注博主&#xff0c;更多蓝桥杯nice题目静待更新:) 枚举与模拟 一、卡片&#xff1a; 【问题描述】 小蓝有很多数字卡片&#xff0c;每张卡片上都是一个数字&#xff08;0到9&#xff09;。 小蓝…

html页面中的内容替换,或改变某些字的颜色

1. html页面中的内容替换,或改变某些字的颜色 前端使用innnerHtml显示返回值+innerHtml文字显示两种不同的颜色(两部分显示颜色不一样) 1.1. html字符串改变某些字的颜色 需求:要文本框的字体部分改变颜色、大小、换行等功能。在了解需求后,文本框input发现完全满足不了现需…

Linux——简单认识vim、gcc以及make/Makefile

前言&#xff1a;大佬写博客给别人看&#xff0c;菜鸟写博客给自己看&#xff0c;我是菜鸟。 1、vim操作&#xff1a; 默认打开vim时&#xff0c;vim处于命令模式。(在其他模式中&#xff0c;Esc就能够返回命令模式) 常用的命令有&#xff1a; n gg&#xff1a;跳转到n行&…

C++基础(12.红黑树实现)

目录 红黑树的概念&#xff1a; 红黑树规则&#xff1a; 红黑树如何确保最长路径不超过最短路径的2倍的&#xff1f; 红黑树的效率&#xff1a; 红黑树的插入: 红黑树树插入⼀个值的大概过程: 情况1&#xff1a;变色 情况2&#xff1a;单旋变色&#xff1a; 情况3&…

Spark 的容错机制:保障数据处理的稳定性与高效性

Spark 的介绍与搭建&#xff1a;从理论到实践_spark环境搭建-CSDN博客 Spark 的Standalone集群环境安装与测试-CSDN博客 PySpark 本地开发环境搭建与实践-CSDN博客 Spark 程序开发与提交&#xff1a;本地与集群模式全解析-CSDN博客 Spark on YARN&#xff1a;Spark集群模式…

Linux内核编程(二十)RTC子系统一驱动rx8010

本文目录 一、基础知识点1.什么是RTC&#xff1f;2. RTC方案3. 电路原理图 二、RTC芯片&#xff08;RX8010&#xff09;移植三、关于时间的一些命令四、应用层使用1. 使用RTC驱动2. 使用time.h库&#xff08;额外知识点&#xff09; 一、基础知识点 1.什么是RTC&#xff1f; R…

GESP4级考试语法知识(贪心算法(一))

海盗船代码&#xff1a; #include<iostream> #include<algorithm> using namespace std; int data[21]; int main() {int n;cin>>n;for(int i0;i<n;i)cin>>data[i];sort(data,datan);int temp0,sum0;for(int i0;i<n;i){tempdata[i];if(temp>…

036 RabbitMQ消息确认 死信队列 延时队列

文章目录 生产者确认模式application.propertiesMessageController.javaMessageConfirmRallback.java 生产者回退模式application.propertiesMessageConfirmRallback.javaMessageController.java 消费者手动确认application.propertiesConsumerAckQueueListener.java 死信队列延…

Unity 插件 - Project窗口资源大小显示

Unity 插件 - Project窗口资源大小显示 &#x1f354;功能&#x1f32d;安装 &#x1f354;功能 &#x1f4a1;.显示Project Assets 和Packages下所有文件的大小&#xff08;右侧显示&#xff09; &#x1f4a1;.统计选中文件夹及其子文件夹下所有文件的大小并显示&#xff08…

Maven 中央仓库地址 mvnrepository.com

下载一些 jar 包驱动&#xff0c;不需用去官网下了&#xff0c;直接去 Maven 中央仓库&#xff0c;高效、简单 Maven 中央仓库地址 https://mvnrepository.com/open-source 我们下期见&#xff0c;拜拜&#xff01;

制作自己的刷题小题库,提高刷题效率

日常刷题 乱序/背题多种模式 组队刷题 查看小组的刷题统计 在线考试 创建考试多人同时答题 ----这是一条分割线----- 土著刷题&#xff0c;是一款可以导入题库的在线刷题学习小&#x1f34a;序&#xff0c;提供一套以【搭建题库-组建小组-刷题练习-在线考试】为中心的完整服务…

WPF在MVVM模式下怎么实现导航功能

在mvvm的模式下wpf通过frame实现页面跳转_哔哩哔哩_bilibili 视频讲解同步可观看 如下图&#xff0c;我们要实现点击左侧的菜单&#xff0c;在右侧展示不同的页面 实现代码如下&#xff1a; 一、如何从主窗体跳转到页面。 1、在mainwindow.xaml的菜单栏代码里加入如下代码 …