【Linux跬步积累】—— 网络编程套接字(二)

🌏博客主页:PH_modest的博客主页
🚩当前专栏:Linux跬步积累
💌其他专栏:
🔴 每日一题
🟡 C++跬步积累
🟢 C语言跬步积累
🌈座右铭:广积粮,缓称王!

文章目录

  • 一、TCP socket API
  • 二、TCP API 使用
    • 1、服务端创建套接字
    • 2、服务端绑定
    • 3、服务端监听
    • 4、服务端获取连接
    • 5、服务端接收连接测试
  • echo server
    • 多进程版本
      • `TcpServer.hpp`
      • `Main.cc`
      • `MainClient.cc`
      • 运行效果图
    • 多线程版本
      • `TcpServer.hpp`
      • `Main.cc`
      • `MainClient.cc`
      • 运行效果图
    • 线程池版本
      • `TcpServer.hpp`
      • `Main.cc`
      • `MainClient.cc`
      • `Thread.hpp`
      • `ThreadPool.hpp`
      • 运行效果图


一、TCP socket API

下面介绍程序中用到的socket API,这些函数都在sys/socket.h中。

//创建socket文件描述符(TCP/UDP,客户端 + 服务器)
int socket(int domain,int type,int protocol);//绑定端口号(TCP/UDP,服务器)
int bind(int socket,const struct sockaddr *address,socklen_t address_len);//开始监听socket(TCP,服务器)
int listen(int socket,int backlog);//接收请求(TCP,服务器)
int accept(int socket,struct sockaddr* address,socklen_t* address_len);//建立连接(TCP,客户端)
int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);

二、TCP API 使用

1、服务端创建套接字

函数原型:

//创建socket文件描述符(TCP/UDP,客户端 + 服务器)
int socket(int domain,int type,int protocol);

使用示例:

//创建文件描述符
_listensock = socket(AF_INET,SOCK_STREAM,0);
if(_listensock < 0)
{std::cerr<<"socket error!"<<std::endl;exit(1);
}

2、服务端绑定

函数原型:

//绑定端口号(TCP/UDP,服务器)
int bind(int socket,const struct sockaddr *address,socklen_t address_len);

使用示例:

//绑定
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;if(bind(_listensock,(struct sockaddr*)&local,sizeof(local)) < 0)
{std::cerr<<"bind error!"<<std::endl;exit(2);
}

3、服务端监听

函数原型:

//开始监听socket(TCP,服务器)
int listen(int socket,int backlog);

使用示例:

//监听
if(listen(_listensock,5) < 0)
{std::cerr<<"listen error!"<<std::endl;exit(3);
}

4、服务端获取连接

函数原型:

//接收请求(TCP,服务器)
int accept(int socket,struct sockaddr* address,socklen_t* address_len);

参数说明:

  • socket:特定的监听套接字,表示从这个套接字获取连接。
  • address:对端网络相关信息,包括协议家族、IP地址、端口号。
  • address_len:这是一个输入输出型参数,调用时传入期望读取的长度,返回时表示实际读取的长度。

accept函数返回的套接字是什么?和socket有什么区别?

  • accept函数获取连接时,是从socket监听套接字当中获取的,如果accept获取连接成功,此时就会返回接收到的套接字对应的文件描述符。

  • socket监听套接字的作用是用来获取客户端发来的新的连接请求。accept会不断从监听套接字当中获取新连接。

  • accept返回的套接字是为本次获取到的连接提供服务的。监听套接字是不断获取新的连接,真正为这些连接提供服务的是accept返回的套接字,而不是监听套接字。

  • 监听套接字可以看成饭店门口拉客的员工,当你被她说服进店之后,会有新的服务员单独为你提供服务,而这个新的服务员就是accept返回的套接字。

使用示例:

