【USTC 计算机网络】第二章:应用层 - TCP UDP 套接字编程

ops/2025/3/26 2:12:19/

本文详细介绍了 TCP 与 UDP 套接字编程,并在 Windows 下使用 C++ 实现套接字编程,对代码做了十分精细的讲解,这部分内容非常重要,是计算机网络学到目前为止第一次编程,也是网络编程开发中最基础的一个部分,必须彻底掌握。

1. Windows 使用 C++ 实现 TCP Socket

在 Windows 下进行套接字编程需要遵循如下步骤:

  1. 初始化 Winsock 库:使用 WSAStartup 初始化 Winsock 库。该函数需要指定所使用的 Winsock 版本(通常使用 2.2 版本)。
  2. 创建套接字:使用 socket() 函数创建一个 TCP 套接字,参数指定协议族(AF_INET)、套接字类型(SOCK_STREAM)以及协议(IPPROTO_TCP)。
  3. 绑定地址和端口(针对服务器端):服务器端需要使用 bind() 函数绑定本地 IP 地址和端口号,以便客户端可以连接到该地址。
  4. 监听连接(服务器端):通过 listen() 函数使套接字进入监听状态,等待客户端的连接请求。
  5. 接受连接(服务器端):使用 accept() 函数接受客户端的连接请求,并获得一个新的套接字用于与客户端通信。
  6. 连接服务器(客户端):客户端使用 connect() 函数连接到服务器的 IP 地址和端口号。
  7. 数据传输:服务器和客户端可以通过 send()recv() 函数进行数据的发送和接收。
  8. 关闭套接字和清理环境:通信完成后,使用 closesocket() 关闭套接字,并调用 WSACleanup() 清理 Winsock 资源。

我们以 CLion 编译器为例,创建一个套接字编程的项目 TCP&UDP_Socket,然后创建好 tcp_server.cpptcp_client.cpp

首先我们需要配置一下 CMakeLists.txt 文件,指定我们的两个可执行文件,然后需要链接 Winsock 库:

cmake_minimum_required(VERSION 3.28)
project(TCP&UDP_Socket)set(CMAKE_CXX_STANDARD 20)add_executable(TCPServer tcp_server.cpp)
add_executable(TCPClient tcp_client.cpp)target_link_libraries(TCPServer ws2_32)
target_link_libraries(TCPClient ws2_32)

CMake 是一个跨平台的自动化构建工具,它帮助开发者通过编写简单的配置脚本来生成适用于不同平台和编译器的构建文件,从而简化软件项目的构建、测试和打包过程。

CMakeLists.txt 是 CMake 的核心配置文件,存储了项目的构建规则和配置信息。这个文件中可以包含:

  • 项目名称和版本:使用 project() 命令定义项目名称、版本信息等。
  • CMake 最低版本要求:使用 cmake_minimum_required() 命令指定最低 CMake 版本。
  • 添加目标:通过 add_executable()add_library() 等命令定义可执行文件或库。
  • 依赖管理与链接:使用 target_link_libraries() 指定目标之间的依赖关系和链接库。

配置完就可以实现服务器与客户端的代码了,首先是服务器 tcp_server.cpp

