【Linux网络编程】第六弹---构建TCP服务器:从基础到线程池版本的实现与测试详解
✨个人主页: 熬夜学编程的小林
💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】【Linux网络编程】
目录
1、TcpServerMain.cc
2、TcpServer.hpp
2.1、TcpServer类基本结构
2.2、构造析构函数
2.3、InitServer()
2.4、Loop()
2.4.1、Server 0(不靠谱版本)
2.4.2、Server 1(多进程版本)
2.4.3、Server 2(多线程版本)
2.4.4、Server 3(线程池版本)
3、TcpClientMain.cc
4、测试结果
4.1、不靠谱版本
4.2、多进程版本
4.3、多线程版本
4.4、线程池版本
5、完整代码
5.1、Makefile
5.2、TcpClientMain.cc
5.3、TcpServer.hpp
5.4、TcpServerMain.cc
前面几弹使用UDP协议实现了相关功能,此弹使用TCP协议实现客户端与服务端的通信,相比与UDP协议,TCP协议更加可靠,也更加复杂!与UDP类似,我们先写主函数,然后实现相关函数!
1、TcpServerMain.cc
服务端主函数使用智能指针构造Server对象,然后调用初始化与执行函数,调用主函数使用该可执行程序 + 端口号!
// ./tcpserver 8888
int main(int argc,char* argv[])
{if(argc != 2){std::cerr << "Usage: " << argv[0] << " local-post" << std::endl;exit(0);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);tsvr->InitServer();tsvr->Loop();return 0;
}
2、TcpServer.hpp
TcpServer.hpp封装TcpServer类!
枚举常量:
enum
{SOCKET_ERROR,BIND_ERROR,LISTEN_ERROR
};
全局静态变量:
const static uint16_t gport = 8888;
const static int gsockfd = -1;
const static int gblcklog = 8;
2.1、TcpServer类基本结构
TcpServer类的基本成员有端口号,文件描述符,与运行状态!
// 面向字节流
class TcpServer
{
public:TcpServer(uint16_t port = gport);void InitServer();void Loop();~TcpServer();
private:uint16_t _port;int _sockfd; // TODObool _isrunning;
};
2.2、构造析构函数
构造函数初始化成员变量,析构函数无需处理!
注意:此处需要用到两个全局静态变量!
TcpServer(uint16_t port = gport):_port(port),_sockfd(gsockfd),_isrunning(false)
{}~TcpServer()
{}
2.3、InitServer()
InitServer() 初始化服务端!
初始化函数主要分为三步:
- 1、创建socket(类型与UDP不同)
类型需要使用 SOCK_STREAM
- 2、bind sockfd 和 socket addr
- 3、获取连接(与UDP不同)
获取连接需要使用listen函数(将套接字设置为监听模式,以便能够接受进入的连接请求)
listen()
#include <sys/types.h>
#include <sys/socket.h>int listen(int sockfd, int backlog);
参数
sockfd
:这是一个已创建的套接字文件描述符,它应该是一个绑定到某个地址和端口的套接字。backlog
:这个参数定义了内核应该为相应套接字排队的最大连接数(此处暂时使用8)。如果队列已满,新的连接请求可能会被拒绝。需要注意的是,这个值只是内核用于优化性能的一个提示,实际实现可能会有所不同。
返回值
- 成功时,
listen
函数返回 0。 - 失败时,返回 -1,并设置
errno
以指示错误类型。
注意:此处需要用到全局静态变量和枚举常量!
// _sockfd 版本
void InitServer()
{// 1.创建socket_sockfd = ::socket(AF_INET,SOCK_STREAM,0);if(_sockfd < 0){LOG(FATAL,"socket create eror\n");exit(SOCKET_ERROR);}LOG(INFO,"socket create success,sockfd: %d\n",_sockfd); // 3struct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;// 2.bind sockfd 和 socket addrif(::bind(_sockfd,(struct sockaddr*)&local,sizeof(local)) < 0){LOG(FATAL,"bind eror\n");exit(BIND_ERROR);}LOG(INFO,"bind success\n");// 3.因为tcp是面向连接的,tcp需要未来不短地获取连接// 老板模式,随时等待被连接if(::listen(_sockfd,gblcklog) < 0){LOG(FATAL,"listen eror\n");exit(LISTEN_ERROR);}LOG(INFO,"listen success\n");
}
为了测试该函数,先将Loop函数设计成死循环!
Loop()
// 测试
void Loop()
{_isrunning = true;while(_isrunning){sleep(1);}_isrunning = false;
}
2.4、Loop()
Loop() 函数一直执行服务!
执行服务函数主要分为两步:
- 1、获取新连接(accept函数[从已完成连接队列的头部返回下一个已完成连接,如果队列为空,则阻塞调用进程])
accept()
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数
sockfd
:这是一个监听套接字的文件描述符,它应该是一个已经通过socket
函数创建,并通过bind
函数绑定到特定地址和端口,以及通过listen
函数设置为监听模式的套接字。addr
:这是一个指向sockaddr
结构的指针,该结构用于存储接受连接的客户端的地址信息。如果不需要这个信息,可以传递NULL
。addrlen
:这是一个指向socklen_t
类型的变量的指针,用于存储addr
结构的大小。在调用accept
之前,应该将该变量的值设置为addr
结构的大小。在调用返回后,该变量将包含实际返回的地址信息的长度。如果addr
是NULL
,则这个参数也可以是NULL
。
返回值
- 成功时,
accept
函数返回一个新的套接字文件描述符,用于与接受的连接进行通信。这个新的套接字是原始监听套接字的子套接字,它继承了许多属性(如套接字选项),但与原始套接字是独立的。 - 失败时,返回 -1,并设置
errno
以指示错误类型。
因此TcpServer类的_sockfd应该改为_listensockfd!!!
TcpServer类
// 面向字节流
class TcpServer
{
public:TcpServer(uint16_t port = gport):_port(port),_listensockfd(gsockfd),_isrunning(false){}void InitServer(){// 1.创建socket_listensockfd = ::socket(AF_INET,SOCK_STREAM,0);if(_listensockfd < 0){LOG(FATAL,"socket create eror\n");exit(SOCKET_ERROR);}LOG(INFO,"socket create success,sockfd: %d\n",_listensockfd); // 3struct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;// 2.bind sockfd 和 socket addrif(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local)) < 0){LOG(FATAL,"bind eror\n");exit(BIND_ERROR);}LOG(INFO,"bind success\n");// 3.因为tcp是面向连接的,tcp需要未来不短地获取连接// 老板模式,随时等待被连接if(::listen(_listensockfd,gblcklog) < 0){LOG(FATAL,"listen eror\n");exit(LISTEN_ERROR);}LOG(INFO,"listen success\n");}~TcpServer(){}
private:uint16_t _port;int _listensockfd;bool _isrunning;
};
- 2、执行服务(前提是获取到新连接)
执行服务总共有四个版本!
2.4.1、Server 0(不靠谱版本)
Server 0版本直接执行长服务!
Loop()
Loop()函数先获取新连接,获取成功则执行服务函数!
void Loop()
{_isrunning = true;while(_isrunning){struct sockaddr_in client;socklen_t len = sizeof(client);// 1.获取新连接int sockfd = ::accept(_listensockfd,(struct sockaddr*)&client,&len);// 获取失败继续获取if(sockfd < 0){LOG(WARNING,"sccept reeor\n");continue;}InetAddr addr(client);LOG(INFO,"get a new link,client info: %s,sockfd:%d\n",addr.AddrStr().c_str(),sockfd); // 4// 获取成功// version 0 -- 不靠谱版本Server(sockfd,addr);}_isrunning = false;
}
Server()
注意:tcp协议可以直接使用read,write函数读写文件描述符的内容(因为tcp是面向字节流的)!
Server()执行服务,先从文件描述符中读数据,再写数据到文件描述符中!
void Server(int sockfd,InetAddr addr)
{// 长服务while(true){char inbuffer[1024]; // 当做字符串// 1.读文件ssize_t n = ::read(sockfd,inbuffer,sizeof(inbuffer) - 1);if(n > 0){inbuffer[n] = 0;LOG(INFO,"get message from client [%s],message: %s\n",addr.AddrStr().c_str(),inbuffer);std::string echo_string = "[server echo]# ";echo_string += inbuffer;// 2.写文件write(sockfd,echo_string.c_str(),echo_string.size());}// 读到文件结尾else if(n == 0){LOG(INFO,"client %s quit\n",addr.AddrStr().c_str());break;}else{LOG(ERROR,"read error\n",addr.AddrStr().c_str());break;}}::close(sockfd);
}
2.4.2、Server 1(多进程版本)
多进程版本即创建子进程,让子进程执行服务函数,父进程回收子进程,但是如果以阻塞等待回收子进程会有一个问题,如果子进程一直没有退出,那么父进程会一直阻塞!为了解决这个问题,我们可以让子进程再创建一个孙子进程,让孙子进程去执行服务函数,子进程直接退出,父进程回收子进程,孙子进程此时会成为孤儿进程,孤儿进程退出OS会自动回收!
void Loop()
{_isrunning = true;while(_isrunning){struct sockaddr_in client;socklen_t len = sizeof(client);// 1.获取新连接int sockfd = ::accept(_listensockfd,(struct sockaddr*)&client,&len);// 获取失败继续获取if(sockfd < 0){LOG(WARNING,"sccept reeor\n");continue;}InetAddr addr(client);LOG(INFO,"get a new link,client info: %s,sockfd:%d\n",addr.AddrStr().c_str(),sockfd); // 4// 获取成功// version 1 -- 多进程版本pid_t id = fork();if(id == 0){// child::close(_listensockfd); // 建议! if(fork() > 0) exit(0); // 让孙子进程执行服务,保证能不阻塞Server(sockfd,addr);exit(0);}// father::close(sockfd); // 防止文件描述符泄漏(打开的不关闭)int n = waitpid(id,nullptr,0); // 0阻塞等待 if(n > 0){LOG(INFO,"wait child success\n");}}_isrunning = false;
}
2.4.3、Server 2(多线程版本)
多线程版本即让新线程去执行服务函数,但是主线程需要回收新线程,为了做到主线程无需回收新线程,可以让新线程分离,此时无需回收新线程!还有一个问题,类内的成员函数有this指针,而新线程的函数只能有一个参数,此时需要使用静态成员函数,但是使用静态之后还有一个问题,不能看到类内的成员,此处可以使用地址传参,将一个包含sockfd,TcpServer类的指针和InetAddr类的 成员变量的地址传入!
内部类
// 内部类
class ThreadData
{
public:int _sockfd;TcpServer* _self;InetAddr _addr;
public:ThreadData(int sockfd,TcpServer* self,const InetAddr &addr):_sockfd(sockfd),_self(self),_addr(addr){}
};
Loop()
void Loop()
{_isrunning = true;while(_isrunning){struct sockaddr_in client;socklen_t len = sizeof(client);// 1.获取新连接int sockfd = ::accept(_listensockfd,(struct sockaddr*)&client,&len);// 获取失败继续获取if(sockfd < 0){LOG(WARNING,"sccept reeor\n");continue;}InetAddr addr(client);LOG(INFO,"get a new link,client info: %s,sockfd:%d\n",addr.AddrStr().c_str(),sockfd); // 4// 获取成功// version 2 -- 多线程版 -- 不能关闭fd了,也不需要 pthread_t tid;ThreadData *td = new ThreadData(sockfd, this,addr);pthread_create(&tid,nullptr,Execute,td); // 新线程分离}_isrunning = false;
}
新线程执行函数
// 无法调用类内成员 无法看到sockfd
static void *Execute(void *args)
{ThreadData *td = static_cast<ThreadData *>(args);pthread_detach(pthread_self()); // 分离新线程,无需主线程回收td->_self->Server(td->_sockfd,td->_addr);delete td;return nullptr;
}
2.4.4、Server 3(线程池版本)
线程池版本即 将执行服务的函数入线程池队列,该函数需要是参数为空和返回值为void的函数,因此需要bind绑定函数!
声明函数类型
using task_t = std::function<void()>;
Loop()
void Loop()
{_isrunning = true;while(_isrunning){struct sockaddr_in client;socklen_t len = sizeof(client);// 1.获取新连接int sockfd = ::accept(_listensockfd,(struct sockaddr*)&client,&len);// 获取失败继续获取if(sockfd < 0){LOG(WARNING,"sccept reeor\n");continue;}InetAddr addr(client);LOG(INFO,"get a new link,client info: %s,sockfd:%d\n",addr.AddrStr().c_str(),sockfd); // 4// 获取成功// version 3 -- 线程池版本task_t t = std::bind(&TcpServer::Server,this,sockfd,addr);ThreadPool<task_t>::GetInstance()->Equeue(t);}_isrunning = false;
}
3、TcpClientMain.cc
客户端主函数主要实现向服务端发送消息的功能,调用主函数使用该可执行程序 + IP + 端口号!
主函数主要分为四步:
- 1、创建socket(与服务端一样)
- 2、与服务端建立连接(使用connect[客户端与服务器建立TCP连接])
connect()
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数
sockfd
:这是一个由socket
函数返回的套接字描述符。addr
:这是一个指向sockaddr
结构的指针,它包含了目标服务器的地址和端口信息。对于IPv4地址,通常使用sockaddr_in
结构;对于IPv6地址,使用sockaddr_in6
结构。addrlen
:这是addr
参数的长度,以字节为单位。对于sockaddr_in
,它通常是sizeof(struct sockaddr_in)
;对于sockaddr_in6
,它通常是sizeof(struct sockaddr_in6)
。
返回值
- 成功时,
connect
返回0。 - 失败时,返回-1,并设置
errno
以指示错误类型。
- 3、发送消息
- 4、关闭socket
// ./tcpclient server-ip server-ip
int main(int argc,char* argv[])
{if(argc != 3){std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;exit(0);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// 1.创建socketint sockfd = ::socket(AF_INET,SOCK_STREAM,0);if(sockfd < 0){std::cerr << "create socket error" << std::endl;exit(1);}// 不需要显示的bind,但是一定要有自己的IP和port,需要隐式的绑定(OS用自己的IP和随机端口号)// 什么时候进行bind? If the connection or binding succeedsstruct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);// server.sin_addr.s_addr = ::inet_pton(AF_INET,serverip.c_str(),&server.sin_addr);// 2.与服务端建立连接int n = ::connect(sockfd,(struct sockaddr*)&server,sizeof(server));// 也可以重连if(n < 0){std::cerr << "connect socket error" << std::endl;exit(2);}// 3.发送消息while(true){std::string message;std::cout << "Enter#";std::getline(std::cin,message);write(sockfd,message.c_str(),message.size());char echo_buffer[1024];n = read(sockfd,echo_buffer,sizeof(echo_buffer));if(n > 0){echo_buffer[n] = 0;std::cout << echo_buffer << std::endl;}else{break;}}// 4.关闭socket::close(sockfd);return 0;
}
4、测试结果
4.1、不靠谱版本
该版本是一个只能执行一个客户端的版本,因此称为不靠谱版本!
4.2、多进程版本
该版本是一个能执行多客户端的版本,但是创建进程的开销比较大,也不是很完美!
4.3、多线程版本
该版本是一个能执行多客户端的版本,相比与多进程版本效果会更好,因为创建线程的开销比进程更少!
4.4、线程池版本
该版本是一个能执行多客户端的版本,与线程池版本差不太多,此处只是使用以前实现的线程池!
5、完整代码
前面一弹就有且没有修改的代码此处就没有再放上来了!
5.1、Makefile
.PHONY:all
all:tcpserver tcpclienttcpserver:TcpServerMain.ccg++ -o $@ $^ -std=c++14tcpclient:TcpClientMain.ccg++ -o $@ $^ -std=c++14.PHONY:clean
clean:rm -rf tcpserver tcpclient
5.2、TcpClientMain.cc
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>// ./tcpclient server-ip server-ip
int main(int argc,char* argv[])
{if(argc != 3){std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;exit(0);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// 1.创建socketint sockfd = ::socket(AF_INET,SOCK_STREAM,0);if(sockfd < 0){std::cerr << "create socket error" << std::endl;exit(1);}// 不需要显示的bind,但是一定要有自己的IP和port,需要隐式的绑定(OS用自己的IP和随机端口号)// 什么时候进行bind? If the connection or binding succeedsstruct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);// server.sin_addr.s_addr = ::inet_pton(AF_INET,serverip.c_str(),&server.sin_addr);// 2.与服务端建立连接int n = ::connect(sockfd,(struct sockaddr*)&server,sizeof(server));// 也可以重连if(n < 0){std::cerr << "connect socket error" << std::endl;exit(2);}// 3.发送消息while(true){std::string message;std::cout << "Enter#";std::getline(std::cin,message);write(sockfd,message.c_str(),message.size());char echo_buffer[1024];n = read(sockfd,echo_buffer,sizeof(echo_buffer));if(n > 0){echo_buffer[n] = 0;std::cout << echo_buffer << std::endl;}else{break;}}// 4.关闭socket::close(sockfd);return 0;
}
5.3、TcpServer.hpp
#pragma once
#include <iostream>
#include <functional>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <pthread.h>
#include "Log.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"using namespace log_ns;enum
{SOCKET_ERROR,BIND_ERROR,LISTEN_ERROR
};const static uint16_t gport = 8888;
const static int gsockfd = -1;
const static int gblcklog = 8;using task_t = std::function<void()>;// 面向字节流
class TcpServer
{
public:// _sockfd 版本// TcpServer(uint16_t port = gport):_port(port),_sockfd(gsockfd),_isrunning(false)// {}TcpServer(uint16_t port = gport):_port(port),_listensockfd(gsockfd),_isrunning(false){}// _sockfd 版本// void InitServer()// {// // 1.创建socket// _sockfd = ::socket(AF_INET,SOCK_STREAM,0);// if(_sockfd < 0)// {// LOG(FATAL,"socket create eror\n");// exit(SOCKET_ERROR);// }// LOG(INFO,"socket create success,sockfd: %d\n",_sockfd); // 3// struct sockaddr_in local;// memset(&local,0,sizeof(local));// local.sin_family = AF_INET;// local.sin_port = htons(_port);// local.sin_addr.s_addr = INADDR_ANY;// // 2.bind sockfd 和 socket addr// if(::bind(_sockfd,(struct sockaddr*)&local,sizeof(local)) < 0)// {// LOG(FATAL,"bind eror\n");// exit(BIND_ERROR);// }// LOG(INFO,"bind success\n");// // 3.因为tcp是面向连接的,tcp需要未来不短地获取连接// // 老板模式,随时等待被连接// if(::listen(_sockfd,gblcklog) < 0)// {// LOG(FATAL,"listen eror\n");// exit(LISTEN_ERROR);// }// LOG(INFO,"listen success\n");// }void InitServer(){// 1.创建socket_listensockfd = ::socket(AF_INET,SOCK_STREAM,0);if(_listensockfd < 0){LOG(FATAL,"socket create eror\n");exit(SOCKET_ERROR);}LOG(INFO,"socket create success,sockfd: %d\n",_listensockfd); // 3struct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;// 2.bind sockfd 和 socket addrif(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local)) < 0){LOG(FATAL,"bind eror\n");exit(BIND_ERROR);}LOG(INFO,"bind success\n");// 3.因为tcp是面向连接的,tcp需要未来不短地获取连接// 老板模式,随时等待被连接if(::listen(_listensockfd,gblcklog) < 0){LOG(FATAL,"listen eror\n");exit(LISTEN_ERROR);}LOG(INFO,"listen success\n");}// 内部类class ThreadData{public:int _sockfd;TcpServer* _self;InetAddr _addr;public:ThreadData(int sockfd,TcpServer* self,const InetAddr &addr):_sockfd(sockfd),_self(self),_addr(addr){}};// 测试// void Loop()// {// _isrunning = true;// while(_isrunning)// {// sleep(1);// }// _isrunning = false;// }void Loop(){_isrunning = true;while(_isrunning){struct sockaddr_in client;socklen_t len = sizeof(client);// 1.获取新连接int sockfd = ::accept(_listensockfd,(struct sockaddr*)&client,&len);// 获取失败继续获取if(sockfd < 0){LOG(WARNING,"sccept reeor\n");continue;}InetAddr addr(client);LOG(INFO,"get a new link,client info: %s,sockfd:%d\n",addr.AddrStr().c_str(),sockfd); // 4// 获取成功// version 0 -- 不靠谱版本// Server(sockfd,addr);// version 1 -- 多进程版本// pid_t id = fork();// if(id == 0)// {// // child// ::close(_listensockfd); // 建议! // if(fork() > 0) exit(0); // 让孙子进程执行服务,保证能不阻塞// Server(sockfd,addr);// exit(0);// }// // father// ::close(sockfd); // 防止文件描述符泄漏(打开的不关闭)// int n = waitpid(id,nullptr,0); // 0阻塞等待 // if(n > 0)// {// LOG(INFO,"wait child success\n");// }// version 2 -- 多线程版 -- 不能关闭fd了,也不需要 // pthread_t tid;// ThreadData *td = new ThreadData(sockfd, this,addr);// pthread_create(&tid,nullptr,Execute,td); // 新线程分离// version 3 -- 线程池版本task_t t = std::bind(&TcpServer::Server,this,sockfd,addr);ThreadPool<task_t>::GetInstance()->Equeue(t);}_isrunning = false;}// 无法调用类内成员 无法看到sockfdstatic void *Execute(void *args){ThreadData *td = static_cast<ThreadData *>(args);pthread_detach(pthread_self()); // 分离新线程,无需主线程回收td->_self->Server(td->_sockfd,td->_addr);delete td;return nullptr;}void Server(int sockfd,InetAddr addr){// 长服务while(true){char inbuffer[1024]; // 当做字符串// 1.读文件ssize_t n = ::read(sockfd,inbuffer,sizeof(inbuffer) - 1);if(n > 0){inbuffer[n] = 0;LOG(INFO,"get message from client [%s],message: %s\n",addr.AddrStr().c_str(),inbuffer);std::string echo_string = "[server echo]# ";echo_string += inbuffer;// 2.写文件write(sockfd,echo_string.c_str(),echo_string.size());}// 读到文件结尾else if(n == 0){LOG(INFO,"client %s quit\n",addr.AddrStr().c_str());break;}else{LOG(ERROR,"read error\n",addr.AddrStr().c_str());break;}}::close(sockfd);}~TcpServer(){}
private:uint16_t _port;// int _sockfd; // TODOint _listensockfd;bool _isrunning;
};
5.4、TcpServerMain.cc
#include "TcpServer.hpp"
#include <memory>// ./tcpserver 8888
int main(int argc,char* argv[])
{if(argc != 2){std::cerr << "Usage: " << argv[0] << " local-post" << std::endl;exit(0);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);tsvr->InitServer();tsvr->Loop();return 0;
}
注意:线程池只需将全局变量gdefaultnum改为10即可!
static const int gdefaultnum = 10; // 默认创建10个线程
相关文章:
【Linux网络编程】第六弹---构建TCP服务器:从基础到线程池版本的实现与测试详解
✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】【Linux网络编程】 目录 1、TcpServerMain.cc 2、TcpServer.hpp 2.1、TcpServer类基本结构 2.2、构造析构函数 2.3、InitServer(…...
XML 语言随笔
XML的含义 XML(eXtensible Markup Language,可扩展标记语言)是一种用于存储和传输数据的标记语言。XML与HTML(HyperText Markup Language,超文本标记语言)类似,但XML的设计目的是描述数据&…...
flex: 1 display:flex 导致的宽度失效问题
flex: 1 & display:flex 导致的宽度失效问题 问题复现 有这样的一个业务场景,详情项每行三项分别占33%宽度,每项有label字数不固定所以宽度不固定,还有content 占满标签剩余宽度,文字过多显示省略号, 鼠标划入展示…...
65页PDF | 企业IT信息化战略规划(限免下载)
一、前言 这份报告是企业IT信息化战略规划,报告详细阐述了企业在面对新兴技术成熟和行业竞争加剧的背景下,如何通过三个阶段的IT战略规划(IT 1.0基础建设、IT 2.0运营效率、IT 3.0持续发展),系统地构建IT管理架构、应…...
15.三数之和
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k ,同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三元组。 示例 1&am…...
【Notepad++】---设置背景为护眼色(豆沙绿)最新最详细
在编程的艺术世界里,代码和灵感需要寻找到最佳的交融点,才能打造出令人为之惊叹的作品。而在这座秋知叶i博客的殿堂里,我们将共同追寻这种完美结合,为未来的世界留下属于我们的独特印记。 【Notepad】---设置背景为护眼色…...
项目代码第2讲:从0实现LoginController.cs,UsersController.cs、User相关的后端接口对应的前端界面
一、User 1、使用数据注解设置主键和外键 设置主键:在User类的U_uid属性上使用[Key]注解。 设置外键:在Order类中,创建一个表示外键的属性(例如UserU_uid),并使用[ForeignKey]注解指定它引用User类的哪个…...
电子商务人工智能指南 3/6 - 聊天机器人和客户服务
介绍 81% 的零售业高管表示, AI 至少在其组织中发挥了中等至完全的作用。然而,78% 的受访零售业高管表示,很难跟上不断发展的 AI 格局。 近年来,电子商务团队加快了适应新客户偏好和创造卓越数字购物体验的需求。采用 AI 不再是一…...
vue.js组件开发的所有流程
1. 设计组件架构 首先,你需要考虑应用的结构,决定哪些部分应该成为独立的组件。这包括: 提取重复的UI元素:如按钮、输入框、卡片等。 功能模块:例如登录框、导航栏、数据表格等。 2. 设置开发环境 安装Node.js…...
从零开始学TiDB(1) 核心组件架构概述
首先TiDB深度兼容MySQL 5.7 1. TiDB Server SQL语句的解析与编译:首先一条SQL语句最先到达的地方是TiDB Server集群,TiDB Server是无状态的,不存储数据,SQL 发过来之后TiDB Server 负责 解析,优化,编译 这…...
VsCode运行Ts文件
1. 生成package.json文件 npm init 2. 生成tsconfig.json文件 tsc --init 3. Vscode运行ts文件 在ts文件点击右键执行Run Code,执行ts文件...
初始化webpack应用示例
1、初始化npm mkdir webpack_test cd webpack_test npm init 2、安装webpack依赖 npm install webpack webpack-cli -D 3、添加文件夹,入口文件 mkdir src touch index.js touch add-content.js 文件夹结构 index.js import addContent from "./add-cont…...
liunx docker 部署 nacos seata sentinel
部署nacos 1.按要求创建好数据库 2.创建docker 容器 docker run -d --name nacos-server -p 8848:8848 -e MODEstandalone -e SPRING_DATASOURCE_PLATFORMmysql -e MYSQL_SERVICE_HOST172.17.251.166 -e MYSQL_SERVICE_DB_NAMEry-config -e MYSQL_SERVICE_PORT3306 -e MYSQL…...
MySQL面试
文章目录 事务隔离级别需要解决的问题事务隔离级别 MySQL 中是如何实现事务隔离的实现可重复读 什么是存储引擎如何定位慢查询分析慢查询原因MySQL超大分页怎么处理索引失效什么时候建立唯一索引、前缀索引、联合索引?redolog与binlog是如何保证一致的redolog刷盘时…...
linux运维之shell编程
Shell 编程在系统运维中及其重要 1. Shell 编程概述 Shell 是一种命令行解释器,能够执行操作系统的命令。Shell 脚本是一个包含一系列 Shell 命令的文件,它可以被执行,以自动化和批量处理任务。常用的 Shell 类型包括 bash、sh、zsh 等。Shel…...
ssm 多数据源 注解版本
application.xml 配置如下 <!-- 使用 DruidDataSource 数据源 --><bean id"primaryDataSource" class"com.alibaba.druid.pool.DruidDataSource" init-method"init" destroy-method"close"></bean> <!-- 使用 数…...
Nginx核心配置详解
一、配置文件说明 nginx官方帮助文档:nginx documentation nginx的配置文件的组成部分: 主配置文件:nginx.conf子配置文件: include conf.d/*.conffastcgi, uwsgi,scgi 等协议相关的配置文件mime.types:…...
十六(AJAX3)、XMLHttpRequest、Promise、简易axios封装、案例天气预报、lodash-debounce防抖
1. XMLHttpRequest 1.1 XMLHttpRequest-基本使用 /* 定义:XMLHttpRequest(XHR)对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。这允许网页在不影响用户操作的情况下,更…...
12.06 深度学习-预训练
# 使用更深的神经网络 经典神经网络 import torch import cv2 from torchvision.models import resnet18,ResNet18_Weights from torch import optim,nn from torch.utils.data import DataLoader from torchvision.datasets import CIFAR10 from torchvision import tr…...
【计算机网络】期末速成(2)
部分内容来源于网络,侵删~ 第五章 传输层 概述 传输层提供进程和进程之间的逻辑通信,靠**套接字Socket(主机IP地址,端口号)**找到应用进程。 传输层会对收到的报文进行差错检测。 比特流(物理层)-> 数据帧(数据链路层) -> 分组 / I…...
Python学习笔记10-作用域
作用域 定义:Python程序程序可以直接访问命名空间的正文区域 作用:决定了哪一部分区域可以访问哪个特定的名称 分类: 局部作用域(Local)闭包函数外的函数中(Enclosing)全局作用域࿰…...
Sui 主网升级至 V1.38.3
Sui 主网现已升级至 V1.38.3 版本,同时协议升级至 69 版本。请开发者及时关注并调整! 其他升级要点如下所示: 协议 #20199 在共识快速路径投票中设置允许的轮次数量。 节点(验证节点与全节点) #20238 为验证节点…...
linux的vdagent框架设计
1、vdagent Linux 的 spice 客户代理由两部分组成,一个系统范围的守护进程 spice-vdagentd 和一个 X11 会话代理 spice-vdagent,每个 X11 会话有一个。spice-vdagentd 通过 Sys-V initscript 或 systemd 单元启动。 如下图:spice-vdagent&a…...
vue3+elementPlus封装的一体表格
目录结构 源码 exportOptions.js export default reactive([{label: 导出本页,key: 1,},{label: 导出全部,key: 2,}, ])index.vue <template><div class"flex flex-justify-between flex-items-end"><div><el-button-group><slot name…...
判断是否 AGP7+ 的方法
如何判断? /*** 是否是AGP7.0.0及以上* param project* return*/static boolean isAGP7_0_0(Project project) {def androidComponents project.extensions.findByName("androidComponents")if (androidComponents && androidComponents.hasProp…...
使用 Streamlit +gpt-4o实现有界面的图片内容分析
在上一篇利用gpt-4o分析图像的基础上,进一步将基于 Python 的 Streamlit 库,结合 OpenAI 的 API,构建一个简洁易用的有界面图片内容分析应用。通过该应用,用户可以轻松浏览本地图片,并获取图片的详细描述。 调用gpt-4o…...
树莓集团是如何链接政、产、企、校四个板块的?
树莓集团作为数字影像行业的积极探索者与推动者,我们通过多维度、深层次的战略举措,将政、产、企、校四个关键板块紧密链接在一起,实现了资源的高效整合与协同发展,共同为数字影像产业的繁荣贡献力量。 与政府的深度合作政府在产业…...
Fyne ( go跨平台GUI )中文文档-Fyne总览(二)
本文档注意参考官网(developer.fyne.io/) 编写, 只保留基本用法 go代码展示为Go 1.16及更高版本,ide为goland2021.2??????? ?这是一个系列文章: Fyne ( go跨平台GUI )中文文档-入门(一)-CSDN博客 Fyne ( go跨平台GUI )中文文档-Fyne总览(二)-CSDN博客 Fyne…...
MySQL数据库(3)-SQL基础语言学习
1. DDL数据定义语言 1.1 什么是DDL DDL(Data Definition Language,数据定义语言)是SQL语言的一部分,用于定义和修改数据库结构。DDL主要包括以下三类语句: 1.CREATE:用于创建数据库对象,如数…...
下拉框根据sql数据回显
vue <a-form-item label"XXXX" :labelCol"labelCol" :wrapperCol"wrapperCol" required><a-select v-decorator"[disputeType, validatorRules.disputeType]" style"width: 200px" placeholder"请选择XXXX&q…...
电池销售系统
文末获取源码和万字论文,制作不易,感谢点赞支持。 摘 要 在当今信息爆炸的大时代,由于信息管理系统能够更有效便捷的完成信息的管理,越来越多的人及机构都已经引入和发展以信息管理系统为基础的信息化管理模式,随之信…...
四、镜像构建
四、镜像构建 从镜像大小上来说,一个比较小的镜像只有十几MB,而内核文件需要一百多MB,因此镜像里面是没有内核的,镜像是在被启动为容器后直接使用宿主机的内核,而镜像本身则只提供相应的rootfs,即系统正常…...
FastAPI 响应状态码:管理和自定义 HTTP Status Code
FastAPI 响应状态码:管理和自定义 HTTP Status Code 本文介绍了如何在 FastAPI 中声明、使用和修改 HTTP 状态码,涵盖了常见的 HTTP 状态码分类,如信息响应(1xx)、成功状态(2xx)、客户端错误&a…...
C#设计模式--原型模式(Prototype Pattern)
原型模式是一种创建型设计模式,它允许通过复制现有对象来创建新对象,而无需通过构造函数。这种方式可以提高性能,特别是在创建复杂对象时。C# 中可以通过实现 ICloneable 接口或自定义克隆方法来实现原型模式。 案例 1:文档编辑器…...
使用Goland对6.5840项目进行go build出现异常
使用Goland对6.5840项目进行go build出现异常 Lab地址: https://pdos.csail.mit.edu/6.824/labs/lab-mr.html项目地址: git://g.csail.mit.edu/6.5840-golabs-2024 6.5840运行环境: mac系统 goland git clone git://g.csail.mit.edu/6.5840-golabs-2024 6.5840 cd 6.5840/src…...
使用kibana实现es索引的数据映射和索引模版/组件模版
1 数据映射 数据映射官方链接 1.1 日期映射案例 1.创建一条索引。把索引中的字段生日映射为日期,并制定映射后的格式为年月日 PUT http://10.0.0.91:9200/zhiyong18-luckyboy-date {"mappings": {"properties": {"birthday": {&q…...
基于elementui的远程搜索下拉选择分页组件
在开发一个练手项目的时候,需要一个远程搜索的下拉选择组件; elementui自带的el-select支持远程搜索;但如果一次性查询的数据过多;会导致卡顿。故自己实现一个可分页的远程下拉选择组件 效果: 代码: <…...
每日一题 LCR 074. 合并区间
LCR 074. 合并区间 对遍历顺序注意一下就可以 class Solution { public:vector<vector<int>> merge(vector<vector<int>>& intervals) {int n intervals.size();sort(intervals.begin(),intervals.end());int idx 0;vector<vector<int&g…...
Flink SQL
Overview | Apache Flink FlinkSQL开发步骤 Concepts & Common API | Apache Flink 添加依赖: <dependency><groupId>org.apache.flink</groupId><artifactId>flink-table-api-java-bridge_2.11</artifactId><version>…...
[免费]SpringBoot+Vue企业OA自动化办公管理系统【论文+源码+SQL脚本】
大家好,我是java1234_小锋老师,看到一个不错的SpringBootVue企业OA自动化办公管理系统,分享下哈。 项目视频演示 【免费】SpringBootVue企业OA自动化办公管理系统 Java毕业设计_哔哩哔哩_bilibili 项目介绍 随着信息技术在管理上越来越深入…...
Linux下编译安装METIS
本文记录Linux下编译安装METIS的流程。 零、环境 操作系统Ubuntu 22.04.4 LTSVS Code1.92.1Git2.34.1GCC11.4.0CMake3.22.1 一、安装依赖 1.1 下载GKlib sudo apt-get install build-essential sudo apt-get install cmake 2.2 编译安装GKlib 下载GKlib代码, …...
LLM学习路径 - 渐进式构建知识体系
LLM学习路径 - 渐进式构建知识体系 文章目录 LLM学习路径 - 渐进式构建知识体系一、模型算法基础二、机器学习三、深度学习四、自然语言处理 (NLP)五、大规模语言模型 (LLM) References 一、模型算法基础 编程基础 Web 框架 深入学习 Gradio 与 Streamlit,理解其构…...
使用Unity脚本模拟绳索、布料(碰撞)
效果演示: 脚本如下: using System.Collections; using System.Collections.Generic; using UnityEngine;namespace PhysicsLab {public class RopeSolver : MonoBehaviour {public Transform ParticlePrefab;public int Count 3;public int Space 1;…...
Qt Chart 模块化封装曲线图
一 版本说明 二 完成示例 此文章包含:曲线轴设置,曲线切换,单条曲线显示,坐标轴。。。 三 曲线图UI创建 在UI界面拖放一个QWidget,然后在 Widget里面放一个 graphicsView 四 代码介绍 1 头文件 #include <QString> #in…...
【Linux】深入理解进程管理与fork系统调用的实现原理
【Linux】深入理解进程管理与fork系统调用的实现原理 进程基本概念描述进程-PCBtask_struct-PCB的一种task_ struct内容分类组织进程 查看进程通过系统调用获取进程标示符Fork 之后的代码共享1. **代码共享**2. **数据段不共享**总结 🌏个人博客主页:个人…...
C语言程序设计P5-2【应用函数进行程序设计 | 第二节】——知识要点:函数的参数及返回值和函数的调用及声明
知识要点:函数的参数及返回值和函数的调用及声明 视频: 目录 一、任务分析 二、必备知识与理论 三、任务实施 一、任务分析 1.求x的n次方的值也就是求n个x的乘积,可把x和n作为函数的形参,定义一个函数power来完成这个功能&am…...
【LeetCode】169.多数元素
题目连接: https://leetcode.cn/problems/majority-element/solutions/2362000/169-duo-shu-yuan-su-mo-er-tou-piao-qing-ledrh/?envTypestudy-plan-v2&envIdtop-interview-150 题目描述: 思路一: 使用哈希表unordered_map记录每个元…...
0x0118消息 WM_SYSTIMER
0x0118消息就是WM_SYSTIMER 编辑框出现输入光标时,产生的消息. 0x0118 would be the undocumented WM_SYSTIMER, which appears to be used for caret blinks. 0x0118是一个undocument 消息, 微软没有记录。 但在一些库的源码中可以看到,比如ATL的库文…...
【Linux内核】Hello word程序
创建测试目录 mkdir -p ~/develop/kernel/hello-1 cd ~/develop/kernel/hello-1 创建MakeFile文件和内核.c文件 nano Makefile nano hello-1.c 编写内容 /* * hello-1.c - The simplest kernel module. */ #include <linux/module.h> /* Needed by all modules */…...
黑马redis
Redis的多IO线程只是用来处理网络请求的,对于读写操作命令Redis仍然使用单线程来处理 Redisson分布式锁实现15问 文章目录 主线程和IO线程是如何协作的Unix网络编程中的五种IO模型Linux世界一切皆文件生产上限制keys *、flushdb、flushall等危险命令keys * 遍历查询100W数据花…...