> 技术文档 > 基于TCP的网络电子词典

基于TCP的网络电子词典


基于TCP的网络电子词典

网络电子词典核心代码块实现

1.1流程设计

基于TCP的网络电子词典

1.2主要模块

  1. 数据库模块:两个数据库,用户数据库、单词数据库

    单词数据库:存储四级单词词汇表

    用户数据库:两个表:

​ 1、存储用户名用户的密码

​ 2、用户查询历史记录表

  1. 服务器模块:创建服务器套接字、绑定IP端口号、启动监听、接收连接请求分线程管理客户端

  2. 处理客户端请求模块:

  • 传输数据类型:用户注册类型、用户登录类型、用户退出类型、单词查询类型、查询历史记录类型

  • 数据类型:MSG int type,char name[20],char text[128]

注意:type要进行数据转换:网络字节序和主机字节序的转换,除了char统一格式不需要变化

1.3主要函数功能

基于TCP的网络电子词典

基于TCP的网络电子词典

1.4代码实现及具体知识讲解

1.4.1server构造函数代码块
Server::Server(shared_ptr db_manager, const string ip, int port) : db_manager_(db_manager), ip_(ip), port_(port), running_(false) // 初始化列表{ // 创建用于连接的套接字文件描述符 sfd_ = socket(AF_INET, SOCK_STREAM, 0); if (sfd_ < 0) { throw runtime_error(\"socket error\"); // 抛出异常 } // 设置端口号快速重用 int opt = 1; if (setsockopt(sfd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { close(sfd_); throw runtime_error(\"setsockopt error\"); } // 绑定ip地址和端口号 sockaddr_in sin; sin.sin_family = AF_INET;  // 通信域 sin.sin_port = htons(port);  // 端口号 sin.sin_addr.s_addr = inet_addr(ip.c_str()); // ip地址 if (bind(sfd_, (struct sockaddr *)&sin, sizeof(sin)) < 0) { close(sfd_); throw runtime_error(\"bind error\"); }}
智能指针

智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。

C++11中提供了三种智能指针,使用这些智能指针时需要引用头文件

std::shared_ptr:共享的智能指针
std::unique_ptr:独占的智能指针
std::weak_ptr:弱引用的智能指针,它不共享指针,不能操作资源,是用来监视shared_ptr的。

TCP通信
TCP/UDP的区别

统属于传输层的相关协议

1> TCP通信

1、TCP提供了面向连接的、可靠的数据传输服务2、传输过程中,能够保障数据无误、数据无丢失、数据无重复、数据无失序 TCP通信中会给每个数据包编上编号,该编号称为序列号 每个序列号都需要应打包应答,如果没有应答,则会将上面的数据包重复传输3、TCP通信中,数据传输效率较低,耗费资源较多4、数据收发是不同步的 为了提高传输效率,tcp通信中会将多个较小的,发送时间间隔较短的数据包,沾成一个数据包进行发送,该现象称为沾包现象5、TCP通信使用场景:对于传输质量要求较高的以及传输量较大的数据通信,在需要可靠传输信息的场合,一般使用TCP通信 例如:账户和密码登录注册、大型文件的下载

2> UDP通信

1、提供面向无连接、不保证数据可靠传输、尽最大努力传输的协议2、数据传输过程中,可能会出现数据丢失、重复、失序、乱序等现象3、数据传输效率较高、实时性高4、限制每次传输的数据大小,多出部分会直接被忽略删除5、数据的收发是同步的,不会沾包6、使用场景:发送小尺寸的,在收到数据后给出应答比较困难的情况下,采用udp通信 例如:广播、组播、通信软件的音视频传输
字节序概念

字节序:计算机在存储多字节整数时,根据主机的CPU处理架构不同,我们将主机分成大端存储的主机和小端存储的主机

  • 大端存储:内存地址低位存储的是数据的高位
  • 小端存储:内存地址低位存储的是数据的低位