#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>#pragma comment(lib, "ws2_32.lib")  // 显式链接 Winsock 库,在编译时自动将 ws2_32.lib 添加到链接器的输入中const int PORT = 8080;  // 监听端口
const int BUFFER_SIZE = 1024;  // 接收消息时的缓冲区大小int main() {// 1. 初始化 WinsockWSADATA wsaData;if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {std::cerr << "WSAStartup failed." << std::endl;return 1;}// 2. 创建服务器套接字SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (serverSocket == INVALID_SOCKET) {std::cerr << "Socket creation failed: " << WSAGetLastError() << std::endl;WSACleanup();return 1;}// 3. 配置服务器地址和端口sockaddr_in serverAddr{};  // 表示 IPv4 地址信息的结构体,{} 利用 C++11 的列表初始化将所有成员初始化为零serverAddr.sin_family = AF_INET;  // 使用 IPv4 协议serverAddr.sin_addr.s_addr = INADDR_ANY;  // 监听所有本地 IPserverAddr.sin_port = htons(PORT);  // 将端口转换为网络字节序// 4. 将地址和端口与服务器套接字绑定if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {std::cerr << "Bind failed: " << WSAGetLastError() << std::endl;closesocket(serverSocket);WSACleanup();return 1;}// 5. 开始监听if (listen(serverSocket, SOMAXCONN) == SOCKET_ERROR) {std::cerr << "Listen failed: " << WSAGetLastError() << std::endl;closesocket(serverSocket);WSACleanup();return 1;}std::cout << "TCP Server listening on port " << PORT << std::endl;// 6. 接受客户端连接sockaddr_in clientAddr{};int clientAddrLen = sizeof(clientAddr);SOCKET clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &clientAddrLen);if (clientSocket == INVALID_SOCKET) {std::cerr << "Accept failed: " << WSAGetLastError() << std::endl;closesocket(serverSocket);WSACleanup();return 1;}std::cout << "Client connected!" << std::endl;// 8. 与客户端通信char buffer[BUFFER_SIZE];while (true) {// 接收数据int bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE, 0);if (bytesReceived > 0) {buffer[bytesReceived] = '\0';  // 确保字符串终止std::cout << "Received: " << buffer << std::endl;// 发送响应const char* response = "Hello Client!";send(clientSocket, response, static_cast<int>(strlen(response)), 0);std::cout << "Sent: " << response << std::endl;} else if (bytesReceived == 0) {std::cout << "Client disconnected." << std::endl;break;} else {std::cerr << "Receive failed: " << WSAGetLastError() << std::endl;break;}}// 9. 清理资源closesocket(clientSocket);closesocket(serverSocket);WSACleanup();return 0;
}

然后是客户端 tcp_client.cpp

#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>#pragma comment(lib, "ws2_32.lib")const char* SERVER_IP = "127.0.0.1";  // 本地回环地址
const int PORT = 8080;  // 服务器端口
const int BUFFER_SIZE = 1024;  // 接收消息时的缓冲区大小int main() {// 1. 初始化 WinsockWSADATA wsaData;if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {std::cerr << "WSAStartup failed." << std::endl;return 1;}// 2. 创建客户端套接字SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (clientSocket == INVALID_SOCKET) {std::cerr << "Socket creation failed: " << WSAGetLastError() << std::endl;WSACleanup();return 1;}// 3. 配置服务器地址和端口sockaddr_in serverAddr{};serverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(PORT);inet_pton(AF_INET, SERVER_IP, &serverAddr.sin_addr);  // 转换 IP 地址为二进制格式// 7. 连接到服务器if (connect(clientSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {std::cerr << "Connect failed: " << WSAGetLastError() << std::endl;closesocket(clientSocket);WSACleanup();return 1;}std::cout << "Connected to server!" << std::endl;// 8. 与服务器通信const char* message = "Hello Server!";send(clientSocket, message, static_cast<int>(strlen(message)), 0);std::cout << "Sent: " << message << std::endl;char buffer[BUFFER_SIZE];int bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE, 0);if (bytesReceived > 0) {buffer[bytesReceived] = '\0';std::cout << "Received: " << buffer << std::endl;} else {std::cerr << "Receive failed: " << WSAGetLastError() << std::endl;}// 9. 清理资源closesocket(clientSocket);WSACleanup();return 0;
}