void Start()
{while(true){struct sockaddr_in peer;memset(&peer,0,sizeof(peer));socklen_t len = sizeof(peer);int sockfd = accept(_listensock,(struct sockaddr*)&peer,&len);if(sockfd < 0){std::cerr<<"accept error!"<<std::endl;continue;//这里不能直接退出,因为还需要获取其他连接}std::string client_ip = inet_ntoa(peer.sin_addr);//将网络序列转换为主机序列,同时将整数ip变为字符串ipint client_port = ntohs(peer.sin_port);//将网络序列转换为主机序列std::cout<<"get a new link-->"<<sockfd<<"["<<client_ip<<"]:"<<client_port<<std::endl;}
}

5、服务端接收连接测试

我们现在做一个简单的测试,测试一下当前服务器能否成功接受请求连接。

void Usage(char *proc)
{std::cout<<"Usage:\n\t"<<proc<<" local_port\n";
}//./tcpserver port
int main(int argc,char *argv[])
{if(argc != 2){Usage(argv[0]);exit(1);}int port = std::stoi(argv[1]);TcpServer* tsvr = new TcpServer(port);tsvr->InitServer();tsvr->Start();return 0;
}

我们编译运行之后,可以通过netstat命令来显示网络连接、路由表、接口统计等网络相关信息。


echo server

多进程版本

TcpServer.hpp

#pragma once
#include"TcpServer.hpp"
#include<iostream>
#include<string>
#include<strings.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/wait.h>
#include"InetAddr.hpp"static const int gbacklog = 15;class TcpServer
{
public:TcpServer(uint16_t port):_port(port),_isrunning(false){}void InitServer(){//1. 创建流式套接字_listensock = socket(AF_INET,SOCK_STREAM,0);if(_listensock < 0){std::cerr<<"socket error!\n";exit(1);}std::cout<<"socket success,sockfd is:"<<_listensock<<std::endl;//2. bindstruct sockaddr_in local;bzero(&local,sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;int n = ::bind(_listensock,(struct sockaddr*)&local,sizeof(local));if(n < 0){std::cerr<<"bind error!\n";exit(2);}std::cout<<"bind success,sockfd is:"<<_listensock<<std::endl;//3. tcp是面向连接的,所以通信之前,必须先建立连接,服务器是被连接的//   tcpserver启动,未来首先要一直等待客户的连接到来n = listen(_listensock,gbacklog);if(n < 0){std::cerr<<"listen error!\n";exit(3);}std::cout<<"listen success,sockfd is:"<<_listensock<<std::endl;}void Service(int sockfd,InetAddr client){printf("get a new link,info %s:%d,fd:%d\n",client.IP().c_str(),client.Port(),sockfd);std::string clientaddr = "["+client.IP()+":"+std::to_string(client.Port())+"]# ";while(true){//读取数据char inbuffer[1024];ssize_t n = read(sockfd,inbuffer,sizeof(inbuffer)-1);if(n > 0)//回写{inbuffer[n] = 0;std::cout<<clientaddr<<inbuffer<<std::endl;std::string echo_string = "[server echo]# ";echo_string+=inbuffer;write(sockfd,echo_string.c_str(),echo_string.size());}else if(n == 0)//退出{//client 退出&&关闭链接了std::cout<<clientaddr<<" quit!\n";break;}else//报错{std::cerr<<clientaddr<<"read error!\n";break;}}//一定要关闭,因为文件描述符表是一个数组,数组容量是有限的close(sockfd);//如果不关闭,会导致文件描述符泄漏问题}void Loop(){//4. 不能直接收数据,先获取链接_isrunning = true;while(_isrunning){struct sockaddr_in peer;socklen_t len = sizeof(peer);int sockfd = accept(_listensock,(struct sockaddr*)&peer,&len);if(sockfd < 0){std::cerr<<"accept error!\n";continue;}//version 0 : 一次只能处理一个请求//Service(sockfd,InetAddr(peer));//version 1 :采用多进程pid_t id = fork();if(id == 0){//child 只关心sockfd,不关心listensock::close(_listensock);//建议关闭if(fork() > 0) exit(0);//创建的父进程退出Service(sockfd,InetAddr(peer));//孙子进程,他的父进程提前退出了,没有等待,会变成孤儿进程,然后被系统领养exit(0);}//father 只关心listensock,不关心sockfd::close(sockfd);waitpid(id,nullptr,0);}_isrunning = false;}~TcpServer(){if(_listensock > -1){close(_listensock);}}
private:int _listensock;uint16_t _port;bool _isrunning;
};

Main.cc

#include"TcpServer.hpp"
#include<iostream>
#include<memory>void Usage(char *proc)
{std::cout<<"Usage:\n\t"<<proc<<" local_port\n";
}//./tcpserver port
int main(int argc,char *argv[])
{if(argc != 2){Usage(argv[0]);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);tsvr->InitServer();tsvr->Loop();return 0;
}

MainClient.cc

#include<iostream>
#include<string>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>void Usage(std::string proc)
{std::cout<<"Usage:\n\t"<<proc<<" local_ip local_port\n";
}//./udpclient serverip serverport 
int main(int argc,char *argv[])
{if(argc != 3){Usage(argv[0]);exit(1);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);//1. 创建套接字int sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd < 0){std::cerr<<"socket error"<<std::endl;exit(2);}//2. 发起连接struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());int n = connect(sockfd,(struct sockaddr*)&server,sizeof(server));if(n < 0){std::cerr<<"connect error!\n";exit(3);}while(true){std::cout<<"Please Enter# ";std::string outstring;getline(std::cin,outstring);ssize_t n = send(sockfd,outstring.c_str(),outstring.size(),0);//writeif(n > 0){char inbuffer[1024];ssize_t m = recv(sockfd,&inbuffer,sizeof(inbuffer)-1,0);if(m > 0){inbuffer[m] = 0;std::cout<<inbuffer<<std::endl;}else{break;}}else{break;}}close(sockfd); return 0;
}

运行效果图

运行示意图-1741955753542-1

多线程版本

TcpServer.hpp

#pragma once
#include "TcpServer.hpp"
#include <iostream>
#include <string>
#include <strings.h>
#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 "InetAddr.hpp"static const int gbacklog = 15;
class TcpServer;class ThreadData
{
public:ThreadData(int fd,InetAddr addr,TcpServer *s):sockfd(fd),clientaddr(addr),self(s){}
public:int sockfd;InetAddr clientaddr;TcpServer *self;
};class TcpServer
{
public:TcpServer(uint16_t port) : _port(port), _isrunning(false){}void InitServer(){// 1. 创建流式套接字_listensock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){std::cerr << "socket error!\n";exit(1);}std::cout << "socket success,sockfd is:" << _listensock << std::endl;// 2. bindstruct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;int n = ::bind(_listensock, (struct sockaddr *)&local, sizeof(local));if (n < 0){std::cerr << "bind error!\n";exit(2);}std::cout << "bind success,sockfd is:" << _listensock << std::endl;// 3. tcp是面向连接的,所以通信之前,必须先建立连接,服务器是被连接的//    tcpserver启动,未来首先要一直等待客户的连接到来n = listen(_listensock, gbacklog);if (n < 0){std::cerr << "listen error!\n";exit(3);}std::cout << "listen success,sockfd is:" << _listensock << std::endl;}void Service(int sockfd, InetAddr client){printf("get a new link,info %s:%d,fd:%d\n", client.IP().c_str(), client.Port(), sockfd);std::string clientaddr = "[" + client.IP() + ":" + std::to_string(client.Port()) + "]# ";while (true){// 读取数据char inbuffer[1024];ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);if (n > 0) // 回写{inbuffer[n] = 0;std::cout << clientaddr << inbuffer << std::endl;std::string echo_string = "[server echo]# ";echo_string += inbuffer;write(sockfd, echo_string.c_str(), echo_string.size());}else if (n == 0) // 退出{// client 退出&&关闭链接了std::cout << clientaddr << " quit!\n";break;}else // 报错{std::cerr << clientaddr << "read error!\n";break;}}// 一定要关闭,因为文件描述符表是一个数组,数组容量是有限的close(sockfd); // 如果不关闭,会导致文件描述符泄漏问题}static void* HandlerSock(void *args){pthread_detach(pthread_self());ThreadData* data = static_cast<ThreadData*>(args);data->self->Service(data->sockfd,data->clientaddr);delete data;return nullptr;}void Loop(){// 4. 不能直接收数据,先获取链接_isrunning = true;while (_isrunning){struct sockaddr_in peer;socklen_t len = sizeof(peer);int sockfd = accept(_listensock, (struct sockaddr *)&peer, &len);if (sockfd < 0){std::cerr << "accept error!\n";continue;}// version 0 : 一次只能处理一个请求// Service(sockfd,InetAddr(peer));// version 1 :采用多进程//  pid_t id = fork();//  if(id == 0)//  {//      //child 只关心sockfd,不关心listensock//      ::close(_listensock);//建议关闭//      if(fork() > 0) exit(0);//创建的父进程退出//      Service(sockfd,InetAddr(peer));//孙子进程,他的父进程提前退出了,没有等待,会变成孤儿进程,然后被系统领养//      exit(0);//  }//  //father 只关心listensock,不关心sockfd//  ::close(sockfd);//  waitpid(id,nullptr,0);// version 2:采用多线程pthread_t t;//线程之间共享文件描述符表ThreadData *data = new ThreadData(sockfd,InetAddr(peer),this);pthread_create(&t,nullptr,HandlerSock,data);}_isrunning = false;}~TcpServer(){if (_listensock > -1){close(_listensock);}}private:int _listensock;uint16_t _port;bool _isrunning;
};

Main.cc

#include"TcpServer.hpp"
#include<iostream>
#include<memory>void Usage(char *proc)
{std::cout<<"Usage:\n\t"<<proc<<" local_port\n";
}//./tcpserver port
int main(int argc,char *argv[])
{if(argc != 2){Usage(argv[0]);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);tsvr->InitServer();tsvr->Loop();return 0;
}

MainClient.cc

#include<iostream>
#include<string>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>void Usage(std::string proc)
{std::cout<<"Usage:\n\t"<<proc<<" local_ip local_port\n";
}//./udpclient serverip serverport 
int main(int argc,char *argv[])
{if(argc != 3){Usage(argv[0]);exit(1);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);//1. 创建套接字int sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd < 0){std::cerr<<"socket error"<<std::endl;exit(2);}//2. 发起连接struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());int n = connect(sockfd,(struct sockaddr*)&server,sizeof(server));if(n < 0){std::cerr<<"connect error!\n";exit(3);}while(true){std::cout<<"Please Enter# ";std::string outstring;getline(std::cin,outstring);ssize_t n = send(sockfd,outstring.c_str(),outstring.size(),0);//writeif(n > 0){char inbuffer[1024];ssize_t m = recv(sockfd,&inbuffer,sizeof(inbuffer)-1,0);if(m > 0){inbuffer[m] = 0;std::cout<<inbuffer<<std::endl;}else{break;}}else{break;}}close(sockfd); return 0;
}

运行效果图

运行示意图-1741961343635-3

线程池版本

TcpServer.hpp

#pragma once
#include "TcpServer.hpp"
#include <iostream>
#include <string>
#include <strings.h>
#include <unistd.h>
#include <pthread.h>
#include <functional>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include "InetAddr.hpp"
#include "ThreadPool.hpp"static const int gbacklog = 15;
class TcpServer;class ThreadData
{
public:ThreadData(int fd,InetAddr addr,TcpServer *s):sockfd(fd),clientaddr(addr),self(s){}
public:int sockfd;InetAddr clientaddr;TcpServer *self;
};using task_t = std::function<void()>;class TcpServer
{
public:TcpServer(uint16_t port) : _port(port), _isrunning(false){}void InitServer(){// 1. 创建流式套接字_listensock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){std::cerr << "socket error!\n";exit(1);}std::cout << "socket success,sockfd is:" << _listensock << std::endl;// 2. bindstruct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;int n = ::bind(_listensock, (struct sockaddr *)&local, sizeof(local));if (n < 0){std::cerr << "bind error!\n";exit(2);}std::cout << "bind success,sockfd is:" << _listensock << std::endl;// 3. tcp是面向连接的,所以通信之前,必须先建立连接,服务器是被连接的//    tcpserver启动,未来首先要一直等待客户的连接到来n = listen(_listensock, gbacklog);if (n < 0){std::cerr << "listen error!\n";exit(3);}std::cout << "listen success,sockfd is:" << _listensock << std::endl;}void Service(int sockfd, InetAddr client){printf("get a new link,info %s:%d,fd:%d\n", client.IP().c_str(), client.Port(), sockfd);std::string clientaddr = "[" + client.IP() + ":" + std::to_string(client.Port()) + "]# ";while (true){// 读取数据char inbuffer[1024];ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);if (n > 0) // 回写{inbuffer[n] = 0;std::cout << clientaddr << inbuffer << std::endl;std::string echo_string = "[server echo]# ";echo_string += inbuffer;write(sockfd, echo_string.c_str(), echo_string.size());}else if (n == 0) // 退出{// client 退出&&关闭链接了std::cout << clientaddr << " quit!\n";break;}else // 报错{std::cerr << clientaddr << "read error!\n";break;}}// 一定要关闭,因为文件描述符表是一个数组,数组容量是有限的close(sockfd); // 如果不关闭,会导致文件描述符泄漏问题}static void* HandlerSock(void *args){pthread_detach(pthread_self());ThreadData* data = static_cast<ThreadData*>(args);data->self->Service(data->sockfd,data->clientaddr);delete data;return nullptr;}void Loop(){// 4. 不能直接收数据,先获取链接_isrunning = true;while (_isrunning){struct sockaddr_in peer;socklen_t len = sizeof(peer);int sockfd = accept(_listensock, (struct sockaddr *)&peer, &len);if (sockfd < 0){std::cerr << "accept error!\n";continue;}// version 0 : 一次只能处理一个请求// Service(sockfd,InetAddr(peer));// version 1 :采用多进程//  pid_t id = fork();//  if(id == 0)//  {//      //child 只关心sockfd,不关心listensock//      ::close(_listensock);//建议关闭//      if(fork() > 0) exit(0);//创建的父进程退出//      Service(sockfd,InetAddr(peer));//孙子进程,他的父进程提前退出了,没有等待,会变成孤儿进程,然后被系统领养//      exit(0);//  }//  //father 只关心listensock,不关心sockfd//  ::close(sockfd);//  waitpid(id,nullptr,0);// version 2:采用多线程// pthread_t t;//线程之间共享文件描述符表// ThreadData *data = new ThreadData(sockfd,InetAddr(peer),this);// pthread_create(&t,nullptr,HandlerSock,data);// version 3:采用线程池task_t task = std::bind(&TcpServer::Service,this,sockfd,InetAddr(peer));ThreadPool<task_t>::GetInstance()->EnQueue(task);}_isrunning = false;}~TcpServer(){if (_listensock > -1){close(_listensock);}}private:int _listensock;uint16_t _port;bool _isrunning;
};

Main.cc

#include"TcpServer.hpp"
#include<iostream>
#include<memory>void Usage(char *proc)
{std::cout<<"Usage:\n\t"<<proc<<" local_port\n";
}//./tcpserver port
int main(int argc,char *argv[])
{if(argc != 2){Usage(argv[0]);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);tsvr->InitServer();tsvr->Loop();return 0;
}

MainClient.cc

#include<iostream>
#include<string>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>void Usage(std::string proc)
{std::cout<<"Usage:\n\t"<<proc<<" local_ip local_port\n";
}//./udpclient serverip serverport 
int main(int argc,char *argv[])
{if(argc != 3){Usage(argv[0]);exit(1);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);//1. 创建套接字int sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd < 0){std::cerr<<"socket error"<<std::endl;exit(2);}//2. 发起连接struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());int n = connect(sockfd,(struct sockaddr*)&server,sizeof(server));if(n < 0){std::cerr<<"connect error!\n";exit(3);}while(true){std::cout<<"Please Enter# ";std::string outstring;getline(std::cin,outstring);ssize_t n = send(sockfd,outstring.c_str(),outstring.size(),0);//writeif(n > 0){char inbuffer[1024];ssize_t m = recv(sockfd,&inbuffer,sizeof(inbuffer)-1,0);if(m > 0){inbuffer[m] = 0;std::cout<<inbuffer<<std::endl;}else{break;}}else{break;}}close(sockfd); return 0;
}

Thread.hpp

#pragma once
#include<string>
#include<pthread.h>
#include<unistd.h>
#include<iostream>
#include<functional>namespace MyThread
{template<class T>using func_t = std::function<void(T&)>;//模版方法template<class T>class Thread{public://thread(func,5,"thread-1");Thread(func_t<T> func,const T &data,const std::string &name = "none-name"):_func(func),_data(data),_threadname(name){}//需要设置成static静态成员函数,否则参数会多一个this指针,就不符合pthread_create的要求了static void* ThreadRoutinue(void* args){//将传过来的this指针强转一下,然后就可以访问到_func和_data了Thread<T>* self = static_cast<Thread<T>*>(args);self->_func(self->_data);//这里调用的_func是线程池中的HanderTask方法return nullptr;}bool Start(){//创建线程int ret = pthread_create(&_tid,nullptr,ThreadRoutinue,this);return ret==0;}void Join(){pthread_join(_tid,nullptr);}void Detach(){pthread_detach(_tid);}~Thread(){}private:pthread_t _tid;          //线程tidstd::string _threadname; //线程名func_t<T> _func;         //线程执行的函数T _data;                 //需要处理的数据};
}

ThreadPool.hpp

#include"Thread.hpp"
#include<vector>
#include<queue>
#include<string>
#include <unistd.h>
#include <pthread.h>template<class T>
class ThreadPool
{   
public:ThreadPool(const int num = 5):_threadNum(num),_waitNum(0),_isRunning(false){pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_cond,nullptr);}void HanderTask(std::string name){//子线程需要一直处理,所以这里使用死循环while(true){pthread_mutex_lock(&_mutex);while(_taskQueue.empty()&&_isRunning)//这里是while循环,不是if判断,避免伪唤醒{_waitNum++;pthread_cond_wait(&_cond,&_mutex);_waitNum--;}//线程池终止了,并且任务队列中没有任务 --> 线程退出if(_taskQueue.empty()&&!_isRunning){pthread_mutex_unlock(&_mutex);std::cout<<name<<" quit..."<<std::endl;break;}//走到这里无论线程池是否终止,都一定还有任务要执行,将任务执行完再退出T task = _taskQueue.front();_taskQueue.pop();std::cout<<name<<" get a task..."<<std::endl;pthread_mutex_unlock(&_mutex);task();}}void ThreadInit(){for(int i=0;i<_threadNum;i++){auto func = bind(&ThreadPool::HanderTask,this,std::placeholders::_1);std::string name = "Thread-"+std::to_string(i);//_threads.push_back(HanderTask,name,name);//第一个name是handerTask的参数,第二个name是Thread内部的成员_threads.emplace_back(func,name,name);}_isRunning = true;}void StartAll(){for(auto& thread : _threads){thread.Start();}}void JoinAll(){for(auto& thread : _threads){thread.Join();}}void EnQueue(const T& task){pthread_mutex_lock(&_mutex);if(_isRunning){_taskQueue.push(task);if(_waitNum > 0){pthread_cond_signal(&_cond);}}pthread_mutex_unlock(&_mutex);}void Stop(){pthread_mutex_lock(&_mutex);_isRunning = false;//终止线程池pthread_cond_broadcast(&_cond);//唤醒所有等待的线程pthread_mutex_unlock(&_mutex);}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}
public:static ThreadPool<T> *GetInstance(){if(nullptr == _instance){pthread_mutex_lock(&_lock);if(nullptr == _instance){_instance = new ThreadPool<T>();_instance->ThreadInit();_instance->StartAll();std::cout<<"创建线程池单例!\n";return _instance;}pthread_mutex_unlock(&_lock);}std::cout<<"获取线程池样例!\n";return _instance;}
private:std::vector<MyThread::Thread<std::string>> _threads;//用数组管理多个线程std::queue<T> _taskQueue;//任务队列int _threadNum;//线程数int _waitNum;//等待的线程数bool _isRunning;//线程池是否在运行pthread_mutex_t _mutex;//互斥锁pthread_cond_t _cond;//条件变量//添加单例模式static ThreadPool<T> *_instance;static pthread_mutex_t _lock;
};template<class T>
ThreadPool<T> *ThreadPool<T>::_instance = nullptr;template<class T>
pthread_mutex_t ThreadPool<T>::_lock = PTHREAD_MUTEX_INITIALIZER;

运行效果图

运行示意图-1742130518502-1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/83319.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…

pikachu靶场通关笔记24 SQL注入07-http header注入

目录 一、SQL注入 二、http header注入 1、User - Agent 头注入 2、Referer 头注入 3、Cookie 头注入 4、Host 头注入 三、extractvalue函数 四、源码分析 1、代码审计 2、渗透思路 五、渗透实战 1、渗透探测 2、获取数据库名database 3、获取表名table 4、获取列…

LabVIEW振动时效处理系统

LabVIEW 开发大功率振动时效处理系统&#xff0c;实现工件残余应力检测与消除。聚焦工业场景中金属加工件的应力处理需求&#xff0c;展现 LabVIEW 在跨硬件集成、实时数据处理及复杂流程控制中的技术优势。 ​ 应用场景 针对航空航天、轨道交通、重型机械等领域中钢性焊接件…

数据定义以及数据类型

toc 数据定义以及数据类型 1. 数据创建 数据库创建除了指定数据库名字&#xff0c;还可以选择指定数据库字符集类型以及校对规则&#xff0c;mysql中utf8mb3就是utf8。 -- 使用指令创建数据库 CREATE DATABASE hsp_db01; -- 删除数据库指令 DROP DATABASE hsp_db01 -- 创建…

中国汽车启动电池市场深度剖析:现状、趋势与展望

一、市场规模与增长前景​ QYResearch 调研团队发布的市场报告显示&#xff0c;中国汽车启动电池市场展现出强劲的增长势头。预计到 2031 年&#xff0c;市场规模将攀升至 74.6 亿美元&#xff0c;在未来几年内&#xff0c;年复合增长率&#xff08;CAGR&#xff09;将稳定保持…

通过RedisCacheManager自定义缓存序列化(适用通过注解缓存数据)

1.Redis 注解默认序列化机制 1.Spring Boot整合Redis组件提供的缓存自动配置类RedisCacheConfiguration&#xff08;org.springframework.boot.autoconfigure.cache&#xff09;, 其内部是通过Redis连接工厂RedisConnectionFactory定义了一个缓存管理器RedisCacheManager&am…

jupyter中的checkpoints为空/打不开解决办法

jupyter中的checkpoints为空/打不开不要以为你是代码有问题或者服务器有问题了&#xff0c;浪费我好几天时间&#xff0c;我说怎么电脑上跑的好好的服务器上模型不见了 新建文件check 然后把checkpoints里的东西全部移动到check文件中就能看见了 checkpoints是Notebook的关键…

基于 Spring AI 的 MCP 客户端/服务端实现

模型上下文协议&#xff08;MCP&#xff09;由Anthropic开源的开放协议&#xff0c;为AI模型与外部数据/工具提供了“标准化桥梁”&#xff0c;通过统一的接口规范&#xff0c;使模型能够动态调用本地文件、数据库、API等资源&#xff0c;实现“上下文感知”的智能交互。MCP的核…

python学习打卡day50

DAY 50 预训练模型CBAM模块 知识点回顾&#xff1a; resnet结构解析CBAM放置位置的思考针对预训练模型的训练策略 差异化学习率三阶段微调 ps&#xff1a;今日的代码训练时长较长&#xff0c;3080ti大概需要40min的训练时长 作业&#xff1a; 好好理解下resnet18的模型结构尝试…

54、错误处理-【源码流程】异常处理流程

54、错误处理-【源码流程】异常处理流程 #### 异常处理流程概述 1. **执行目标方法**&#xff1a; - 程序执行目标方法&#xff0c;期间若发生异常&#xff0c;会被捕获并记录&#xff0c;标志当前请求结束。 - 将异常信息赋值给 dispatchException 变量。 2. **进入视图解析…

使用 VSCode 开发 FastAPI 项目(1)

一、引言 FastAPI 是一款现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建 API&#xff0c;使用 Python 3.7 及更高版本。它基于标准 Python 类型提示&#xff0c;具有自动生成文档等出色功能。而 VSCode 凭借其轻量、强大的特性&#xff0c;为开发者…

Bash 脚本中的特殊变量

在 Bash 脚本和命令行中&#xff0c;​特殊变量​&#xff08;Special Variables&#xff09;主要用于获取脚本或命令的上下文信息&#xff0c;如参数、进程状态、返回值等。以下是常见的特殊变量及其典型应用场景&#xff1a; ​1. 脚本参数处理​ $0、$1、$2 ... $9、${10}.…

免部署的数字人 API 调用教程:基于 wav2lip模型训练的开放API,附 PHP 代码示例

前言 去年我开始研究数字人模型算法&#xff0c;测试了市面上几乎所有开源数字人模型&#xff0c;过程中踩了不少坑。最大的痛点就是训练太烧显卡了&#xff0c;光租显卡的费用就花了我6个月的薪资&#xff0c;每次看到账单都心疼。不过现在终于把基于wav2lip的数字人API做出来…

力扣HOT100之贪心算法:45. 跳跃游戏 II

这道题刷代码随想录的时候也刷过&#xff0c;本来以为有了上一题55.跳跃游戏的基础&#xff0c;这道题会好做一点&#xff0c;但是依旧想不出来思路&#xff0c;回去看了下自己当时写的博客&#xff0c;没想到今天的感受和当时的感受都一模一样。。。What can I say?看了下代码…

使用Docker申请Let‘s Encrypt证书

1、安装Docker # 安装Docker https://docs.docker.com/get-docker/# 安装Docker Compose https://docs.docker.com/compose/install/# CentOS安装Docker https://mp.weixin.qq.com/s/nHNPbCmdQs3E5x1QBP-ueA 2、申请Lets Encrypt证书 详见&#xff1a; https://docs.linuxse…

算法题(167):FBI树

审题&#xff1a; 本题需要我们将字符串按照题目要求进行递归展开&#xff0c;并按照后序遍历的顺序输出 思路&#xff1a; 方法一&#xff1a;递归 首先我们需要模拟一下题目的意思 其实就是第一步判断属于什么字符&#xff0c;然后将字符串分两半进行下一轮判断。而由于题目要…

从“分散开发”到“智能协同” —— Gitee 如何赋能河南农担构建金融级研发体系?

河南省农业信贷担保有限责任公司&#xff08;以下简称「河南农担」&#xff09;成立于 2016 年&#xff0c;是河南省属骨干国有企业&#xff0c;承担破解“三农”融资难题的重要职责。截至 2024 年底&#xff0c;河南农担累计实现担保规模 1037.05 亿元&#xff0c;位居全国农担…

青少年编程与数学 01-011 系统软件简介 14 Foxpro数据库

青少年编程与数学 01-011 系统软件简介 14 Foxpro数据库 一、历史沿革二、技术架构三、主要功能四、应用场景五、产品版本六、使用方法七、技术价值八、历史意义全文总结 **摘要&#xff1a;**FoxPro 是一款经典的桌面数据库管理系统&#xff0c;起源于 1984 年的 FoxBASE&…

android studio向左向右滑动页面

本文演示了Android Studio中使用ViewPager实现页面切换的方法。通过创建包含3个页面的ViewPager示例&#xff0c;详细展示了实现步骤&#xff1a;1)在XML布局中配置ViewPager和切换按钮&#xff1b;2)使用LayoutInflater动态加载页面布局&#xff1b;3)自定义SimplePagerAdapte…