#include //检查是否是对应int main(int argc, const char *argv[]) {    //定义一个整形变量    int num = 0x12345678;    //定义一个字符类型的指针,指向整形变量的起始地址    char *ptr = (char *)&num;    //对ptr所指向的字节中的内容进行判断,如果是0x12则说明是大端存储    //如果是0x78则说明是小端存储    if(*ptr == 0x12)   {        cout<<\"big endian\"<<endl;   }else if(*ptr == 0x78)   {        cout << \"little endian\"<<endl;   }    std::cout << \"Hello, World!\" << std::endl;    return 0;}

无论发送端是大端存储还是小端存储,在传输多字节整数时,一律先转换为网络字节序。经由网络传输后,到达目的主机后,在转换为主机字节序即可

系统提供了一套有关网络字节序和主机字节序之间相互转换的函数

主机:host h
网络:network n
转换:to to

 #include        uint32_t htonl(uint32_t hostlong);//将4字节整数主机字节序转换为网络字节序,参数是主机字节序,返回值是网络字节序       uint16_t htons(uint16_t hostshort);//将2字节整数主机字节序转换为网络字节序,参数是主机字节序,返回值是网络字节序       uint32_t ntohl(uint32_t netlong);//将4字节整数的网络字节序转换为主机字节序,参数是网络字节序,返回值是主机字节序       uint16_t ntohs(uint16_t netshort);//将2字节整数的网络字节序转换为主机字节序,参数是网络字节序,返回值是主机字节序 eg:int num = 0x12345678; //调用函数,将该数据转换为网络字节序 int res = htonl(num); printf(\"res = %#x\\n\", res);      //res = 0x78563412//常见使用情况:封装消息结构体(MSG msg),端口号使用(port) 

不需要使用网络字节序转换函数的情况:
1、在进行单字节整数传输时,不需要使用 eg:char类型
2、在网络中传输字符串时,也不需要使用 eg:string类型

除此以外在进行多字节整数网络传输时,需要使用字节序转换函数

点分十进制

我们将ip地址的每一个字节单独计算出十进制数据(192.168.56.129即为4字节的十进制数据),并用进行分割,这种方式,称 为点分十进制,在程序中使用的是字符串来存储的。但是,ip地址的本质是4字节无符号整数,在网络中进行传输时,需要使用的是4字节无符号整数,而不是点分十进制的字符串。此时,就需要引入关于点分十进制数据向4字节无符号整数转换的相关函数

地址:address
网络:network
转换:to

 #include        #include        #include        in_addr_t inet_addr(const char *cp);//将点分十进制的ip地址转换为4字节无符号整数的网络字节序,参数时点分十进制数据,返回值时4字节无符号整数       char *inet_ntoa(struct in_addr in);//将4字节无符号整数的网络字节序,转换为点分十进制的字符串eg://地址指针记录ip地址 const char *ip = \"172.20.10.8\";    //点分十进制 unsigned int ip_net = inet_addr(ip);    //调用函数将点分十进制转换为4字节无符号整数
网络通信基础

基于TCP的网络电子词典

在网络通信过程中,有两种通信方式,分别是基于BS模型的,即浏览器服务器模型,和基于CS模型,即客户端服务器模型

客户端服务器模型六件套 socket bind listen accept send/recv close


注意事项
  1. bindaccept函数使用的地址结构体不同
  2. bindaccept函数参数3不同(前者Int类型,后者为*类型)
函数 功能 地址结构体用途 bind 绑定套接字到本地地址(IP+端口) 设置本地地址(明确指定监听目标) accept 接受远程客户端连接 获取远程地址(动态接收客户端信息)
socket函数
 #include           /* See NOTES */       #include        int socket(int domain, int type, int protocol); //协议族,通信类型(专属TCP,UDP),通信协议(具体可为0)       功能:为通信创建一个端点,并返回该端点对应的文件描述符,文件描述符的使用原则是最小未分配原则       参数1:协议族,常用的协议族如下       Name                Purpose                          Man page       AF_UNIX, AF_LOCAL   本地通信,同一主机的多进程通信         具体内容查看 man 7 unix       AF_INET             提供IPv4的相关通信方式               具体内容查看 man 7 ip       AF_INET6            提供IPv6的相关通信方式               具体内容查看 man 7 ipv6             参数2:通信类型,指定通信语义,常用的通信类型如下       SOCK_STREAM     支持TCP面向连接的通信协议       SOCK_DGRAM      支持UDP面向无连接的通信协议           参数3:通信协议,当参数2中明确指定特定协议时,参数3可以设置为0,但是有多个协议共同使用时,需要用参数3指定当前套接字确定的协议       返回值:成功返回创建的端点对应的文件描述符,失败返回-1并置位错误码 eg: int cfd = socket(AF_INET,SOCK_STREAM,0); //cfd作为文件描述符