下面对代码的每一步进行详细的讲解(顺序编号与代码中一致,按顺序逐步理解):

  1. 初始化 Winsock(服务器与客户端)
    • WSAStartup() 是 Winsock API 的初始化函数,如果初始化成功,则返回值为 0;如果失败,返回非零错误代码,表明具体的错误原因​。初始化成功后指针 &wsaData 会接收 Winsock 初始化后返回的一些信息,例如 Winsock 的版本、描述信息等。
    • MAKEWORD(2, 2) 是一个用于生成一个 16 位版本号的宏。其中低字节代表主版本号(major version),高字节代表次版本号(minor version)。MAKEWORD(2, 2) 表示请求使用 Winsock 的 2.2 版本。这也是当前广泛使用的版本,提供了比较完善的功能和兼容性​。
  2. 创建套接字(服务器与客户端)
    • SOCKET 数据类型是在 Windows 平台上定义的一个数据类型,位于头文件 <winsock2.h> 中。它是一个抽象的套接字句柄,用于标识和操作一个网络连接。它在不同的平台上内部可能是一个无符号整数或指针类型,但在 Windows 下由 Winsock API 统一管理。
    • AF_INET 表示使用 IPv4 地址族(Address Family IPv4),用于网络通信时使用 32 位地址;SOCK_STREAM 指定套接字类型为流式套接字,适用于基于 TCP 的可靠数据传输(TCP 中数据是作为连续的字节流进行传输);IPPROTO_TCP 明确指定使用 TCP 协议。这三个宏都在 <winsock2.h>(以及相关的 <ws2tcpip.h>)中定义,保证在 Windows 下使用 Winsock API 时具有正确的常量值。
    • socket() 函数会创建一个新的套接字,用于网络通信。函数返回一个 SOCKET 类型的值,表示创建成功的套接字句柄,之后可以利用这个句柄进行绑定、监听、连接、数据收发等操作。如果返回值为 INVALID_SOCKET,则说明创建套接字失败,需调用 WSAGetLastError() 获取错误码​。
  3. 配置服务器地址和端口(服务器与客户端)
    • sockaddr_in 是用于表示 IPv4 地址信息的结构体,定义在头文件 <winsock2.h><ws2tcpip.h> 中。它包含了网络地址族 sin_familyshort 类型)、端口号 sin_portu_short 类型)以及 IP 地址 sin_addr 等属性,还可能包含填充字节 sin_zero 以确保结构体大小与通用地址结构 sockaddr 一致。
    • sockaddr_in 中的 sin_addr 属性也是一个结构体,其中 s_addr 成员保存 32 位的 IP 地址。INADDR_ANY 表示任何本地 IP 地址,即绑定时使用主机上的所有网络接口。这样服务器程序就能接收来自任意网络接口的连接请求。
    • 网络传输使用的是网络字节序(大端序),而主机通常使用本机字节序(可能是小端序),htons()(Host TO Network Short)函数用于将主机字节序的 16 位整数转换为网络字节序。这里将常量 PORT 转换后赋值给 sin_port,保证端口号在网络传输时能正确解释。
    • 客户端:inet_pton(Internet Presentation to Network)函数将以文本(Presentation)形式表示的 IP 地址转换为网络(Network)字节序的二进制形式,这个格式适用于网络通信函数使用。
  4. 将地址和端口与服务器套接字绑定(服务器)
    • bind() 函数用于将一个套接字和一个本地地址(包括 IP 地址和端口)绑定在一起。这是服务器端设置套接字的一个必要步骤,使得套接字知道在哪个地址和端口上等待客户端连接。绑定后,套接字就与该地址关联,可以通过这个地址对外通信。如果 bind() 执行成功,返回值为 0,如果绑定失败,则返回 SOCKET_ERROR(通常为 -1),并且可以调用 WSAGetLastError() 函数获取具体的错误代码,从而了解失败的原因。
    • serverAddr(类型为 sockaddr_in)的地址转换为通用地址类型 sockaddr*。这是因为 bind() 函数要求传入的地址参数为 sockaddr* 类型。
  5. 开始监听(服务器)
    • 调用 listen() 函数后,套接字将变为“被动”模式,即不再主动发起连接,而是等待来自客户端的连接请求。函数返回 0 表示成功,套接字已成功进入监听状态,如果失败,则返回 SOCKET_ERROR
    • SOMAXCONN 是一个预定义的常量,定义在 <winsock2.h> 头文件中,不同平台可能对其具体数值有所不同,表示允许在套接字的挂起连接队列中的最大连接请求数。使用 SOMAXCONN 能让系统选择一个合理的最大值,从而简化开发者的配置。
  6. 接受客户端连接(服务器)
    • 首先定义了一个 IPv4 地址结构体 clientAddr,用于存储连接过来的客户端的 IP 地址和端口号。
    • accept() 函数用于从处于监听状态的套接字 serverSocket 的挂起连接队列中提取出一个连接请求,并为该连接创建一个新的套接字。当有客户端连接时,accept() 会填充 clientAddr 中的客户端地址信息,并通过 clientAddrLen 告知地址结构的实际大小。accept() 阻塞调用会一直等待直到有客户端发起连接,然后返回一个新的、用于后续数据通信的套接字。
  7. 连接到服务器(客户端)
    • connect() 函数用于建立客户端与服务器之间的连接。客户端通过该函数发起连接请求,并与服务器建立通信通道。调用此函数后,如果连接成功,则客户端的套接字将与服务器端对应的套接字建立起连接,从而可以进行数据传输。如果连接成功,connect() 返回 0,否则返回 SOCKET_ERROR
  8. 发送数据并接收响应(服务器与客户端)
    • send() 函数用于向已连接的套接字发送数据,其中变量 message 是一个指向数据缓冲区的指针,该缓冲区中存储了要发送的数据,标志参数设为 0 表示默认的发送方式,不使用特殊的发送选项。函数返回值为成功发送的字节数,即实际发送出去的数据长度,如果发送失败,则返回 SOCKET_ERROR
    • recv() 函数用于从一个已经连接的套接字中接收数据,其中变量 buffer 存储接收到数据的字符数组(缓冲区),BUFFER_SIZE 表示缓冲区的大小,指定最多接收多少字节的数据,标志参数设为 0 表示不使用特殊标志,即默认接收方式。函数返回值为实际接收到的字节数,如果返回 0,表示对方已经正常关闭连接,如果返回 SOCKET_ERROR 表示出现错误。
  9. 清理资源
    • 使用 closesocket() 函数关闭套接字,释放与该套接字相关的系统资源,确保不再使用这个网络连接。使用 WSACleanup() 函数终止 Winsock 库的使用,释放在调用 WSAStartup() 时分配的资源。

