【linux】网络套接字编程(五)TCP服务器与客户端的实现——多进程版,多线程版

【linux】网络套接字编程(五)TCP服务器与客户端的实现——多进程版,多线程版
小编个人主页详情<—请点击
小编个人gitee代码仓库<—请点击
linux系统编程专栏<—请点击
linux网络编程专栏<—请点击
倘若命中无此运,孤身亦可登昆仑,送给屏幕面前的读者朋友们和小编自己!

目录


前言

【linux】网络套接字编程(四)TCP服务器与客户端的实现(单进程/单线程的TCP服务器),setsockopt,listen,accept,telnet,connect,inet_pton——书接上文 详情请点击<——,本文会在上文的基础上进行讲解,所以对上文不了解的读者友友请点击前方的蓝字链接进行学习
本文由小编为大家介绍——【linux】网络套接字编程(五)TCP服务器与客户端的实现——多进程版,多线程版

一、多进程版

  1. 由于是多进程,所以对于创建子进程不熟悉的读者友友,可以点击后方蓝字链接进行学习
    (一)fork创建子进程,exit终止子进程,详情请点击<——
    (二)waitpid进程等待,详情请点击<——
    (三)孤儿进程,详情请点击<——

逻辑一

  1. 在之前的文章中,小编讲解了TCP服务器与客户端的实现单进程/单线程版,本文小编会在上文的基础上继续进行扩展四个版本,所以本文会在上文的基础上进行讲解,所以对上文不了解的读者友友请点击后方蓝字链接进行学习 详情请点击<——
  2. 父进程fork创建子进程后,子进程的文件描述符是拷贝的父进程的文件描述符表,所以父进程的资源,子进程同样也可以进行访问,那么对于子进程来讲,子进程可以看到listensock_这个网络文件描述符对应的文件打开对象,但是我们并不期望子进程对这个文件打开对象进行访问或者修改等操作,因为我服务器fork创建出子进程的目的,就是让子进程拿着sockfd然后作为服务器和客户端进行通信服务的,所以开始进入子进程,那么我们就close将listensock_这个网络文件描述符进行关闭
  3. 小编,小编,可是你直接close关闭对应的网络文件描述符listensock_和sockfd之后,不会对对方进程有什么影响吗?即子进程close关闭网络文件描述符listensock_会不会直接将对应的文件打开对象释放掉,然后造成父进程无法继续监听,父进程close关闭网络文件描述符sockfd会不会直接将对应的文件打开对象释放掉,然后造成子进程无法使用sockfd与客户端进行通信?
  4. 不会的,因为每一个文件打开对象都会维护一个引用计数,用于表征当前有几个进程正在使用当前的文件打开对象,只有最后一个使用文件打开对象的进程close关闭文件打开对象的时候,文件打开对象才会被释放
  5. 所以当父进程fork创建子进程后,listensock_和sockfd对应的文件打开对象的引用计数会变成2,子进程close关闭网络文件描述符listensock_以及父进程close关闭网络文件描述符sockfd之后,listensock_和sockfd对应的文件打开对象都只会将引用计数从2减成1,所以文件打开对象不会释放,不会对进程的正常使用文件打开对象造成影响
  6. 所以服务器进程fork创建子进程,当fork的返回值id等于0的时候,代表是子进程,那么我们让子进程执行close关闭listensock_即可,然后子进程执行和客户端的通信服务Service,当执行完毕后close关闭文件描述符socket即可,最后子进程已经完成了任务,我们不期望子进程继续向后执行,所以exit终止子进程
  7. 那么当fork的返回值id不等于0的时候,那么走到下面就是父进程,那么我们先让父进程close关闭socket即可,然后父进程总要管并且等待子进程,所以接下来就让父进程waitpid等待子进程即可,当父进程等待子进程成功之后,然后父进程就会继续循环然后继续监听连接

那么对于当前的服务器父进程来讲,我父进程都已经将网络文件描述符sockfd交给子进程了让子进程和客户端进行通信服务,所以我父进程已经不需要sockfd这个网络文件描述符,所以我父进程直接close将sockfd这个网络文件描述符关闭即可,可是父进程总要管子进程吧,所以父进程waitpid就要等待子进程即可

在这里插入图片描述

这里的多进程版是指的服务器,即当服务器收到accept连接成功后,当前的服务器进程fork创建子进程,让子进程执行服务器和客户端的通信服务,那么父进程等待子进程即可