bind函数
 #include           /* See NOTES */       #include        int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); //文件描述符,地址信息结构体,参数2的大小(不需要&)       功能:为套接字分配名称,给套接字绑定ip地址和端口号       参数1:要被绑定的套接字文件描述符 cfd       参数2:通用地址信息结构体,对于不同的通信域而言,使用的实际结构体是不同的,该结构体的目的是为了强制类型转换,防止警告 //需要提前包装结构体       通信域为:AF_INET而言,ipv4的通信方式       struct sockaddr_in {               sa_family_t    sin_family; /* 地址族: AF_INET */               in_port_t      sin_port;   /* 端口号的网络字节序 */               struct in_addr sin_addr;   /* 网络地址 */           };           /* Internet address. 嵌套结构体 */           struct in_addr {               uint32_t       s_addr;     /* ip地址的网络字节序 */           };       通信域为:AF_UNIX而言,本地通信       struct sockaddr_un {               sa_family_t sun_family;               /* 通信域:AF_UNIX */               char        sun_path[UNIX_PATH_MAX];  /* 通信使用的文件 */           };        参数3:参数2的大小        返回值:成功返回0,失败返回-1并置位错误码 eg:// 绑定ip地址和端口号 sockaddr_in sin; sin.sin_family = AF_INET;  // 通信域 sin.sin_port = htons(port);  // 端口号 sin.sin_addr.s_addr = inet_addr(ip.c_str()); // ip地址
listen函数
 #include           /* See NOTES */       #include        int listen(int sockfd, int backlog);//文件描述符,挂起队列个数       功能:将套接字设置成被动监听状态       参数1:套接字文件描述符       参数2:挂起队列能够增长的最大长度,一般为128       返回值:成功返回0,失败返回-1并置位错误码 eg:// 启动监听 if (listen(sfd_, 5) < 0) { cerr << \"listen error\" << endl; return false; }
accept函数(服务器专属)
 #include           /* See NOTES */       #include        int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);//文件套接字,地址信息结构体,参数2大小(需要&)       功能:阻塞等待客户端的连接请求,如果已连接队列中有客户端,则从连接队列中拿取第一个,并创建一个用于通信的套接字       参数1:服务器套接字文件描述符       参数2:通用地址信息结构体,用于接受已连接的客户端套接字地址信息的       参数3:接收参数2的大小       返回值:成功发那会一个新的用于通信的套接字文件描述符,失败返回-1并置位错误码eg: sockaddr_in client_addr;  // 接收客户端地址信息结构体 socklen_t addr_len = sizeof(client_addr); // 接收客户端地址信息结构体的大小 // 接收客户端连接请求操作 sfd_是socket创建的 int cfd = accept(sfd_, (struct sockaddr *)&client_addr, &addr_len);
send/recv函数
 #include        #include        ssize_t recv(int sockfd, void *buf, size_t len, int flags);//文件描述符,结构体起始地址,参数2的长度,阻塞or非阻塞       功能:从套接字中读取消息放入到buf中       参数1:通信的套接字文件描述符       参数2:要存放数据的起始地址       参数3:读取的数据的大小       参数4:读取标识位,是否阻塞读取       0:表示阻塞等待       MSG_DONTWAIT:非阻塞       返回值:可以是大于0:表示成功读取的字节个数       可以是等于0:表示对端已经下线(针对于TCP通信)       -1:失败,并置位错误码             ssize_t send(int sockfd, const void *buf, size_t len, int flags);//文件描述符,结构体起始地址,参数2的长度,阻塞or非阻塞       功能:向套接字文件描述符中将buf这个容器中的内容写入       参数1:通信的套接字文件描述符       参数2:要发送的数据的起始地址       参数3:发送的数据的大小       参数4:读取标识位,是否阻塞读取       0:表示阻塞等待       MSG_DONTWAIT:非阻塞       返回值:成功返回发送字节的个数       -1:失败,并置位错误码