2. Windows 使用 C++ 实现 UDP Socket

UDP Socket 无需握手建立连接,服务器只需要与本地的 IP 和端口进行绑定,发送端在每一个发送的报文中都需要明确地指定目标的 IP 地址和端口号,服务器必须从收到的分组中提取出发送端的 IP 地址和端口号才能响应发送端。

我们先创建好 udp_server.cppudp_client.cpp,然后修改一下 CMake 配置文件:

cmake_minimum_required(VERSION 3.28)
project(TCP&UDP_Socket)set(CMAKE_CXX_STANDARD 20)add_executable(TCPServer tcp_server.cpp)
add_executable(TCPClient tcp_client.cpp)
add_executable(UDPServer udp_server.cpp)
add_executable(UDPClient udp_client.cpp)target_link_libraries(TCPServer ws2_32)
target_link_libraries(TCPClient ws2_32)
target_link_libraries(UDPServer ws2_32)
target_link_libraries(UDPClient ws2_32)

UDP Socket 的实现与 TCP 基本一样,先上代码,首先是服务器 udp_server.cpp

#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>#pragma comment(lib, "ws2_32.lib")const int PORT = 8888;
const int BUFFER_SIZE = 1024;int main() {// 1. 初始化 WinsockWSADATA wsaData;if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {std::cerr << "WSAStartup failed." << std::endl;return 1;}// 2. 创建服务器套接字SOCKET serverSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if (serverSocket == INVALID_SOCKET) {std::cerr << "Socket creation failed: " << WSAGetLastError() << std::endl;WSACleanup();return 1;}// 3. 配置服务器地址和端口sockaddr_in serverAddr{};serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = INADDR_ANY;serverAddr.sin_port = htons(PORT);// 4. 将地址和端口与服务器套接字绑定if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {std::cerr << "Bind failed: " << WSAGetLastError() << std::endl;closesocket(serverSocket);WSACleanup();return 1;}std::cout << "UDP Server listening on port " << PORT << std::endl;// 5. 接收和发送数据sockaddr_in clientAddr{};int clientAddrLen = sizeof(clientAddr);char buffer[BUFFER_SIZE];while (true) {// 接收数据int bytesReceived = recvfrom(serverSocket, buffer, BUFFER_SIZE, 0, (sockaddr*)&clientAddr, &clientAddrLen);if (bytesReceived > 0) {// 打印客户端信息及内容int clientPort = ntohs(clientAddr.sin_port);char clientIP[INET_ADDRSTRLEN];inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, INET_ADDRSTRLEN);buffer[bytesReceived] = '\0';  // 确保字符串终止std::cout << "Received from " << clientIP << ":" << clientPort << ": " << buffer << std::endl;// 发送响应const char* response = "Hello Client!";int sendResult = sendto(serverSocket, response, static_cast<int>(strlen(response)), 0, (sockaddr*)&clientAddr, clientAddrLen);if (sendResult == SOCKET_ERROR) {std::cerr << "Send failed: " << WSAGetLastError() << std::endl;} else {std::cout << "Sent to " << clientIP << ":" << clientPort << ": " << response << std::endl;}} else {std::cerr << "Receive failed: " << WSAGetLastError() << std::endl;break;}}// 6. 清理资源closesocket(serverSocket);WSACleanup();return 0;
}