在这里插入图片描述
voidStartServer(){lg(Info,"tcpserver is running...");for(;;){structsockaddr_in client; socklen_t len =sizeof(client);int sockfd =accept(listensock_,(structsockaddr*)&client,&len);if(sockfd <0){lg(Warning,"accept error, errno: %d, errstring: %s", errno,strerror(errno));continue;}uint16_t clientport =ntohs(client.sin_port);char clientip[32];inet_ntop(AF_INET,&(client.sin_addr), clientip,sizeof(clientip));lg(Info,"get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, clientip, clientport);//version 1 单进程/单线程版// Service(sockfd, clientip, clientport);// close(sockfd);//version 2 多进程版 pid_t id =fork();if(id ==0){//childclose(listensock_);Service(sockfd, clientip, clientport);close(sockfd);exit(0);}close(sockfd); pid_t rid =waitpid(id,nullptr,0);if(rid == id) std::cout <<"father wait success"<< std::endl;}}
测试
  1. 如上,当我们启动第二个客户端然后想要让第二个客户端和服务器进行连接通信的时候,服务器没有响应,我们想要的是服务器创建出子进程之后,服务器让子进程去执行与客户端通信,然后服务器继续去监听连接,如果还有其他客户端也来连接了,那么就再创建子进程,当新创建的子进程与其它客户端进行通信,所以为什么,我们的服务器不响应了呢?
  2. 因为在我们的代码逻辑中,当前服务器作为子进程的父进程正在waitpid等待子进程,所以由于此时子进程并且没有结束和客户端的通信,即子进程并没有终止,所以服务器就会一直等待子进程,所以服务器压根没有执行accept的代码,所以也就自然不会对第二个客户端的连接进行响应了
  3. 那么照你的意思,我把子进程的服务终止了之后,服务器就会等待子进程成功,那么服务器就可以响应第二个客户端的连接请求了,对的,没错,可是如何终止子进程呢?很简单
  4. 一瞬间,当小编使用ctrl+c终止第一个客户端的时候,第二个客户端就会和服务器连接成功,并且进行服务,无误
  5. 可是这个样让服务器去等待子进程退出,有点太挫了,能不能让服务器一直处于监听请求,然后如果监听成功之后,将sockfd交给子进程,子进程去和客户端进行通信,此时作为父进程的服务器很短的时间内就可以继续进入监听请求的状态,完全可以,那么就是下面小编的逻辑二的多线程版本

我们使用ctrl+c终止掉第一个客户端即可,所以此时第一个客户端正在与子进程进行通信的写端就会关闭,那么子进程的读端就会读到0,并且返回0,并且我们在Service函数中也对这个返回值0进行了break退出死循环处理,所以子进程就会结束服务,然后退出终止了

在这里插入图片描述

如上,可见一个服务器,一个客户端的场景还是没有问题的,可是小编若是在添加一个客户端呢?

在这里插入图片描述

那么我们接下来依次运行服务器,客户端

在这里插入图片描述

逻辑二

  1. 既然子进程不行,那么子进程的子进程呢?即我们尝试让孙子进程执行与客户端通信
  2. 所以当服务器创建子进程之后,让这个子进程再次创建子进程,然后让这个服务器的子进程立即exit退出,所以服务器几乎在一瞬间就可以等待到服务器的子进程退出
  3. 不是,小编,你这有问题呀,那么服务器的子进程的子进程不就没人管了,即服务器的子进程再次创建的子进程和服务器的关系就是子孙关系,即当前服务器的孙子进程就没人管了,进程如果fork子进程之后,只需要管好自己的子进程即可,至于孙子进程,我服务器不关心,谁关心?
  4. 操作系统关心呀,因为服务器虽然将它的子进程回收了,但是服务器的子进程可是服务器的孙子进程的父进程呀,所以孙子进程就成为了孤儿进程,此时孙子进程就会被操作系统领养,领养就领养吧,没事,也不会造成资源泄露,并且孙子进程还会执行Service和服务端进行通信,一举两得,所以此时服务端就会立即等待子进程成功,那么服务器就可以继续循环然后执行accept监听请求了
  5. 所以从此以后,服务器面对再多的客户端的连接请求都可以从容不迫的进行连接了,并且将与客户端进行通信的任务交给孙子进程来做,完美
voidStartServer(){lg(Info,"tcpserver is running...");for(;;){structsockaddr_in client; socklen_t len =sizeof(client);int sockfd =accept(listensock_,(structsockaddr*)&client,&len);if(sockfd <0){lg(Warning,"accept error, errno: %d, errstring: %s", errno,strerror(errno));continue;}uint16_t clientport =ntohs(client.sin_port);char clientip[32];inet_ntop(AF_INET,&(client.sin_addr), clientip,sizeof(clientip));lg(Info,"get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, clientip, clientport);//version 1 单进程/单线程版// Service(sockfd, clientip, clientport);// close(sockfd);//version 2 多进程版 pid_t id =fork();if(id ==0){//childclose(listensock_);if(fork()>0)exit(0);//grandchildService(sockfd, clientip, clientport);close(sockfd);exit(0);}close(sockfd); pid_t rid =waitpid(id,nullptr,0);if(rid == id) std::cout <<"father wait success"<< std::endl;}}
测试
  1. 小编,小编,可是各自与这两个客户端进行通信的两个孙子进程真的成孤儿进程,然后被操作系统领养了吗?即孙子进程的父进程变成1操作系统了吗?所以我们使用如下指令查看一下

无误,第二个客户端一启动,那么服务器就等待子进程成功了,此时与客户端进行通信的是服务器的孙子进程,所以服务器就继续循环,然后监听其它客户端的连接请求,那么如下我们在第二个客户端输入可以进行正常通信,无误

在这里插入图片描述

那么关键来了,第二个客户端可以通信吗?所以此时我们开始启动第二个客户端

在这里插入图片描述

那么我们在第一个客户端进行输入通信,无误

在这里插入图片描述

所以此时我们编译,然后运行服务器,客户端,此时第一个客户端运行起来之后,立马服务器就等待子进程成功了,此时与客户端进行通信的是服务器的孙子进程,所以服务器就继续循环,然后监听其它客户端的连接请求

在这里插入图片描述
ps axj |head -1 &&ps axj |grep tcp |grep -v grep

结果如下,孙子进程的父进程变成1操作系统了,无误,至此多线程版的TCP服务器与客户端的实现完成,TCP服务器再也不用怕多个客户端的连接请求了

在这里插入图片描述

二、多线程版

  1. 由于是多线程,所以对于创建多线程不熟悉的读者友友,可以点击后方蓝字链接进行学习
    (一)pthread_create,详情请点击<——
    (二)pthread_datech,详情请点击<——
  2. 那么下面我们就pthread_create创建线程即可,那么线程在执行线程函数Routine的时候应该有什么呢?线程要调用Service函数,那么首先要有网络文件描述符sockfd,其次是客户端IP地址,客户端端口号port
  3. 我们期望将这个线程函数Routine放在类中,这样具有一定的封装性,其实更严格点来讲可以将这个线程函数设置成私有的,我们不期望外部可以直接调用,而是在类内使用这个线程函数Routine,但是这里小编便捷点就不设置了
  4. 那么对于这个线程函数Routine来讲,由于小编将其放在了类内,所以第一个参数会有一个this指针,所以这样就和pthread_create中要求的线程函数类型不匹配了,所以我们期望第一个参数是void*类型的变量,所以我们就使用static修改这个线程函数Routine,这样的话这个线程函数Routine就没有this指针了,并且类型匹配,也可以传参pthread_create了
  5. 但是当前的新线程要执行和客户端进行通信的服务就要执行Service,Service这个函数是类内的成员函数,而新线程执行的Routine线程函数是static静态的成员函数,静态的成员函数没有this指针,所以不能够调用普通的成员函数Service,所以呢?我们还要给Routine传参this指针,可是这里Routine传参的不仅仅是this指针,还需要有网络文件描述符sockfd,其次是客户端IP地址,客户端端口号port,那么怎么办呢?类
  6. 所以我们将这些变量封装一个类ThreadData不就好了,那么pthread_create给线程函数传参的时候,直接传参类实例化的对象的指针即可,这样新线程在Routine函数的内部就可以拿到这些变量了,所以下面封装这个类ThreadData,并且将这些变量,this指针,网络文件描述符sockfd,其次是客户端IP地址,客户端端口号port,设置为类ThreadData的成员变量,并且在构造函数中进行初始化即可
  7. 由于小编将ThreadData这个类放在了TcpServer类的前面,所以这里如果在ThreadData的成员变量中直接使用TcpServer*定义类的指针,那么由于编译器编译是从向前找,所以此时就会找不到TcpServer这个类,自然也就无法使用TcpServer这个类定义指针了,所以也就会编译报错了,那么我们使用class TcpServer;提前声明一下即可解决这个问题
  8. 我们知道线程也是需要被等待pthread_join的,但是这里的主线程很明显不想等待,这里的主线程作为服务器想要继续向后执行,然后accept监听其它客户端的连接请求,所以主线程不想等待,那么该如何办呢?很简单线程分离即可
  9. 很明显主线程这里认为等待新线程是一种负担,主线程并不想等待新线程,所以让新线程执行线程函数Routine的时候,上来先pthread_join线程分离即可,这样当新线程退出的时候,系统会自动将新线程回收释放新线程对应的资源,这样主线程就不需要pthread_join新线程了,所以主线程就可以继续accept监听其它客户端的连接请求,一旦收到了连接请求,那么将网络文件描述符sockfd交给新线程去给客户端进行通信即可
  10. 那么此时我们继续看线程函数,线程分离之后,然后进行参数转换为ThreadData的指针td,然后就可以通过td找到this指针然后传入对应的参数调用Service函数让新线程去和客户端通信了
  11. 当Service函数执行完毕,那么此时就可以close关闭网络文件描述符sockfd了,注意这里的td指针也是new出来的,所以为了避免内存泄漏,这里我们要及时的对td指针delete释放,最后线程函数需要一个返回值,这里我们返回nullptr即可
  12. 这里注意,由于我们引入了线程,所以在服务器进行编译的时候要添加-lpthread选项,以便编译器找到pthread库所在的位置进行链接
classTcpServer;classThreadData{public:ThreadData(int fd,const std::string& ip,constuint16_t& p, TcpServer* t):sockfd(fd),clientip(ip),clientport(p),tsvr(t){}public:int sockfd; std::string clientip;uint16_t clientport; TcpServer* tsvr;};
staticvoid*Routine(void* args){pthread_detach(pthread_self()); ThreadData* td =static_cast<ThreadData*>(args); td->tsvr->Service(td->sockfd, td->clientip, td->clientport);close(td->sockfd);delete td;returnnullptr;}voidStartServer(){lg(Info,"tcpserver is running...");for(;;){structsockaddr_in client; socklen_t len =sizeof(client);int sockfd =accept(listensock_,(structsockaddr*)&client,&len);if(sockfd <0){lg(Warning,"accept error, errno: %d, errstring: %s", errno,strerror(errno));continue;}uint16_t clientport =ntohs(client.sin_port);char clientip[32];inet_ntop(AF_INET,&(client.sin_addr), clientip,sizeof(clientip));lg(Info,"get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, clientip, clientport);//version 1 单进程/单线程版// Service(sockfd, clientip, clientport);// close(sockfd);//version 2 多进程版// pid_t id = fork();// if(id == 0)// {// //child// close(listensock_);// if(fork() > 0)// exit(0);// //grandchild// Service(sockfd, clientip, clientport);// close(sockfd);// exit(0);// }// close(sockfd);// pid_t rid = waitpid(id, nullptr, 0);// if(rid == id)// std::cout << "father wait success" << std::endl;//version 3 多线程版 ThreadData* td =newThreadData(sockfd, clientip, clientport,this); pthread_t tid;pthread_create(&tid,nullptr, Routine, td);}}
测试
  1. 忘记观察系统中的轻量级线程了,那么小编将服务器和客户端重新运行起来,所以我们使用如下执行查看一下系统中的线程,可以看到tcpserver对应的的确有三个线程正在运行,一个主线程,两个新线程,其中这两个新线程对应负责两个客户端进程,无误

最后ctrl+c退出服务端,无误

在这里插入图片描述

然后ctrl+c退出第一个客户端,退出第二个客户端,无误

在这里插入图片描述

接下来运行第二个客户端,然后进行通信,无误

在这里插入图片描述

接下来运行第一个客户端,并且进行通信,无误

在这里插入图片描述

所以我们接下来进行编译服务器与客户端的代码,然后运行服务器

在这里插入图片描述
ps -aL |head -1 &&ps -aL |grep tcp 
在这里插入图片描述

三、源代码

makefile

all:tcpserver tcpclient tcpserver:Main.cc g++ -o $@ $^ -std=c++11 -lpthread tcpclient:TcpClient.cc g++ -o $@ $^ -std=c++11 .PHONT:clean clean: rm -f tcpserver tcpclient 

TcpServer.hpp

#include<iostream>#include<string>#include<cstring>#include<unistd.h>#include<pthread.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>#include<sys/wait.h>#include"Log.hpp"constint defaultfd =-1;constuint16_t defaultport =8080;const std::string defaultip ="0.0.0.0";constint backlog =10;extern Log lg;enum{ UsageError =1, SocketError, BindError, ListenError };classTcpServer;classThreadData{public:ThreadData(int fd,const std::string& ip,constuint16_t& p, TcpServer* t):sockfd(fd),clientip(ip),clientport(p),tsvr(t){}public:int sockfd; std::string clientip;uint16_t clientport; TcpServer* tsvr;};classTcpServer{public:TcpServer(constuint16_t&port = defaultport,const std::string &ip = defaultip):listensock_(defaultfd),port_(port),ip_(ip){}voidInitServer(){ listensock_ =socket(AF_INET, SOCK_STREAM,0);if(listensock_ <0){lg(Fatal,"create socket error, errno: %d, errstring: %s", errno,strerror(errno));exit(SocketError);}lg(Info,"create socket success, listensock_: %d", listensock_);//防止偶发性服务器无法立即重启int opt =1;setsockopt(listensock_, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT,&opt,sizeof(opt));structsockaddr_in server;memset(&server,0,sizeof(server)); server.sin_family = AF_INET; server.sin_port =htons(port_);inet_aton(ip_.c_str(),&(server.sin_addr)); socklen_t len =sizeof(server);if(bind(listensock_,(structsockaddr*)&server, len)<0){lg(Fatal,"bind error, errno: %d, errstring: %s", errno,strerror(errno));exit(BindError);}lg(Info,"bind socket success, listensock_: %d", listensock_);if(listen(listensock_, backlog)<0){lg(Fatal,"listen error, errno: %d, errstring: %s", errno,strerror(errno));exit(ListenError);}lg(Info,"listen socket success, listensock_: %d", listensock_);}staticvoid*Routine(void* args){pthread_detach(pthread_self()); ThreadData* td =static_cast<ThreadData*>(args); td->tsvr->Service(td->sockfd, td->clientip, td->clientport);close(td->sockfd);delete td;returnnullptr;}voidStartServer(){lg(Info,"tcpserver is running...");for(;;){structsockaddr_in client; socklen_t len =sizeof(client);int sockfd =accept(listensock_,(structsockaddr*)&client,&len);if(sockfd <0){lg(Warning,"accept error, errno: %d, errstring: %s", errno,strerror(errno));continue;}uint16_t clientport =ntohs(client.sin_port);char clientip[32];inet_ntop(AF_INET,&(client.sin_addr), clientip,sizeof(clientip));lg(Info,"get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, clientip, clientport);//version 1 单进程/单线程版// Service(sockfd, clientip, clientport);// close(sockfd);//version 2 多进程版// pid_t id = fork();// if(id == 0)// {// //child// close(listensock_);// if(fork() > 0)// exit(0);// //grandchild// Service(sockfd, clientip, clientport);// close(sockfd);// exit(0);// }// close(sockfd);// pid_t rid = waitpid(id, nullptr, 0);// if(rid == id)// std::cout << "father wait success" << std::endl;//version 3 多线程版 ThreadData* td =newThreadData(sockfd, clientip, clientport,this); pthread_t tid;pthread_create(&tid,nullptr, Routine, td);}}voidService(int sockfd,const std::string& clientip,constuint16_t& clientport){while(true){char inbuffer[4096]; ssize_t n =read(sockfd, inbuffer,sizeof(inbuffer)-1);if(n >0){ inbuffer[n]=0; std::cout <<"client say# "<< inbuffer << std::endl; std::string echo_string ="tcpserver echo# "; echo_string += inbuffer;write(sockfd, echo_string.c_str(), echo_string.size());}elseif(n ==0){lg(Info,"%s:%d quit, server colse sockfd: %d", clientip.c_str(), clientport, sockfd);break;}else{//异常lg(Warning,"read error, sockfd: %d, client ip: %s, client port: %d", sockfd, clientip.c_str(), clientport);break;}}}~TcpServer(){if(listensock_ >0)close(listensock_);}private:int listensock_;uint16_t port_; std::string ip_;};

Main.cc

#include<iostream>#include<memory>#include"TcpServer.hpp"voidUsage(const std::string str){ std::cout <<"\n\tUsage: "<< str <<" port[1024+]\n"<< std::endl;}intmain(int argc,char* argv[]){if(argc !=2){Usage(argv[0]);exit(UsageError);}uint16_t port = std::stoi(argv[1]); std::unique_ptr<TcpServer>server(newTcpServer(port)); server->InitServer(); server->StartServer();return0;}

TcpCilent.cc

#include<iostream>#include<string>#include<unistd.h>#include<cstring>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>voidUsage(const std::string& str){ std::cout <<"\n\tUsage: "<< str <<" serverip serverport"<< std::endl;}// ./tcpclient serverip serverportintmain(int argc,char* argv[]){if(argc !=3){Usage(argv[0]);return0;} std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);int sockfd =socket(AF_INET, SOCK_STREAM,0);if(sockfd <0){ std::cerr <<"socket create err"<< std::endl;return1;}structsockaddr_in server;memset(&server,0,sizeof(server)); server.sin_family = AF_INET; server.sin_port =htons(serverport);inet_pton(AF_INET, serverip.c_str(),&(server.sin_addr)); socklen_t len =sizeof(server);//客户端发起connect请求的时候,操作系统会自动进行端口号的随机bind绑定int n =connect(sockfd,(structsockaddr*)&server, len);if(n <0){ std::cerr <<"connect err..."<< std::endl;return2;} std::string message;char inbuffer[4096];while(true){ std::cout <<"Please Enter# "; std::getline(std::cin, message);int n =write(sockfd, message.c_str(), message.size());if(n <0){ std::cerr <<"write err"<< std::endl;break;} n =read(sockfd, inbuffer,sizeof(inbuffer)-1);if(n >0){ inbuffer[n]=0; std::cout << inbuffer << std::endl;}else{break;}}close(sockfd);return0;}

Log.hpp

#pragmaonce#include<iostream>#include<string>#include<ctime>#include<cstdio>#include<cstdarg>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#defineSIZE1024#defineInfo0#defineDebug1#defineWarning2#defineError3#defineFatal4#defineScreen1//输出到屏幕上#defineOnefile2//输出到一个文件中#defineClassfile3//根据事件等级输出到不同的文件中#defineLogFile"log.txt"//日志名称classLog{public:Log(){ printMethod = Screen; path ="./log/";}voidEnable(int method)//改变日志打印方式{ printMethod = method;}~Log(){} std::string levelToString(int level){switch(level){case Info:return"Info";case Debug:return"Debug";case Warning:return"Warning";case Error:return"Error";case Fatal:return"Fata";default:return"";}}voidoperator()(int level,constchar* format,...){//默认部分 = 日志等级 + 日志时间 time_t t =time(nullptr);structtm* ctime =localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%d-%d-%d %d:%d:%d]",levelToString(level).c_str(), ctime->tm_year +1900, ctime->tm_mon +1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec); va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer,sizeof(rightbuffer), format, s);va_end(s);char logtxt[2* SIZE];snprintf(logtxt,sizeof(logtxt),"%s %s", leftbuffer, rightbuffer);printLog(level, logtxt);}voidprintLog(int level,const std::string& logtxt){switch(printMethod){case Screen: std::cout << logtxt << std::endl;break;case Onefile:printOneFile(LogFile, logtxt);break;case Classfile:printClassFile(level, logtxt);break;default:break;}}voidprintOneFile(const std::string& logname,const std::string& logtxt){ std::string _logname = path + logname;int fd =open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND,0666);if(fd <0)return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}voidprintClassFile(int level,const std::string& logtxt){ std::string filename = LogFile; filename +="."; filename +=levelToString(level);printOneFile(filename, logtxt);}private:int printMethod; std::string path;}; Log lg;

总结

以上就是今天的博客内容啦,希望对读者朋友们有帮助
水滴石穿,坚持就是胜利,读者朋友们可以点个关注
点赞收藏加关注,找到小编不迷路!

Read more

在 Mac 上完美配置 VSCode 的 C/C++ 开发环境(GCC/G++ 详细教程 )

本文手把手教你如何在 macOS 系统上配置 VSCode 的 C/C++ 开发环境,解决各种常见问题,让你轻松开启 C/C++ 编程之旅! 前言 作为程序员,一个顺手的开发环境至关重要。VSCode 作为轻量级但功能强大的代码编辑器,配合 GCC/G++ 编译器,能够在 Mac 上提供优秀的 C/C++ 开发体验。本文将详细介绍从零开始的完整配置过程。 一、环境准备:安装编译工具 1.1 安装 Xcode Command Line Tools(推荐首选) 打开终端,执行以下命令: xcode-select --install 执行后会弹出安装对话框,点击"安装"即可。

By Ne0inhk
Flutter 组件 graphql 的适配 鸿蒙Harmony 实战 - 驾驭标准化分布式图形协议、实现鸿蒙端实时订阅与高性能交互网关方案

Flutter 组件 graphql 的适配 鸿蒙Harmony 实战 - 驾驭标准化分布式图形协议、实现鸿蒙端实时订阅与高性能交互网关方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 graphql 的适配 鸿蒙Harmony 实战 - 驾驭标准化分布式图形协议、实现鸿蒙端实时订阅与高性能交互网关方案 前言 在鸿蒙(OpenHarmony)生态的万物互联、极繁交互中台、以及对数据获取灵活性有极致要求的现代应用研发中,“高效的数据检索协议”是应用响应速度的灵魂。面对复杂的社交网络关系查询、实时的行情推送、或是海量状态信息的聚合。如果仅仅依靠传统的 RESTful 接口,那么不仅会导致因为 Over-fetching(获取多余数据)导致的带宽浪费,更会因为频繁的 API 版本演进引入严重的跨端兼容性碎片化问题。 我们需要一种“按需检索、逻辑解耦”的交互艺术。 graphql 是一套专为 Flutter 设计的标准 GraphQL 客户端套件。它通过构建规范的规范化缓存(Normalized Cache)与极其灵活的连接链路(Links)

By Ne0inhk
Flutter 三方库 df_generate_dart_models_core 的鸿蒙化适配指南 - 实现自动化的数据模型代码生成、支持 JSON 反序列化模板定义与工程化规范一致性

Flutter 三方库 df_generate_dart_models_core 的鸿蒙化适配指南 - 实现自动化的数据模型代码生成、支持 JSON 反序列化模板定义与工程化规范一致性

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 df_generate_dart_models_core 的鸿蒙化适配指南 - 实现自动化的数据模型代码生成、支持 JSON 反序列化模板定义与工程化规范一致性 前言 在进行 Flutter for OpenHarmony 的大规模业务逻辑开发时,手动编写海量的 Data Models(POJO/Entity)以及配套的 fromJson/toJson 方法不仅枯燥乏味,还极易引入手写错误。df_generate_dart_models_core 是一个强大的代码生成核心库,它能将原始 JSON 样本或 Schema 自动转化为符合 Dart 规范的数据类代码。本文将指导大家如何将该库集成到鸿蒙项目的工程化提效链路中。 一、原理解析

By Ne0inhk
Flutter 组件 string_stats 的鸿蒙化适配实战 - 驾驭极致文本分析大坝,实现 OpenHarmony 高性能文本审计、字符特征提取与工业级内容解析核

Flutter 组件 string_stats 的鸿蒙化适配实战 - 驾驭极致文本分析大坝,实现 OpenHarmony 高性能文本审计、字符特征提取与工业级内容解析核

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 string_stats 的鸿蒙化适配实战 - 驾驭极致文本分析大坝,实现 OpenHarmony 高性能文本审计、字符特征提取与工业级内容解析核 前言 随着鸿蒙(OpenHarmony)生态深入政企、金融与教育等核心领域,应用中对于“海量文本内容的高性能深度剖析”已经成为了保障系统稳健运行的第一道闸门。无论是实时监控系统中的日志关键词审计,还是编辑器应用中的多维度字符统计,如果开发者只是简单地使用 Dart 原生的字符串操作,在面对数十万乃至百万级别的长文本时,极易由于过度消耗主线程资源导致 UI 掉帧,甚至在重型计算时引发 ANR。 我们需要一种“冷血、精准、高通量”的文本特征提取机制。string_stats 库正是为了这种极致的字符属性审计而生的分析阵列。它通过高效的一阶遍历算法,能够秒级提取文本的行数、字数、高频词以及语法特征。适配到鸿蒙平台后,它不仅能为你的应用提供工业级的统计精度,更是我们构建“

By Ne0inhk