Linux 的 UDP 网络编程 -- 回显服务器,翻译服务器
目录
1. 回显服务器 -- echo server
1.1 相关函数介绍
1.1.1 socket()
1.1.2 bind()
1.1.3 recvfrom()
1.1.4 sendto()
1.1.5 inet_ntoa()
1.1.6 inet_addr()
1.2 Udp 服务端的封装 -- UdpServer.hpp
1.3 服务端代码 -- UdpServer.cc
1.4 客户端代码 -- UdpClient.cc
1.4.1 Linux版本的客户端
1.4.2 Windows 版本的客户端
1.5 demo 演示
1.6 网络相关命令
2. 翻译服务器 -- Translation server
2.1 Udp 服务端封装 -- UdpServer.hpp
2.2 字典结构体的封装 -- Dict.hpp
2.3 网络地址转主机地址的封装 -- InetAddr.hpp
2.4 Udp 服务端 -- UdpServer.cc
2.5 Udp 客户端 -- UdpClient.cc
1. 回显服务器 -- echo server
使用C++实现一个回显服务器,该代码的作用是客户端向服务端发送消息,然后回显到客户端的显示器上。
先给出需要使用的互斥锁的封装模块和线程安全的日志模块。
// Mutex.hpp#pragma once
#include <pthread.h>// 将互斥量接口封装成面向对象的形式
namespace MutexModule
{class Mutex{public:Mutex(){int n = pthread_mutex_init(&_mutex, nullptr);(void)n;}~Mutex(){int n = pthread_mutex_destroy(&_mutex);(void)n;}void Lock(){int n = pthread_mutex_lock(&_mutex);(void)n;}void Unlock(){int n = pthread_mutex_unlock(&_mutex);(void)n;}pthread_mutex_t* Get() // 获取原生互斥量的指针{return &_mutex;}private:pthread_mutex_t _mutex;};// 采用RAII风格进行锁管理,当局部临界区代码运行完的时候,局部LockGuard类型的对象自动进行释放,调用析构函数释放锁class LockGuard{public:LockGuard(Mutex &mutex): _mutex(mutex){_mutex.Lock();}~LockGuard(){_mutex.Unlock();}private:Mutex& _mutex;};
}
// Log.hpp#ifndef __LOG_HPP__
#define __LOG_HPP__#include <iostream>
#include <cstdio>
#include <string>
#include <filesystem> //C++17
#include <sstream>
#include <fstream>
#include <ctime>
#include <memory>
#include <unistd.h>
#include "Mutex.hpp"namespace LogModule
{using namespace MutexModule;const std::string gsep = "\r\n";// 策略模式 -- 利用C++的多态特性// 1. 刷新策略 a: 向显示器打印 b: 向文件中写入// 刷新策略基类class LogStrategy{public:virtual ~LogStrategy() = default;virtual void SyncLog(const std::string &message) = 0;};// 显示器打印日志的策略class ConsoleLogStrategy : public LogStrategy{public:ConsoleLogStrategy(){}void SyncLog(const std::string &message) override{// 加锁使多线程原子性的访问显示器LockGuard lockGuard(_mutex);std::cout << message << gsep;}~ConsoleLogStrategy(){}private:Mutex _mutex;};// 文件打印日志策略// 默认的日志文件路径和日志文件名const std::string defaultPath = "./log";const std::string defaultFile = "my.log";class FileLogStrategy : public LogStrategy{public:FileLogStrategy(const std::string &path = defaultPath, const std::string &file = defaultFile): _path(path),_file(file){// 加锁使多线程原子性的访问文件LockGuard lockGuard(_mutex);// 判断目录是否存在if (std::filesystem::exists(_path)) // 检测文件系统对象(文件,目录,符号链接等)是否存在{return;}try{// 如果目录不存在,递归创建目录std::filesystem::create_directories(_path);}catch (const std::filesystem::filesystem_error &e) // 如果创建失败则打印异常信息{std::cerr << e.what() << '\n';}}void SyncLog(const std::string &message) override{LockGuard lockGuard(_mutex);// 追加方式向文件中写入std::string fileName = _path + (_path.back() == '/' ? "" : "/") + _file;// std::ofstream是C++标准库中用于输出到文件的流类,主要用于将数据写入文件std::ofstream out(fileName, std::ios::app);if (!out.is_open()){return;}out << message << gsep;out.close();}~FileLogStrategy(){}private:std::string _path; // 日志文件所在路径std::string _file; // 日志文件本身Mutex _mutex;};// 2. 形成完整日志并刷新到指定位置// 2.1 日志等级enum class LogLevel{DEBUG,INFO,WARNING,ERROR,FATAL};// 2.2 枚举类型的日志等级转换为字符串类型std::string Level2Str(LogLevel level){switch (level){case LogLevel::DEBUG:return "DEBUG";case LogLevel::INFO:return "INFO";case LogLevel::WARNING:return "WARNING";case LogLevel::ERROR:return "ERROR";case LogLevel::FATAL:return "FATAL";default:return "UNKNOWN";}}// 2.3 获取当前时间的函数std::string GetCurTime(){// time 函数参数为一个time_t类型的指针,若该指针不为NULL,会把获取到的当前时间值存储在指针指向的对象中// 若传入为NULL,则仅返回当前时间,返回从1970年1月1日0点到目前的秒数time_t cur = time(nullptr);struct tm curTm;// localtime_r是localtime的可重入版本,主要用于将time_t类型表示的时间转换为本地时间,存储在struct tm 结构体中localtime_r(&cur, &curTm);char timeBuffer[128];snprintf(timeBuffer, sizeof(timeBuffer), "%4d-%02d-%02d %02d:%02d:%02d",curTm.tm_year + 1900,curTm.tm_mon + 1,curTm.tm_mday,curTm.tm_hour,curTm.tm_min,curTm.tm_sec);return timeBuffer;}// 2.4 日志形成并刷新class Logger{public:// 默认刷新到显示器上Logger(){EnableConsoleLogStrategy();}void EnableConsoleLogStrategy(){// std::make_unique用于创建并返回一个std::unique_ptr对象_fflushStrategy = std::make_unique<ConsoleLogStrategy>();}void EnableFileLogStrategy(){_fflushStrategy = std::make_unique<FileLogStrategy>();}// 内部类默认是外部类的友元类,可以访问外部类的私有成员变量// 内部类LogMessage,表示一条日志信息的类class LogMessage{public:LogMessage(LogLevel &level, std::string &srcName, int lineNum, Logger &logger): _curTime(GetCurTime()),_level(level),_pid(getpid()),_srcName(srcName),_lineNum(lineNum),_logger(logger){// 日志的基本信息合并起来// std::stringstream用于在内存中进行字符串的输入输出操作, 提供一种方便的方式处理字符串// 将不同类型的数据转换为字符串,也可以将字符串解析为不同类型的数据std::stringstream ss;ss << "[" << _curTime << "] "<< "[" << Level2Str(_level) << "] "<< "[" << _pid << "] "<< "[" << _srcName << "] "<< "[" << _lineNum << "] "<< "- ";_logInfo = ss.str();}// 使用模板重载运算符<< -- 支持不同数据类型的输出运算符重载template <typename T>LogMessage &operator<<(const T &info){std::stringstream ss;ss << info;_logInfo += ss.str();return *this;}~LogMessage(){if (_logger._fflushStrategy){_logger._fflushStrategy->SyncLog(_logInfo);}}private:std::string _curTime; // 日志时间LogLevel _level; // 日志等级pid_t _pid; // 进程pidstd::string _srcName; // 输出日志的文件名int _lineNum; //输出日志的行号std::string _logInfo; //完整日志内容Logger &_logger; // 方便使用策略进行刷新};// 使用宏进行替换之后调用的形式如下// logger(level, __FILE__, __LINE__) << "hello world" << 3.14;// 这里使用仿函数的形式,调用LogMessage的构造函数,构造一个匿名的LogMessage对象// 返回的LogMessage对象是一个临时对象,它的生命周期从创建开始到包含它的完整表达式结束(可以简单理解为包含// 这个对象的该行代码)// 代码调用结束的时候,如果没有LogMessage对象进行临时对象的接收,则会调用析构函数,// 如果有LogMessage对象进行临时对象的接收,会调用拷贝构造或者移动构造构造一个对象,并析构临时对象// 所以通过临时变量调用析构函数进行日志的打印LogMessage operator()(LogLevel level, std::string name, int line){return LogMessage(level, name, line, *this);}~Logger(){}private:std::unique_ptr<LogStrategy> _fflushStrategy;};// 定义一个全局的Logger对象Logger logger;// 使用宏定义,简化用户操作并且获取文件名和行号#define LOG(level) logger(level, __FILE__, __LINE__) // 使用仿函数的方式进行调用#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
}#endif
1.1 相关函数介绍
1.1.1 socket()
在网络编程领域,socket
是一个基础且关键的函数,主要用于创建网络通信的端点,也就是 “套接字”。
原型:int socket(int domain, int type, int protocol);头文件:#include <sys/types.h>#include <sys/socket.h>参数:domain(协议族):此参数用于确定网络通信所使用的协议栈,常见的取值有:AF_INET:代表 IPv4 协
议,AF_INET6:表示 IPv6 协议,AF_UNIX:用于本地通信的 Unix 域套接字。type(套接字类型):该参数决定了通信的特性,常用的类型有:SOCK_STREAM:提供面向连接的、可靠
的数据流服务,TCP 协议就属于这种类型。SOCK_DGRAM:实现无连接的、不可靠的数据报服务,UDP 协议是其
典型代表。SOCK_RAW:允许直接访问底层协议,可用于自定义协议的开发。protocol(协议):当套接字类型不能唯一确定使用的协议时,就需要通过这个参数来明确指定。一般情
况下,将其设置为 0 即可,系统会自动选择合适的协议。对于 SOCK_STREAM 类型,系统通常会选择 TCP 协
议。对于 SOCK_DGRAM 类型,系统一般会选择 UDP 协议。返回值:成功,返回一个非负整数,即调节子描述符,类似文件描述符。失败,返回-1,并设置errno来指示具体的错误原因。功能:创建网络通信的套接字
1.1.2 bind()
在网络编程中,bind()
函数是一个关键的系统调用,主要用于将一个套接字(通过 socket()
函数创建)与特定的网络地址和端口号进行绑定。
原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);头文件:#include <sys/types.h>#include <sys/socket.h>参数:sockfd:这是通过 socket() 函数返回的套接字描述符,它标识了要进行绑定操作的套接字。addr:这是一个指向 struct sockaddr 类型的指针,其中包含了要绑定的地址和端口信息。不过,
在实际编程中,通常会使用特定协议的地址结构,比如 struct sockaddr_in(用于 IPv4)或 struct
sockaddr_in6(用于 IPv6),然后再将其强制转换为 struct sockaddr 类型。addrlen:该参数表示 addr 结构的长度,其类型为 socklen_t返回值:成功,返回0.失败,返回-1,并设置 errno 来指示具体的错误原因。功能:用于将一个套接字(通过 socket() 函数创建)与特定的网络地址和端口号进行绑定。
1.1.3 recvfrom()
在网络编程里,recvfrom
函数主要用于从 UDP 套接字接收数据并获取发送方的套接字信息。
原型:ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);头文件:#include <sys/types.h>#include <sys/socket.h>参数:sockfd:这是通过 socket() 函数返回的套接字描述符,它标识了要接收数据的套接字。buf:这是一个指向缓冲区的指针,用于存储接收到的数据。len:表示缓冲区 buf 的最大长度,即最多可以接收的字节数。flags:这是一个可选的标志参数,通常设置为 0。常见的标志选项有:MSG_DONTWAIT:将操作设置为非
阻塞模式。MSG_PEEK:查看数据但不将其从接收队列中移除。src_addr:这是一个指向 struct sockaddr 类型的指针,用于存储发送方的地址信息。addrlen:这是一个指向 socklen_t 类型的指针,用于指定 src_addr 结构的长度。函数返回时,该参
数会被更新为实际存储的地址结构长度。返回值:成功,返回实际接收到的字节数。返回0,表示连接已关闭(对于TCP套接字而言)。返回-1,表示调用失败,此时会设置 errno 来指示具体的错误原因。功能:用于从 UDP 套接字接收数据和获取发送方的套接字信息。
1.1.4 sendto()
sendto()
是 C 语言网络编程中的一个关键函数,主要用于在无连接的套接字(如 UDP)上发送数据。sendto()
在发送数据时需要指定目标地址,这使得它非常适合 UDP 这种无连接的通信模式。
原型:ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);头文件:#include <sys/types.h>#include <sys/socket.h>参数:sockfd:这是通过 socket() 函数创建的套接字描述符,用于标识发送数据的套接字。buf:指向要发送数据的缓冲区的指针。len:要发送数据的长度(以字节为单位)。flags:可选的标志参数,通常设置为 0。常见的标志选项有:MSG_DONTWAIT:将操作设置为非阻塞模
式。MSG_NOSIGNAL:避免在连接断开时发送 SIGPIPE 信号。dest_addr:指向目标地址的指针,类型为 struct sockaddr。对于 IPv4,通常使用 struct
sockaddr_in;对于 IPv6,则使用 struct sockaddr_in6。addrlen:目标地址结构的长度,类型为 socklen_t。返回值:成功,返回实际发送的字节数(可能小于请求发送的字节数)。失败,返回-1,并设置 errno 来指示具体的错误原因。功能:主要用于在无连接的套接字(如 UDP)上发送数据。
1.1.5 inet_ntoa()
inet_ntoa()
是 C 语言网络编程中的一个关键函数,其主要作用是将 32 位二进制 IPv4 地址转换为 点分十进制字符串(如 192.168.1.1
)。
原型:char *inet_ntoa(struct in_addr in);头文件:#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>参数:in:struct in_addr 类型的结构体,该结构体内部有一个 s_addr 成员,用于存储 32 位的 IPv4 地址
(以网络字节序表示)。返回值:返回一个指向点分十进制字符串风格的ip地址。功能:将 32 位二进制 IPv4 网络字节序的 ip 地址转换为点分十进制字符串(如 192.168.1.1)
1.1.6 inet_addr()
inet_addr()
是 C 语言网络编程中的一个基础函数,其主要功能是将点分十进制格式(如 192.168.1.1
)的 IPv4 地址转换为 32 位二进制网络字节序整数。
原型:in_addr_t inet_addr(const char *cp);头文件:#include <sys/types.h>#include <netinet/in.h>#include <arpa/inet.h>参数:cp:指向点分十进制字符串的指针,例如 "127.0.0.1"。返回值:成功,返回 in_addr_t 类型的 32 位整数(网络字节序)。失败,返回 INADDR_NONE(通常为 0xFFFFFFFF),这意味着无法解析输入的字符串。功能:将点分十进制字符串风格的 ip 地址,转换为4字节的网络字节序整数。
1.2 Udp 服务端的封装 -- UdpServer.hpp
#pragma once#include <iostream>
#include <string>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"using namespace LogModule;
using func_t = std::function<std::string(const std::string&)>; // 参数为string& 返回值为 string 的函数类型const int defaultfd = -1;class UdpServer
{
public:UdpServer(uint16_t port, func_t func): _sockfd(defaultfd),_port(port),_isrunning(false),_func(func){}void Init(){// 1. 创建套接字_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){// 创建套接字失败LOG(LogLevel::FATAL) << "socket create error!";exit(1);}LOG(LogLevel::INFO) << "socket create seccess, sockfd: " << _sockfd; // 创建成功只是打开文件// 2. 绑定 socket 信息,ip 和 端口号// 2.1 填充 sockaddr_in 结构体struct sockaddr_in local; // 用于网络通信的结构体bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port); // 主机字节序转成网络字节序// 服务端不建议手动bind特定ip// 当一个机器有多张网卡的时候,服务端 ip 绑定INADDR_ANY,就可以接收任意ip中端口号为portlocal.sin_addr.s_addr = INADDR_ANY;// 2.2 绑定服务器的套接字信息// 为什么服务器端要显式的bind?// 服务器的ip和端口号必须是众所周知且不能轻易改变的.int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));if (n < 0){LOG(LogLevel::FATAL) << "bind error";exit(2);}LOG(LogLevel::INFO) << "bind success, sockfd: " << _sockfd;}void Start(){_isrunning = true;while(_isrunning) // 启动服务器之后是死循环{// 1. 创建用于接收消息的缓冲器变量 buffer 以及接收远端主机的套接字变量 peerchar buffer[1024];struct sockaddr_in peer; // 客户端套接字结构体socklen_t len = sizeof(peer);// 2. 收消息,服务端收取客户端的数据,对数据进行处理// 从 _sockfd 指向的网络文件中收取客户端 peer 发送的 sizeof(buffer) - 1 个字节以及客户端的套接字信息// 第四个参数为0,表示阻塞读ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);if (s > 0) // 收到消息,s表示收到数据的字节数{int peer_port = ntohs(peer.sin_port); // 将客户端端口号转成主机字节序std::string peer_ip = inet_ntoa(peer.sin_addr); // 将客户端ip转为字符串风格的ipbuffer[s] = 0;// 服务端显式发送消息的客户端信息LOG(LogLevel::DEBUG) << "[" << peer_ip << ":" << peer_port << "]# " << buffer;// 2. 发消息,将消息进行处理后回发给客户端std::string result = buffer;result = _func(buffer);sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr*)&peer, len);}}}~UdpServer(){}private:int _sockfd; // 套接字描述符uint16_t _port; // 端口号bool _isrunning;// 运行标志位func_t _func; // 服务端处理数据的回调函数
};
1.3 服务端代码 -- UdpServer.cc
#include <memory>
#include "UdpServer.hpp"std::string defaultHandler(const std::string &message)
{std::string s = "server say@ ";s += message;return s;
}// 通过命令行 ./udpserver port 启动服务器
int main(int argc, char *argv[])
{if (argc != 2){std::cerr << "Usage: " << argv[0] << " port" << std::endl;return 1;}uint16_t port = std::stoi(argv[1]);Enable_Console_Log_Strategy();std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, defaultHandler);usvr->Init();usvr->Start();return 0;
}
1.4 客户端代码 -- UdpClient.cc
1.4.1 Linux版本的客户端
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>// 通过命令行 ./udpclient server_ip server_port 启动客户端
int main(int argc, char *argv[])
{// 客户端访问目标服务器需要知道什么// 需要服务器的ip和端口// 怎么知道服务器的ip和端口呢 -- 内置的ipif (argc != 3){std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;return 1;}std::string server_ip = argv[1];uint16_t server_port = std::stoi(argv[2]);// 1. 创建套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){std::cerr << "socket create error" << std::endl;return 2;}// 2. 填充服务端的套接字信息struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET; // AF_INET 或者 PF_INETserver.sin_port = htons(server_port);server.sin_addr.s_addr = inet_addr(server_ip.c_str());// client不需要显式的bind,首次发送消息,操作系统自动给client进行bind,// 端口号采用随机端口号,一个端口号只能被一个进程bind,为了避免client端口冲突// client端口号是多少不重要,只要是唯一的就行while(true){// 1. 给客户端发消息std::string input;std::cout << "Please Enter# ";if (input.empty()) continue;std::getline(std::cin, input);int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr*)&server, sizeof(server));(void)n;// 2. 回显消息char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);if (m > 0){buffer[m] = 0;std::cout << buffer << std::endl;}}return 0;
}
1.4.2 Windows 版本的客户端
#define _CRT_SECURE_NO_WARNINGS#include <iostream>
#include <cstdio>
#include <thread>
#include <string>
#include <cstdlib>
// Windows中需要包含的头文件
#include <WinSock2.h>
#include <Windows.h>#pragma warning(disable : 4996) // 屏蔽一些 warning 报错#pragma comment(lib, "ws2_32.lib") // 引入 ws2_32.lib 库std::string server_ip = "服务器ip地址"; // 服务器ip
uint16_t server_port = 8888; // 服务器端口号int main()
{WSADATA wsd;WSAStartup(MAKEWORD(2, 2), &wsd); // 构建 2.2 版本// 1. 创建 udp 套接字SOCKET sockfd = socket(AF_INET, SOCK_DGRAM, 0); // SOCKET == intif (sockfd == SOCKET_ERROR){std::cerr << "socket create error" << std::endl;return 1;}// 2. 填充 sockaddr_in 结构体struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(server_port);server.sin_addr.s_addr = inet_addr(server_ip.c_str());std::string message;char buffer[1024];while (true){// 3. 发信息给服务端std::cout << "Please Enter# ";std::getline(std::cin, message);if (message.empty()) continue;sendto(sockfd, message.c_str(), sizeof(buffer), 0, (struct sockaddr*)&server, sizeof(server));// 4. 收消息,并显示到显示器上struct sockaddr_in temp;int len = sizeof(temp);int s = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&temp, &len);if (s > 0){buffer[s] = 0;std::cout << buffer << std::endl;}}closesocket(sockfd);WSACleanup();return 0;
}
WinSock2.h 是 Windows Sockets API(应用程序接口)的头文件,用于在Windows 平台上进行网络编程。它包含了 Windows Sockets 2(Winsock2)所需的数据类型、函数声明和结构定义,使得开发者能够创建和使用套接字(sockets)进行网络通信。
在编写使用 Winsock2 的程序时,需要在源文件中包含 WinSock2.h 头文件。这样,编译器就能够识别并理解 Winsock2 中定义的数据类型和函数,从而能够正确地编译和链接网络相关的代码。
此外,与 WinSock2.h 头文件相对应的是 ws2_32.lib 库文件。在链接阶段,需要将这个库文件链接到程序中,以确保运行时能够找到并调用 Winsock2 API 中实现的函数。
在 WinSock2.h 中定义了一些重要的数据类型和函数,如:
WSADATA:保存初始化 Winsock 库时返回的信息。
SOCKET:表示一个套接字描述符类型,用于在网络中唯一标识一个套接字。
sockaddr_in:IPv4 地址结构体,用于存储 IP 地址和端口号等信息。
socket():创建一个新的套接字。
bind():将套接字与本地地址绑定。
listen():将套接字设置为监听模式,等待客户端的连接请求。
accept():接受客户端的连接请求,并返回一个新的套接字描述符,用于与客户端进行通信。
WSAStartup 函数是 Windows Sockets API 的初始化函数,它用于初始化Winsock 库。该函数在应用程序或 DLL 调用任何 Windows 套接字函数之前必须首先执行,它扮演着初始化的角色。
以下是 WSAStartup 函数的一些关键点:
它接受两个参数:wVersionRequested 和 lpWSAData。wVersionRequested 用于指定所请求的 Winsock 版本,通常使用 MAKEWORD(major, minor)宏,其中major 和 minor 分别表示请求的主版本号和次版本号。lpWSAData 是一个指向 WSADATA 结构的指针,用于接收初始化信息。函数调用成功,它会返回 0;否则,返回错误代码。
在调用 WSAStartup 函数后,如果应用程序完成了对请求的 Socket 库的使用,应调用 WSACleanup 函数来解除与 Socket 库的绑定并释放所占用的系统资源。
1.5 demo 演示
(1)本地使用客户端和服务端进行通信。
服务端因为服务端 ip 进行绑定的时候绑定的是 INADDR_ANY,所以服务端启动的时候仅需要传入端口号。
客户端启动的时候,可以传入 内网 ip 或者 本地环回 ip:127.0.0.1 和端口号。
客户端和服务端启动之后即可进行通信,服务端显式客户端的套接字信息以及客户端发送的信息,客户端回显发送的信息:
(2)跨网络使用客户端和服务端进行通信。
服务端启动的时候也仅传入端口号。
客户端启动的时候传入服务端进程的公网 ip 和端口号。Windows 系统下也一样,但是Windows下需要启动 Windows 版本的客户端。
1.6 网络相关命令
ping [-选项] [网址或ip]
功能:用于检测主机是否与网络进行了连接。
常用选项:
c[次数],默认情况下 ping 是会一直持续下去的,这个选项表示 ping 的次数。
上述表示对百度的网站 ping 3 次。
netstat [-选项]
功能:查看网络状态信息。
常用选项:
n:拒绝显示别名,能显示数字的全部转化成数字。
l:仅列出有在 Listen(监听)的服务状态。
p:显示建立相关链接的程序名和pid。
t:仅显示 tcp 相关服务。
u:仅显示 udp 相关服务。
a:显示所有选项,默认是不显示 LISTEN 相关。
上述命令显示所有与 udp 相关的网络服务。
增加 p 选项会显示进程名和进程 pid,这里没有显示是因为 netstat 命令是用普通用户启动的,而这几个服务都是使用超级用户启动的,有权限问题。
n 选项可以将能用数字显示的信息用数字显示出来。
watch 命令可以周期性的指向命令。
watch -n 1 netstat -nuap -- 每个 1 秒执行一次 netstat -nuap 命令。
pidof [进程名]
功能:查看进程的 pid。
xargs [命令]
功能:将上一个命令传入管道的内容转换成后一个命令的参数。
通过上述命令快速杀掉启动的 udpserver 进程。
2. 翻译服务器 -- Translation server
2.1 Udp 服务端封装 -- UdpServer.hpp
#pragma once#include <iostream>
#include <string>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"
#include "InetAddr.hpp"using namespace LogModule;
using func_t = std::function<std::string(const std::string&, InetAddr&)>; // 参数为string& 返回值为 string 的函数类型const int defaultfd = -1;class UdpServer
{
public:UdpServer(uint16_t port, func_t func): _sockfd(defaultfd),_port(port),_isrunning(false),_func(func){}void Init(){// 1. 创建套接字_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket create error!";exit(1);}LOG(LogLevel::INFO) << "socket create seccess, sockfd: " << _sockfd;// 2. 绑定 socket 信息,ip 和 端口号struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;// 2.2 绑定服务器的套接字信息int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));if (n < 0){LOG(LogLevel::FATAL) << "bind error";exit(2);}LOG(LogLevel::INFO) << "bind success, sockfd: " << _sockfd;}void Start(){_isrunning = true;while(_isrunning){// 1. 创建用于接收消息的缓冲器变量 buffer 以及接收远端主机的套接字变量 peerchar buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);// 2. 收消息,服务端收取客户端的数据,对数据进行处理ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);if (s > 0){InetAddr client(peer);int peer_port = ntohs(peer.sin_port);std::string peer_ip = inet_ntoa(peer.sin_addr);buffer[s] = 0;// 2. 发消息,将消息进行处理后回发给客户端std::string result = _func(buffer, client); // 处理数据sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr*)&peer, len);}}}~UdpServer(){}private:int _sockfd; // 套接字描述符uint16_t _port; // 端口号bool _isrunning;// 运行标志位func_t _func; // 服务端处理数据的回调函数
};
2.2 字典结构体的封装 -- Dict.hpp
字典文件 -- dictionary.txt
apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
hello:
: 你好run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天
#pragma once#include <iostream>
#include <fstream>
#include <string>
#include <unordered_map>
#include "Log.hpp"
#include "InetAddr.hpp"const std::string defaultDictPath = "./dictionary.txt";
const std::string sep = ": ";using namespace LogModule;class Dict
{
public:Dict(const std::string &path = defaultDictPath): _dict_path(path){}bool LoadDict(){std::ifstream in(_dict_path);if (!in.is_open()){LOG(LogLevel::DEBUG) << "打开字典:" << _dict_path << " 失败";return false;}// 1. 循环加载字典的每行数据std::string line;while(std::getline(in, line)){auto pos = line.find(sep);// 1.1 排除字典中无效内容if (pos == std::string::npos){LOG(LogLevel::WARNING) << "解析: " << line << " 失败";continue; }// 1.2 将有效内容进行加载std::string english = line.substr(0, pos);std::string chinese = line.substr(pos + sep.size());_dict.insert(std::make_pair(english, chinese));if (english.empty() || chinese.empty()){LOG(LogLevel::WARNING) << line << "没有有效内容";continue;}_dict.insert(std::make_pair(english, chinese));LOG(LogLevel::DEBUG) << "加载: " << line << " 成功";}in.close();return true;}std::string Translate(const std::string &word, InetAddr &client){auto iter = _dict.find(word);if (iter == _dict.end()){LOG(LogLevel::DEBUG) << "进入到了翻译模块,[" << client.Ip() << " : " << client.Port() << "]# " << word << "->None";return "None";}LOG(LogLevel::DEBUG) << "进入到了翻译模块,[" << client.Ip() << " : " << client.Port() << "]# " << word << "->" << iter->second;return iter->second;}~Dict(){}private:std::string _dict_path; // 路径 + 文件名std::unordered_map<std::string, std::string> _dict;
};
2.3 网络地址转主机地址的封装 -- InetAddr.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>class InetAddr
{
public:InetAddr(struct sockaddr_in &addr) : _addr(addr){_port = ntohs(_addr.sin_port);_ip = inet_ntoa(_addr.sin_addr);}uint16_t Port() {return _port;}std::string Ip() {return _ip;}~InetAddr(){}
private:struct sockaddr_in _addr;std::string _ip;uint16_t _port;
};
2.4 Udp 服务端 -- UdpServer.cc
#include <memory>
#include "UdpServer.hpp"
#include "Dict.hpp"// 回显服务经常用于检测
std::string defaultHandler(const std::string &message)
{std::string s = "server say@ ";s += message;return s;
}// 通过命令行 ./udpserver port 启动服务器
int main(int argc, char *argv[])
{if (argc != 2){std::cerr << "Usage: " << argv[0] << " port" << std::endl;return 1;}uint16_t port = std::stoi(argv[1]);Enable_Console_Log_Strategy();// 1. 字典对象,提供翻译功能Dict dict;dict.LoadDict();// 2. 网络服务器对象,提供通信功能std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&dict](const std::string &word, InetAddr &client)->std::string{return dict.Translate(word, client);});usvr->Init();usvr->Start();return 0;
}
2.5 Udp 客户端 -- UdpClient.cc
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>// 通过命令行 ./udpclient server_ip server_port 启动客户端
int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;return 1;}std::string server_ip = argv[1];uint16_t server_port = std::stoi(argv[2]);// 1. 创建套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){std::cerr << "socket create error" << std::endl;return 2;}// 2. 填充服务端的套接字信息struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET; // AF_INET 或者 PF_INETserver.sin_port = htons(server_port);server.sin_addr.s_addr = inet_addr(server_ip.c_str());// 3. 循环读取客户端消息while(true){// 3.1. 给客户端发单词std::string input;std::cout << "Please Enter# ";std::getline(std::cin, input);if (input.empty()) continue;int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr*)&server, sizeof(server));(void)n;// 3.2. 显示翻译后的中文char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);if (m > 0){buffer[m] = 0;std::cout << buffer << std::endl;}}return 0;
}
相关文章:
Linux 的 UDP 网络编程 -- 回显服务器,翻译服务器
目录 1. 回显服务器 -- echo server 1.1 相关函数介绍 1.1.1 socket() 1.1.2 bind() 1.1.3 recvfrom() 1.1.4 sendto() 1.1.5 inet_ntoa() 1.1.6 inet_addr() 1.2 Udp 服务端的封装 -- UdpServer.hpp 1.3 服务端代码 -- UdpServer.cc 1.4 客户端代码 -- UdpClient.…...
回溯法理论基础 LeetCode 77. 组合 LeetCode 216.组合总和III LeetCode 17.电话号码的字母组合
目录 回溯法理论基础 回溯法 回溯法的效率 用回溯法解决的问题 如何理解回溯法 回溯法模板 LeetCode 77. 组合 回溯算法的剪枝操作 LeetCode 216.组合总和III LeetCode 17.电话号码的字母组合 回溯法理论基础 回溯法 回溯法也可以叫做回溯搜索法,它是一…...
LeetCode --- 156双周赛
题目列表 3541. 找到频率最高的元音和辅音 3542. 将所有元素变为 0 的最少操作次数 3543. K 条边路径的最大边权和 3544. 子树反转和 一、找到频率最高的元音和辅音 分别统计元音和辅音的出现次数最大值,然后相加即可,代码如下 // C class Solution {…...
第五项修炼:打造学习型组织
最近一直接到的需求,都是公司董事长或总经理都特别推崇《第五项修炼:打造学习型组织》的内容,让各个层级的管理者都持续学习、应用、实践。我不禁开始反思,这背后到底隐藏着什么原因? 随着商业环境的变化和复杂性的增加…...
Bellman - Ford 算法与 SPFA 算法求解最短路径问题 ——从零开始的图论讲解(4)
目录 前言 为什么Dijkstra算法面对负权值图会有误差??? 举例说明 什么是Bellman -Ford算法? BF算法的核心思想 什么是松弛 为什么最多松弛N-1次? 代码实现 举例 初始状态(dist[] 数组) 第 1 轮松弛(遍历所有边) …...
Python训练营打卡 Day27
函数专题2:装饰器 知识点回顾: 装饰器的思想:进一步复用函数的装饰器写法注意内部函数的返回值 昨天我们接触到了函数大部分的功能,然后在你日常ctrl点进某个复杂的项目,发现函数上方有一个xxx,它就是装饰器 装饰器本质…...
初识计算机网络。计算机网络基本概念,分类,性能指标
初识计算机网络。计算机网络基本概念,分类,性能指标 本系列博客源自作者在大二期末复习计算机网络时所记录笔记,看的视频资料是B站湖科大教书匠的计算机网络微课堂,祝愿大家期末都能考一个好成绩! 视频链接地址 一、…...
5月16日day27打卡
函数专题2:装饰器 知识点回顾: 装饰器的思想:进一步复用函数的装饰器写法注意内部函数的返回值 作业: 编写一个装饰器 logger,在函数执行前后打印日志信息(如函数名、参数、返回值) logger def …...
【生成式AI文本生成实战】DeepSeek系列应用深度解析
目录 🌟 前言🏗️ 技术背景与价值🩹 当前技术痛点🛠️ 解决方案概述👥 目标读者说明 🧠 一、技术原理剖析📊 核心概念图解💡 核心作用讲解🔧 关键技术模块说明⚖️ 技术选…...
【Pandas】pandas DataFrame kurt
Pandas2.2 DataFrame Computations descriptive stats 方法描述DataFrame.abs()用于返回 DataFrame 中每个元素的绝对值DataFrame.all([axis, bool_only, skipna])用于判断 DataFrame 中是否所有元素在指定轴上都为 TrueDataFrame.any(*[, axis, bool_only, skipna])用于判断…...
2025年渗透测试面试题总结-安恒[实习]安全服务工程师(题目+回答)
网络安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 安恒[实习]安全服务工程师 1. SQLMap爆出当前库名的参数是什么? 2. Nmap探测系统的参数&am…...
在 Visual Studio Code (VSCode) 中配置 MCP(Model Context Protocol)
前提条件 安装 VSCode:确保已安装最新版本的 VSCode(建议使用 1.99 或以上版本,支持 MCP)。安装 GitHub Copilot 扩展:MCP 通常与 GitHub Copilot 的代理模式(Agent Mode)结合使用,…...
顶层架构 - 消息集群推送方案
一、推送基础概念简述 在即时通讯(IM)系统中,最基础的一件事就是“如何把消息推送给用户”。为了实现这个过程,我们要先了解两种常见的网络通信方式:HTTP 和 WebSocket。 1. HTTP 是什么? HTTP 就像一次性…...
C++性能测试工具——Vtune等的介绍
一、介绍 我们在前面的相关文章中对C性能的测试和分析工具(见“C性能测试工具gprof和gperftools基础”等)有一个初步的了解和应用,其实类似的相关工具还有不少。为了进一步的让开发者们掌握更多的相关性能测试分析相关的方法,对另…...
车道线检测----CLRKDNet
今天的最后一篇 车道线检测系列结束 CLRKDNet:通过知识蒸馏加速车道检测 摘要:道路车道是智能车辆视觉感知系统的重要组成部分,在安全导航中发挥着关键作用。在车道检测任务中,平衡精度与实时性能至关重要,但现有方法…...
【AI模型部署】
解决python引入huggingface_hub模块下载超时问题 背景问题解决 背景 AMD Ryzen™ AI处理器通过独特的NPUGPU异构架构,为AI工作负载提供强大的并行计算能力。本方案展示了如何将YOLOv8目标检测、RCAN超分辨率重建和Stable Diffusion文生图三类模型分别部署到NPU和GP…...
排序01:多目标模型
用户-笔记的交互 对于每篇笔记,系统记录曝光次数、点击次数、点赞次数、收藏次数、转发次数。 点击率点击次数/曝光次数 点赞率点赞次数/点击次数 收藏率收藏次数/点击次数 转发率转发次数/点击次数 转发是相对较少的,但是非常重要,例如转发…...
电子电器架构 --- Zonal架构正在开创汽车电子设计新时代
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界噪音的通透淡然。 生活中有两种人,一种人格外在意别人的眼光;另一种人无论…...
如何阅读、学习 Tcc (Tiny C Compiler) 源代码?如何解析 Tcc 源代码?
阅读和解析 TCC(Tiny C Compiler) 的源代码需要对编译器的基本工作原理和代码结构有一定的了解。以下是分步骤的指南,帮助你更高效地学习和理解 TCC 的源代码: 1. 前置知识准备 C 语言基础:TCC 是用 C 语言编写的&…...
Java 泛型与类型擦除:为什么解析对象时能保留泛型信息?
引言:泛型的“魔术”与类型擦除的困境 在 Java 中,泛型为开发者提供了类型安全的集合操作,但其背后的**类型擦除(Type Erasure)**机制却常常让人困惑。你是否遇到过这样的场景? List<String> list …...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(22):复习
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(22):复习 1、前言(1)情况说明(2)工程师的信仰2、知识点(1)复习(2)復習3、单词(1)日语(2)日语片假名单词4、对话练习5、单词辨析记录6、总结1、前言 (1)情况说明 自己在今年,在日本留学中,目前在语言学校,…...
Java基础学习
Java 基础大纲 1. Java 概述 Java 语言特点(跨平台、面向对象、自动内存管理) JVM、JRE、JDK 的作用与区别 开发环境搭建(安装 JDK、配置环境变量、IDE 使用) 2. 基础语法(已经学习) 变量与数据类型&a…...
MGX:多智能体管理开发流程
MGX的多智能体团队如何通过专家混合系统采用全新方法,彻底改变开发流程,与当前的单一智能体工具截然不同。 Lovable和Cursor在自动化我们的特定开发流程方面取得了巨大飞跃,但问题是它们仅解决软件开发的单一领域。 这就是MGX(MetaGPT X)的用武之地,它是一种正在重新定…...
2025第三届盘古石杯初赛(计算机部分)
前言 比赛的时候时间不对,打一会干一会,导致比赛时候思路都跟不上,赛后简单复现一下,希望大家批批一下 计算机取证 1、分析贾韦码计算机检材,计算机系统Build版本为?【标准格式:19000】 183…...
XML介绍及常用c及c++库
一.xml概述 1.什么是XML? XML(eXtensible Markup Language)是一种标记语言,1998 年 2 月:XML 1.0 发布,用于存储和传输结构化数据。与HTML专注于数据显示不同,XML专注于数据本身及其结构。 它…...
动态规划-63.不同路径II-力扣(LeetCode)
一、题目解析 与62.不同路径不同的一点是现在网格中有了障碍物,其他的并没有什么不同 二、算法解析 1.状态表示 dp[i][j]表示:到[i,j]位置时,不同的路径数 2.状态转移方程 由于多了障碍物,所以我们要判断是否遇到障碍物 3.初…...
海盗王3.0的数据库3合1并库处理方案
原版的海盗王数据库有3个accountserver,gamedb,tradedb,对应到是账号数据库,游戏数据库,商城数据库。 一直都有个想法,如何把这3个库合并到一起,这样可以实现一些功能。 涉及到sqlserver的数据库…...
Vue百日学习计划Day16-18天详细计划-Gemini版
重要提示: 番茄时钟: 每个番茄钟为25分钟学习,之后休息5分钟。每完成4个番茄钟,进行一次15-30分钟的长休息。动手实践: DOM 操作和事件处理的理解高度依赖于实际编码。请务必在浏览器中创建 HTML 页面,并配…...
【C++】15.并发支持库
本篇内容参考自cplusplus 1. thread 1.1 thread thread库底层是对各个系统的线程库(Linux下的pthread库和Windows下Thread库)进行封装。C11thread库的第一个特点是可以跨平台,第二个特点是Linux和Windows下提供的线程库都是面向过程的&…...
Linux系统编程——exec族函数
我们来完整、系统、通俗地讲解 Linux 系统编程中非常重要的一类函数:exec 族函数(也叫 exec family)。 一、什么是 exec? exec 系列函数的作用是: 用一个新的程序,替换当前进程的内容。 也就是说…...
职教实训室中的写实数字人:技术与应用方案
在当今快速发展的数字化时代,职业教育的重要性日益凸显。面对传统教学模式中个性化不足、互动性差等挑战,深声科技基于2D写实交互数字人的解决方案为职教实训室带来了全新的变革。本文将详细介绍该技术方案的核心原理、产品特色及其在职业培训中的实际应…...
Nginx模块配置与请求处理详解
Nginx 作为模块化设计的 Web 服务器,其核心功能通过不同模块协同完成。以下是各模块的详细配置案例及数据流转解析: 一、核心模块配置案例 1. Handler 模块(内容生成) 功能:直接生成响应内容(如静态文件、重定向等) # 示例1:静态文件处理(ngx_http_static_module)…...
54. 螺旋矩阵
题目链接: a54. 螺旋矩阵 题目描述: 给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。 题目分析: 改题目需要判断是否溢出边界,与59不同,59可以判断是否为0…...
virtualbox虚拟机中的ubuntu 20.04.6安装新的linux内核5.4.293 | 并增加一个系统调用 | 证书问题如何解决
参考文章:linux添加系统调用【简单易懂】【含32位系统】【含64位系统】_64位 32位 系统调用-CSDN博客 安装新内核 1. 在火狐下载你需要的版本的linux内核压缩包 这里我因为在windows上面下载过,配置过共享文件夹,所以直接复制粘贴通过共享文…...
代码随想录算法训练营第三十八天打卡
今天是动态规划的第三天,昨天的不同路径与整数分解的几道题目大家理解得如何?如果有疑问大家还是多去想想dp数组究竟是什么含义,还有我的状态转移是否正确,初始化是否正确,这一点很重要,今天的题目依旧是跑…...
【论信息系统项目的整合管理】
论信息系统项目的整合管理 某省机场管理集团航空货运站原有物流生产信息系统无法满足机场货运站生产信息与航空公司、对方航站、进出口航空货物按海关监管要求电子报关等行业信息实时共享发展需要,生产信息需多次重复录入问题已成为业务发展最大瓶颈,急需…...
小学数学题批量生成及检查工具
软件介绍 今天给大家介绍一款近期发现的小工具,它非常实用。 软件特点与出题功能 这款软件体积小巧,不足两兆,具备强大的功能,能够轻松实现批量出题。使用时,只需打开软件,输入最大数和最小数,…...
Python线性回归:从理论到实践的完整指南
Python线性回归:从理论到实践的完整指南 线性回归是数据科学和机器学习中最基础且最重要的算法之一。本文将深入探讨如何使用Python实现线性回归,从理论基础到实际应用,帮助读者全面理解这一重要的统计学和机器学习方法。 什么是线性回归&a…...
python 爬虫框架介绍
文章目录 前言一、Requests BeautifulSoup(基础组合)二、Scrapy(高级框架)三、PySpider(可视化爬虫)四、Selenium(浏览器自动化)五、Playwright(新一代浏览器自动化&…...
强化学习算法实战:一个例子搞懂sarsa、dqn、ddqn、qac、a2c及其区别
简介 在学习强化学习算法:sarsa、dqn、ddqn、qac、a2c、trpo、ppo时,由于有大量数学公式的推导,觉得十分晦涩,且听过就忘记了。 但是当把算法应用于实战时,代码的实现要比数学推导直观很多。 接下来通过不同的算法实现…...
文章记单词 | 第86篇(六级)
一,单词释义 pretty /ˈprɪti/- adj. 漂亮的;相当的 /adv. 相当地labour /ˈleɪbə(r)/- n. 劳动;劳工;分娩 /v. 劳动;努力(英式英语, labor)imaginary /ɪˈmdʒɪnəri/- adj. …...
firewall防火墙
一.Firewalld 防火墙概述 1.firewalld 简介 firewalld 的作用是为包过滤机制提供匹配规则(或称为策略),通过各种不同的规则告诉netfilter 对来自指定源、前往指定目的或具有某些协议特征的数据包采取何种处理方式为了更加方便地组织和管理防火墙,firewa11d 提供了…...
TII-2024《AGP-Net: Adaptive Graph Prior Network for Image Denoising》
推荐深蓝学院的《深度神经网络加速:cuDNN 与 TensorRT》,课程面向就业,细致讲解CUDA运算的理论支撑与实践,学完可以系统化掌握CUDA基础编程知识以及TensorRT实战,并且能够利用GPU开发高性能、高并发的软件系统…...
Pageassist安装(ollama+deepseek-r1)
page-assist网站:https://github.com/n4ze3m/page-assist 首先电脑配置node.js,管理员打开命令窗口输入下面命令下载bun npm install -g buncd 到你想要安装page-assist的地方(推荐桌面) 输入下列命令 git clone https://gith…...
Java—— 方法引用 : :
方法引用是什么 把已经存在的方法拿过来用,当做函数式接口中抽象方法的方法体 方法引用符 :: 方法引用的条件 1.需要有函数式接口 2.被引用方法必须已经存在 3.被引用方法的形参和返回值需要跟抽象方法保持一致 4.被引用方法的功能要满足当前…...
Linux基础开发工具大全
目录 软件包管理器 1>软件包 2>软件生态 3>yum操作 a.查看软件包 b.安装软件 c.卸载软件 4>知识点 vim编辑器 1>基本概念 2>基本操作 3>正常模式命令集 a.模式切换 b.移动光标 c.删除 d.复制 e.替换 f.撤销 g.更改 4>底行模式命令…...
C语言实现INI配置文件读取和写入
一.INI文件介绍 INI配置文件是一种简单的文本文件,用于存储配置信息,通常由一个或多个节(section)组成,每个节包含多个键值对(Key-Value)格式。INI文件易于阅读和编辑,广泛应用于多…...
volatile关键字详解
volatile关键字详解 1. 定义与核心作用 volatile 是Java中的关键字,用于修饰变量,主要解决多线程环境下的内存可见性和指令重排序问题。其核心作用: 保证可见性:确保所有线程读取到变量的最新值。禁止指令重排序:防止…...
二叉树子树判断:从递归到迭代的全方位解析
一、题目解析 题目描述 给定两棵二叉树root和subRoot,判断root中是否存在一棵子树,其结构和节点值与subRoot完全相同。 示例说明 示例1: root [3,4,5,1,2],subRoot [4,1,2] 返回true,因为root的左子树与subRoot完…...
【PhysUnits】4.1 类型级比特位实现解释(boolean.rs)
一、源码 该代码实现了一个类型级(type-level)的布尔系统,允许在编译时进行布尔运算。 //! 类型级比特位实现 //! //! 这些是基础的比特位类型,作为本库中其他数值类型的构建基础 //! //! 已实现的**类型运算符**: //! //! - 来自 core::op…...