然后是客户端 udp_client.cpp

#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>#pragma comment(lib, "ws2_32.lib")const char* SERVER_IP = "127.0.0.1";  // 服务器 IP
const int PORT = 8888;  // 服务器端口
const int BUFFER_SIZE = 1024;int main() {// 1. 初始化 WinsockWSADATA wsaData;if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {std::cerr << "WSAStartup failed." << std::endl;return 1;}// 2. 创建客户端套接字SOCKET clientSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if (clientSocket == INVALID_SOCKET) {std::cerr << "Socket creation failed: " << WSAGetLastError() << std::endl;WSACleanup();return 1;}// 3. 配置服务器地址和端口sockaddr_in serverAddr{};serverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(PORT);inet_pton(AF_INET, SERVER_IP, &serverAddr.sin_addr);// 5. 发送数据const char* message = "Hello Server!";int sendResult = sendto(clientSocket, message, static_cast<int>(strlen(message)), 0, (sockaddr*)&serverAddr, sizeof(serverAddr));if (sendResult == SOCKET_ERROR) {std::cerr << "Send failed: " << WSAGetLastError() << std::endl;closesocket(clientSocket);WSACleanup();return 1;}std::cout << "Sent: " << message << std::endl;// 5. 接收数据char buffer[BUFFER_SIZE];sockaddr_in fromAddr{};int fromAddrLen = sizeof(fromAddr);int bytesreceived = recvfrom(clientSocket, buffer, BUFFER_SIZE, 0, (sockaddr*)&fromAddr, &fromAddrLen);if (bytesreceived > 0) {buffer[bytesreceived] = '\0';std::cout << "Received: " << buffer << std::endl;} else {std::cerr << "Receive failed: " << WSAGetLastError() << std::endl;}// 6. 清理资源closesocket(clientSocket);WSACleanup();return 0;
}

现在来看一下 UDP Socket 与 TCP Socket 不同的几个地方。

首先是使用 socket() 创建 Socket 时,UDP 使用 SOCK_DGRAM 表示创建一个数据报套接字,这种套接字是面向消息的,不提供数据流的可靠传输,而是用于发送独立的数据包。这正好符合 UDP 的特点(无连接、不保证可靠性)。IPPROTO_UDP 明确指定使用 UDP 协议。这样,系统就知道这个套接字将基于 UDP 进行数据传输。

其次是 UDP 中服务器没有监听与接受客户端连接的过程,同理客户端没有连接到服务器的过程。服务器与客户端直接使用 sendto()recvfrom() 函数进行通信,sendto()send() 相比的区别在于每次发送都需要指定接收端的地址信息,同样 recvfrom() 也需要接收发送端的地址信息,可以使用 ntohs()(Network TO Host Short)函数将 16 位数值的端口号从网络字节序转换为主机字节序,使用 inet_ntop() 函数将网络字节序中的 IP 地址(二进制形式)转换为文本(字符串)格式。

由于 UDP 中不存在客户端结束连接这个操作,因此服务器的 UDP Socket 在接收没有出问题的情况下会不断等待消息,多启动几次客户端代码就可以看到服务器产生了多次通信:

UDP Server listening on port 8888
Received from 127.0.0.1:52045: Hello Server!
Sent to 127.0.0.1:52045: Hello Client!
Received from 127.0.0.1:53888: Hello Server!
Sent to 127.0.0.1:53888: Hello Client!
...

通过上面的例子,能够发现在 TCP 和 UDP 套接字编程中,客户端通常不需要显式绑定地址和端口,客户端默认会使用本地主机的所有可用网络接口,即 INADDR_ANY,操作系统自动分配一个临时端口(Ephemeral Port),范围通常为 1024~65535,TCP 客户端在调用 connect() 函数时系统会为套接字分配本地 IP(根据路由选择最佳接口),并分配一个随机临时端口;UDP 客户端在第一次发送或接收数据时完成端口分配。

我们所使用的 127.0.0.1 为本地回环地址,我们也可以使用 ipconfig 命令(Linux 为 ifconfig)查看我们本机的 IPv4 地址,这是一个局域网地址,通常分配给某个网络接口,这个接口与本地回环接口不一样。