close函数
 #include        int close(int fd);       功能:关闭套接字文件描述符       参数:要关闭的套接字文件描述符       返回值:成功返回0,失败返回-1并置位错误码
connect函数(客户端专属)
 #include           /* See NOTES */       #include        int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//文件套接字,地址信息结构体,参数2大小       功能:将指定的套接字,连接到给定的地址上       参数1:要连接的套接字文件描述符       参数2:通用地址信息结构体       参数3:参数2的大小       返回值:成功返回0,失败返回-1并置位错误码 if(connect(sockfd_,(struct sockaddr*)&sin,sizeof(sin))<0) { perror(\"connect error\"); }
网络属性

1> 网络套接字在不同层中,有不同的设置,分别有应用层(套接字层)、传输层、网络层、以太网层
2> 对套接字属性进行设置,可以使用setsockopt和getsockopt函数完成

函数 核心作用 适用场景 setsockopt() **设置(修改)**套接字选项的值 配置缓冲区大小、启用地址重用、设置超时等 getsockopt() **获取(查询)**套接字选项的当前值 诊断连接错误、检查缓冲区状态、验证配置
 #include           /* See NOTES */       #include        int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t*optlen);//文件描述符,操作的套接字层,       int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);       功能:设置或者获取套接字文件描述符的属性       参数1:套接字文件描述符       参数2:表示操作的套接字层 SOL_SOCKET:表示应用层或者套接字层,通过man 7 socket进行查找       该层中常用的属性:SO_REUSEADDR  地址快速重用         SO_BROADCAST  允许广播         SO_RCVTIMEO and SO_SNDTIMEO:发送或接收超时时间 //传输层       IPPROTO_TCP:表示传输层的基于TCP的协议       IPPROTO_UDP:表示传输层中基于UDP的协议 //网络层       IPPROTO_IP:表示网络层 该层常用的属性:IP_ADD_MEMBERSHIP,加入多播组       参数3:对应层中属性的名称 //上面对应的层级的属性       参数4:参数3属性的值,一般为int类型,其他类型,会给出 //单独开辟出来int空间       参数5:参数4的大小 //get与set有不同,get需要传地址,set直接sizeof()即可       返回值:成功返回0,失败返回-1并置位错误码 //测试套接字地址默认是否能快速重用  eg:char reuse = -1;      //接收获取下来的数据 socklen_t reuselen ;       //接收大小 if(getsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, &reuselen)==-1) { perror(\"getscokopt error\"); return -1; }//设置端口号快速重用 int reu = 1; if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reu, sizeof(reu)) == -1) { perror(\"setsockopt error\"); return -1; } 
runtime_error()异常处理

runtime_error 是 C++ 标准库中用于表示运行时逻辑错误的异常类

设计目的:报告程序运行时无法通过逻辑检查的外部错误(例如文件操作失败、网络中断等),而非代码逻辑本身的错误