客户端在配置服务器地址时可以试试改成我们本机的局域网地址:

inet_pton(AF_INET, "192.168.82.14", &serverAddr.sin_addr);

这时候运行客户端会看到服务器那边接收到的客户端地址改变了:

Received from 192.168.82.14:63857: Hello Server!
Sent to 192.168.82.14:63857: Hello Client!

因为目标地址是一个局域网地址,因此客户端也自动使用我们本机的局域网接口发送消息,服务器端我们设置了监听所有本地 IP,所以包括本地回环地址与本地 IPv4 地址。

同样我们也可以在客户端自己绑定 IP 与端口号:

sockaddr_in clientAddr{};
clientAddr.sin_family = AF_INET;
clientAddr.sin_port = htons(12345);  // 指定固定端口
clientAddr.sin_addr.s_addr = inet_addr("192.168.82.14");  // 指定从局域网 IP 发出消息
if (bind(clientSocket, (sockaddr*)&clientAddr, sizeof(clientAddr)) == SOCKET_ERROR) {std::cerr << "Bind failed: " << WSAGetLastError() << std::endl;closesocket(clientSocket);WSACleanup();return 1;
}

这样启动客户端就能看到服务器的输出:

Received from 192.168.82.14:12345: Hello Server!
Sent to 192.168.82.14:12345: Hello Client!

http://www.ppmy.cn/ops/168002.html

相关文章

遇到一个奇怪问题,页面请求不到后端

背景 页面有两个请求,第一个接口获取令牌,第二个接口根据令牌去获取数据, 突然发现获取数据接口校验令牌的时候一直报错 而且报错的时候服务器没有获取令牌请求 而且发现偶尔是正常的,正常的发现服务器ip和异常的不一样,同事定位可能是域名解析问题 解决 最后定位是腾讯cdn解…

Rust 生命周期

Rust 生命周期 概述 Rust 语言以其内存安全性、并发和性能著称,其生命周期(Lifetimes)是其核心特性之一。生命周期机制允许 Rust 在编译时确保内存安全,同时提供极大的灵活性。本文将深入探讨 Rust 生命周期的概念、用法以及在实际编程中的应用。 生命周期概念 在 Rust…

JAVA泛型的作用

‌1. 类型安全&#xff08;Type Safety&#xff09;‌ 在泛型出现之前&#xff0c;集合类&#xff08;如 ArrayList、HashMap&#xff09;只能存储 Object 类型元素&#xff0c;导致以下问题&#xff1a; ‌问题‌&#xff1a;从集合中取出元素时&#xff0c;需手动强制类型转…

【负载均衡系列】Nginx

1. 工作原理 ​事件驱动模型: 基于异步非阻塞 I/O(如 Linux 的 epoll、BSD 的 kqueue),高效处理高并发连接。单线程可处理数千并发请求,避免传统多线程模型的资源竞争问题。​多进程架构: ​主进程(Master)​:管理配置加载、热升级、工作进程启停。​工作进程(Worker…

2维压缩感知SL0重构实现算法

压缩感知&#xff1a;2维压缩感知SL0重构算法&#xff0c;涉及两个测量矩阵&#xff0c;两个方向进行。 列表 SL0_2D_2/Lena.bmp , 66616 SL0_2D_2/SL0_2D.m , 778 SL0_2D_2/SL0_2D_Test.m , 601

【云上CPU玩转AIGC】——腾讯云高性能应用服务HAI已支持DeepSeek-R1模型预装环境和CPU算力

&#x1f3bc;个人主页&#xff1a;【Y小夜】 &#x1f60e;作者简介&#xff1a;一位双非学校的大三学生&#xff0c;编程爱好者&#xff0c; 专注于基础和实战分享&#xff0c;欢迎私信咨询&#xff01; &#x1f386;入门专栏&#xff1a;&#x1f387;【MySQL&#xff0…

【Linux网络】手动部署并测试内网穿透

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…

修改HuggingFace模型默认缓存路径

huggingface模型的默认缓存路径是~/.cache/huggingface/hub/ 通常修改为自己的路径会更为方便。 方式一&#xff1a;cache_dir 参数 可以通过from_pretrained函数中的 cache_dir 参数来指定&#xff0c;缺点&#xff0c;每次都需要手动指定&#xff0c;比较麻烦。 如&#x…