异常类型 触发原因 检测时机 logic_error 程序逻辑错误(如参数无效、越界访问) 编译时或运行前 runtime_error 外部环境或运行时条件异常(如系统资源不足、硬件故障) 运行时动态检测 domain_error 数学运算域错误(如对负数开平方) 运行时 bad_alloc 内存分配失败(继承自 exception,非 runtime_error 子类) 运行时
1.4.2server开始函数代码块
bool Server::start(){ // 启动监听 if (listen(sfd_, 5) < 0) { cerr << \"listen error\" << endl; return false; } // 设置运行状态 running_ = true; // 表示可以接受客户端请求 cout << \"Server started on \" << ip_ << \" : \" << port_ << endl; // 等待客户端连接请求:循环完成 while (running_) { sockaddr_in client_addr;  // 接收客户端地址信息结构体 socklen_t addr_len = sizeof(client_addr); // 接收客户端地址信息结构体的大小 // 接收客户端连接请求操作 int cfd = accept(sfd_, (struct sockaddr *)&client_addr, &addr_len); if (cfd < 0) { if (running_) { cerr << \"accept error\" << endl; } continue; } // 创建一个分支线程,在分支线程中,进行跟客户端的通信 thread([this, cfd, client_addr]()  { cout<<\"Client connected :\"<<inet_ntoa(client_addr.sin_addr)<<\":\"<<ntohs(client_addr.sin_port)<<endl; //调用客户端线程处理函数 handleClient(cfd,client_addr); }) .detach(); // 将线程设置成分离态,资源由系统回收 } return true; }
thread线程体(C++11系列)
1. 全局函数作为线程体
#include  void global_func(int x, const std::string& s) { std::cout << \"全局函数: \" << x << \", \" << s << std::endl; } int main() { std::thread t(global_func, 10, \"Hello\"); // 直接传递函数指针和参数 t.join(); }//参数1:函数结构体,参数2,参数3...都是传入对应的函数结构体里当成对应的参数

特点:默认按值传递(参数被复制到线程独立存储中)


std::ref

需传递引用时,使用std::refstd::cref包装(避免悬空引用)**常量包装用cref **ref的详细知识

std::ref()C++11 引入的标准库函数(定义在 头文件中),用于将对象包装成可拷贝的引用包装器(std::reference_wrapper),解决函数式编程和模板中传递引用时的值拷贝问题

  1. 函数式编程(std::bind, std::function
void func(int& a) { a++; }int val = 0;auto bound = std::bind(func, val); // 错误:val 被拷贝,修改不影响原值 auto bound_ref = std::bind(func, std::ref(val)); // 正确:修改 val 
  1. 多线程(std::thread
void worker(int& data) { data = 42; }int value = 0;std::thread t(worker, value); // 错误:value 被拷贝std::thread t_ref(worker, std::ref(value)); // 正确:修改 value 
  1. 模板参数推导
template void template_func(T arg) { arg++; }int num = 1;template_func(num); // 值传递,num 不变 template_func(std::ref(num)); // 引用传递,num = 2 

2. 类成员函数作为线程体
 class MyClass { public: void member_func(int data) { std::cout << \"成员函数: \" << data << std::endl; } }; int main() { MyClass obj; std::thread t(&MyClass::member_func, &obj, 100); // 传递对象指针和参数 t.join(); }//替代版本 // 使用lambda绑定成员函数std::thread t([&obj] { obj.member_func(100); });

特点:需传递成员函数指针对象实例指针(或引用)

3. 仿函数(Functor)作为线程体
4. Lambda表达式作为线程体
int main() { int local_var = 42; std::thread t([local_var] { // 按值捕获局部变量 std::cout << \"Lambda: \" << local_var << std::endl; }); t.join(); }eg: // 创建一个分支线程,在分支线程中,进行跟客户端的通信 //this用来调用handleClient函数 thread([this, cfd, client_addr]()  { cout<<\"Client connected :\"<<inet_ntoa(client_addr.sin_addr)<<\":\"<<ntohs(client_addr.sin_port)<<endl; //调用客户端线程处理函数 handleClient(cfd,client_addr); }) .detach(); // 将线程设置成分离态,资源由系统回收

注意事项

  1. 参数传递规则

    • 参数默认拷贝到线程内部存储(即使函数声明为引用)
    • 强制传递引用需用 std::ref:、
    int x = 10;std::thread t([](int& y) { y *= 2; }, std::ref(x)); // x被修改为20
  2. 线程所有权管理

    • 必须调用 join()detach() 避免 std::terminate
    • 使用 std::jthread(C++20) 可自动合并线程
  3. 异常安全

 std::thread t(...); try { /* 可能抛异常的操作 */ } catch (...) { t.join(); // 确保异常时线程不泄漏 throw; } t.join(); 
lambda表达式

Lambda 表达式是 C++11 引入的核心特性,用于创建匿名函数对象,可简化代码并增强灵活性

[capture-list] (params) mutable? noexcept? -> return-type? { body }
组成部分 作用说明 示例 捕获列表 [] 指定外部变量的捕获方式(值/引用) [x](值捕获)、[&y](引用捕获) 参数列表 () 定义函数参数(可省略) (int a, string b) mutable 允许修改按值捕获的副本(默认捕获变量为 const[=]() mutable { x++; } noexcept 声明不抛出异常 [ ]() noexcept { ... } 返回类型 -> 显式指定返回值类型(可省略,编译器自动推导) [ ]() -> int { return 42; } 函数体 {} 实现逻辑 { cout << \"Hello\"; }

捕获列表的 4 种方式

捕获形式 行为 适用场景 [ ] 不捕获任何外部变量 仅依赖参数或全局变量 4 [=] 值捕获所有外部变量(创建副本) 需保留当前状态,避免后续修改影响 2 [&] 引用捕获所有外部变量(直接操作原变量) 需修改外部变量或减少拷贝开销 3 混合捕获 灵活组合值/引用捕获(如 [x, &y]) 精细控制变量访问权限
 int count = 0; thread t([&] { count++; // 直接操作外部变量 }); t.join();class MyClass { void run() { thread t([this] { this->process(); }); // 捕获 this }};
1.4.2 server处理客户端请求函数
void Server::handleClient(int cfd, sockaddr_in client_addr){ Msg msg; // 用于存储收发的数据 while (1) { // 接收客户端消息 ssize_t recv_len = recv(cfd, &msg, sizeof(msg), 0); if (recv_len <= 0) { if (recv_len == 0) // 当前客户端已下线 { cout << \"Client disconnect :\" << inet_ntoa(client_addr.sin_addr) << endl; } else { cerr << \"recv error\" <registerUser(msg.name, msg.text); strcpy(response.text, success ? \"**OK**\" : \"**EXISTS**\"); // 如果成功注册则回复OK,否则回复EXISTS break; } case L: // 登录功能 { // 执行登录操作 bool is_online = false;  // 返回是否在线 bool success = db_manager_->loginUser(msg.name, msg.text, is_online); // 执行登录函数 if (success) { strcpy(response.text, is_online ? \"**EXISTS**\" : \"**OK**\");  // 根据是否已经在线,进行回复 } else { strcpy(response.text, \"**FAIL**\"); // 表示登录失败 } break; } case Q: // 退出功能 { // 执行退出操作 db_manager_->logoutUser(msg.name); close(cfd); break; } case S: // 搜查功能 { // 执行搜查操作 string meaning; // 报错单词含义 if (db_manager_->querryWord(msg.text, meaning)) { // 成功找到,将单词和含义一起回复给客户端 snprintf(response.text, sizeof(response.text), \"%s %s\", msg.text, meaning.c_str()); } else { strcpy(response.text, \"Not Found\"); // 表示没找到 } // 保存历史记录 time_t now = time(NULL);  // 获取当前系统时间对应的秒数 tm *local = localtime(&now); // 将以秒数为单位的时间转变成时间的结构体 char time_str[20] = \"\";  // 保存当前系统时间的字符串 strftime(time_str, sizeof(time_str), \"%Y-%m-%d %H:%M:%S\", local); // string format time 转为字符串 // 将结构体中的年月日时分秒转变成格式串 // 调用历史查询记录 db_manager_->recordHistory(msg.name, msg.text, meaning, time_str); break; } case H: // 历史功能 { // 执行历史操作 string history; // 保存得到的历史记录 if (db_manager_->getHistory(msg.name, history)) { // 执行成功,history就有历史记录 strncpy(response.text, history.c_str(), sizeof(response.text));  // 将历史记录发送给客户端 } else { strcpy(response.text, \"No history\"); // 当前用户没有进行查单词操作 } break; } default: strcpy(response.text, \"valid Command\"); // 表示请求非法操作 break; } // 发送请求 response.networkByteOrder(); // 转变成网络字节序 if (send(cfd, &response, sizeof(response), 0) < 0) { cerr << \"send error\" << endl; break; } //判断如果是查看历史记录,那么最后还要给出一个历史查询接收的情况 if(msg.type == H) { strcpy(response.text,\"**OVER**\"); send(cfd,&response,sizeof(response),0); } } close(cfd); // 关闭客户端套接字}
strncpyvsstrcpy比对

strncpy 的核心功能是 从源字符串(src)复制最多 n 个字符到目标缓冲区(dest

对比项 strcpy strncpy 参数设计 char* dest, const char* src char* dest, const char* src, size_t n 终止符处理 自动复制src\\0dest末尾(\\0为空字符串) 若src长度≥n,不会添加\\0;若src长度<n,填充\\0至n字符 复制行为 完全复制src,直到遇到\\0 仅复制前n个字符(可能截断)
char dest[5];strncpy(dest, \"abcdef\", 5); // dest内容为\'a\',\'b\',\'c\',\'d\',\'e\'(无\\0)printf(\"%s\", dest); // 可能输出乱码或崩溃 
snprintf 函数
int snprintf(char *str, size_t size, const char *format, ...); 参数说明: str:目标缓冲区指针,用于存储格式化后的字符串。 size:缓冲区总容量(单位:字节),包含终止符 \\0 所需空间。 format:格式化字符串(与 printf 语法一致)。 ...:可变参数列表,对应 format 中的占位符。 返回值: 成功时:返回预期写入的字符数(不包括 \\0),即使缓冲区不足也会计算完整长度。 错误时:返回负值(如编码错误或非法参数)。 eg:snprintf(response.text, sizeof(response.text), \"%s %s\", msg.text, meaning.c_str());

sprintf参数只在size处缺少,其余都正常处理,但尽量少使用。

C语言时间函数
// 保存历史记录 time_t now = time(NULL); // 获取当前系统时间对应的秒数 tm *local = localtime(&now);  // 将以秒数为单位的时间转变成时间的结构体 char time_str[20] = \"\"; // 保存当前系统时间的字符串 strftime(time_str, sizeof(time_str), \"%Y-%m-%d %H:%M:%S\", local); // string format time 转为字符串 // 将结构体中的年月日时分秒转变成格式串 return string(time_str);

time() 是C标准库()中获取系统时间戳的核心函数

time_t time(time_t *timer); 参数 timer: 若传入 NULL(常用):直接返回当前时间戳。 若传入 time_t 变量地址:将时间戳存入该变量并返回。 返回值: 成功:返回自 Epoch(1970-01-01 00:00:00 UTC) 起的秒数(time_t 类型)。 失败:返回 (time_t)-1(如系统不支持时间获取)。 time_t now = time(NULL); // 当前时间戳存入 now printf(\"Seconds since Epoch: %ld\\n\", now); 

localtime()将UTC时间戳(time_t转换成本地时区时间

struct tm *localtime(const time_t *timer); 输入:指向UTC时间戳的指针(如 time(NULL) 的结果)输出:指向 tm 结构体的指针(内含分解时间字段) struct tm { int tm_year; // 年份 - 1900(2025年值为125) int tm_mon; // 月份 0-11(0=1月) int tm_mday; // 日 1-31 int tm_hour; // 时 0-23 int tm_min; // 分 0-59 int tm_sec; // 秒 0-60(60用于闰秒) }; 

strftime(time_str, sizeof(time_str), \"%Y-%m-%d %H:%M:%S\", local);

时间格式化:将tm结构体转换为人类可读字符串

符号 含义 2025-07-23 16:48示例 %Y 4位年份 2025 %m 2位月份(01-12) 07 %d 2位日期(01-31) 23 %H 24小时制小时(00-23) 16 %M 分钟(00-59) 48 %S 秒(00-60) 00