目录
一、UDP
1、Linux客户端、服务器
1.1udpServer.hpp
1.2udpServer.cc
1.3udpClient.hpp
1.4udpClient.cc
1.5onlineUser.hpp
2、Windows客户端
二、TCP
1、单进程版的TCP客户端、服务器
1.1tcpServer.hpp
1.2tcpServer.cc
1.3tcpClient.hpp
1.4tcpClient.cc
1.5log.hpp
2、多进程版的TCP客户端、服务器
3、多线程版的TCP客户端、服务器
4、线程池版的TCP客户端、服务器
4.1tcpServer.hpp
4.2ThreadPool.hpp
4.3Task.hpp
5、守护进程+多线程版的TCP客户端、服务器
5.1daemon.hpp
5.2tcpServer.cc
UDP/TCP客户端、服务器代码可参考本人gitee
UDP/TCP套接字的创建流程可参考此处
一、UDP
1、Linux客户端、服务器
1.1udpServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <arpa/inet.h>
#include <strings.h>
#include <functional>
namespace Server
{const static string defaultIp="0.0.0.0";//缺省的IPconst static int gnum=1024;typedef function<void(int,string,uint16_t,string)> func_t;enum {USAGE_ERR=1,SOCKET_ERR,BIND_ERR,OPEN_ERR,};class udpServer{public:udpServer(const func_t& callback,const uint16_t& port,const string& ip=defaultIp):_callback(callback)//udpServer.cc传入的对客户端数据处理的函数,_port(port),_ip(ip),_sockfd(-1){}void initServer(){//1、创建socket_sockfd=socket(AF_INET,SOCK_DGRAM,0);//网络通信+数据报if(-1==_sockfd){cout<<"socket error"<<errno<<":"<<strerror(errno)<<endl;exit(SOCKET_ERR);}cout<<"socket success"<<":"<<_sockfd<<endl;//2、绑定IP和端口号struct sockaddr_in local;bzero(&local,sizeof(local));//将一段内存初始化为全0local.sin_family=AF_INET;//协议族设置为网络通信local.sin_port=htons(_port);//设置端口号,需要转为大端,主机转网络local.sin_addr.s_addr=inet_addr(_ip.c_str());//将IP字符串转uint32_t的同时转为网络字节序//local.sin_addr.s_addr=htonl(INADDR_ANY);//INADDR_ANY就是0,表明任何IP都可以访问这个服务器的_port端口int n=bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(-1==n){cout<<"bind error"<<errno<<":"<<strerror(errno)<<endl;exit(BIND_ERR);}}void start(){char buffer[gnum];while(1){//循环读取数据struct sockaddr_in local;//输出型参数socklen_t len=sizeof(local);//必填size_t s=recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&local,&len);//阻塞式读取//这里需要关心1、数据是什么2、数据是谁发的if(s>0){buffer[s]=0;//加上'\0'//1、这是从网络读出来的IP,需要由网络字节序转主机字节序2、整数转点分十进制IP,用inet_ntoa进行转换string clientIp=inet_ntoa(local.sin_addr);//将32位IPv4地址(in_addr结构体)转换成点分十进制字符串形式的IP地址uint16_t clientPort=ntohs(local.sin_port);//一样需要转换字节序string message=buffer;cout<<clientIp<<"["<<clientPort<<"]#"<<message<<endl;//对数据进行处理_callback(_sockfd,clientIp,clientPort,message);}}}~udpServer(){}private:uint16_t _port;//端口号string _ip;//IP地址(服务器不建议固定的绑定一个IP地址,因为服务器需要接收所有的IP)int _sockfd;//套接字文件描述符func_t _callback;//回调函数};
}
1.2udpServer.cc
#include <memory>
#include <unordered_map>
#include <fstream>
#include <signal.h>
using namespace std;
#include "udpServer.hpp"
#include "onlineUser.hpp"
using namespace Server;
// const std::string dictTxt="./dict.txt";
// unordered_map<string,string> dict;//字典
// std::string key,value;static void Usage(string proc)
{cout<<"Usage:\n\t"<<proc<<"local_ip local_port\n\n";
}// static bool cutString(const string& target,string* key,string* value,const string& sep)//字符串截取
// {
// //string sep=":";
// auto pos=target.find(sep,0);
// if(pos==string::npos)
// {
// return false;
// }
// *key=target.substr(0,pos);
// *value=target.substr(pos+sep.size());
// return true;
// }
// static void initDict()//文件操作
// {
// ifstream in(dictTxt,std::ios_base::binary);
// if(!in.is_open())//如果文件打开失败
// {
// cerr<<"open file"<<dictTxt<<"error"<<endl;
// exit(OPEN_ERR);
// }
// string line;
// while(getline(in,line))
// {
// if(cutString(line,&key,&value,":"))//如果截取成功
// {
// dict.insert(make_pair(key,value));//dict.insert(key,value);
// }
// else //截取失败
// {
// //...
// }
// }
// in.close();
// cout<<"load dict success"<<endl;
// }
// static void debugPrint()//测试打印函数
// {
// for(auto& dt:dict)
// {
// cout<<dt.first<<"/"<<dt.second<<endl;
// }
// }
// //客户端单词翻译代码
// void handMessage(int sockfd,string clientIp,uint16_t clientPort,string message)
// {
// //对客户端的信息进行特定的业务处理,实现了server通信和业务的解耦
// string response_message;//将查找的字符串保存至此处
// unordered_map<string,string>::iterator iter=dict.find(message);
// if(iter==dict.end())
// {
// response_message="unknow";
// }
// else
// response_message=iter->second;
// //服务端向客户端回发数据
// struct sockaddr_in client;
// bzero(&client,sizeof(client));
// client.sin_family=AF_INET;
// client.sin_addr.s_addr=inet_addr(clientIp.c_str());
// client.sin_port=htons(clientPort);
// sendto(sockfd,response_message.c_str(),response_message.size(),0,(struct sockaddr*)&client,sizeof(client));
// }
// //解析客户端上传的命令
// void execCommand(int sockfd,string clientIp,uint16_t clientPort,string cmd)
// {
// //对客户端的信息进行特定的业务处理,实现了server通信和业务的解耦
// auto end=cmd.find("rm");
// if(end!=string::npos)
// {
// cerr<<clientIp<<":"<<clientPort<<"非法操作"<<cmd<<endl;
// return;
// }
// string response_message;//将客户端上传的指令保存至此处
// FILE* fp=popen(cmd.c_str(),"r");
// if(fp==nullptr)
// {
// response_message=cmd+" exec failed";
// }
// char line[1024];
// while(fgets(line,sizeof(line),fp))
// {
// response_message+=line;//读出客户端传入的指令
// }
// pclose(fp);
// //服务端向客户端回发数据
// struct sockaddr_in client;
// bzero(&client,sizeof(client));
// client.sin_family=AF_INET;
// client.sin_addr.s_addr=inet_addr(clientIp.c_str());
// client.sin_port=htons(clientPort);
// sendto(sockfd,response_message.c_str(),response_message.size(),0,(struct sockaddr*)&client,sizeof(client));
// }
//聊天室
OnlineUser olUser;
void routeMessage(int sockfd,string clientIp,uint16_t clientPort,string message)
{//上线就新增,下线就减掉if(message=="online"){olUser.addUser(clientIp,clientPort);}if(message=="offline"){olUser.delUser(clientIp,clientPort);}if(olUser.isOnline(clientIp,clientPort)){//广播消息olUser.broadcastMessage(sockfd,clientIp,clientPort,message);}else{//服务端向客户端回发数据string response_message="请先运行online";struct sockaddr_in client;bzero(&client,sizeof(client));client.sin_family=AF_INET;client.sin_addr.s_addr=inet_addr(clientIp.c_str());client.sin_port=htons(clientPort);sendto(sockfd,response_message.c_str(),response_message.size(),0,(struct sockaddr*)&client,sizeof(client));}
}
// void reload(int signo)//热加载回调函数
// {
// (void)signo;
// initDict();
// }
int main(int argc,char* argv[])//./udpServer port
{if(argc!=2)//判断外部传入的参数是否为3{Usage(argv[0]);exit(USAGE_ERR);}uint16_t port=atoi(argv[1]);//需要转uint16_t整型// signal(2,reload);//发送信号,实现文本的热加载// initDict();//std::unique_ptr<udpServer> usvr(new udpServer(handMessage,port));//在线翻译//std::unique_ptr<udpServer> usvr(new udpServer(execCommand,port));//指令解析std::unique_ptr<udpServer> usvr(new udpServer(routeMessage,port));//聊天室usvr->initServer();usvr->start();return 0;
}
1.3udpClient.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <arpa/inet.h>
#include <strings.h>
namespace Client
{using namespace std;class udpClient{public:udpClient(const string& serverIp,const uint16_t& serverPort):_sockfd(-1),_serverPort(serverPort),_serverIp(serverIp){}void initClient(){//创建socket_sockfd=socket(AF_INET,SOCK_DGRAM,0);if(-1==_sockfd){cout<<"socket error"<<errno<<":"<<strerror(errno)<<endl;exit(2);}cout<<"socket syuccess"<<":"<<_sockfd<<endl;}static void* readMessage(void* args)//类内创建线程,有个this指针干扰{int sockfd=*static_cast<int*>(args);pthread_detach(pthread_self());while(1){//接收服务端发送的数据char buffer[1024];struct sockaddr_in temp;socklen_t len=sizeof(temp);size_t s=recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&temp,&len);if(s>0){buffer[s]=0;//字符串以'\0'结尾}cout<<buffer<<endl;}return nullptr;}void run(){pthread_create(&_reader,nullptr,readMessage,(void*)&_sockfd);struct sockaddr_in server;memset(&server,sizeof(server),0);//初始化为全0server.sin_family=AF_INET;server.sin_addr.s_addr=inet_addr(_serverIp.c_str());server.sin_port=htons(_serverPort);//主机转网络string message;char cmdline[1024];while(1){//cerr<<"Please Enter#";// cin>>message;fprintf(stderr,"Enter#");fflush(stderr);fgets(cmdline,sizeof(cmdline),stdin);cmdline[strlen(cmdline)-1]=0;message=cmdline;//发送数据,sendto的时候,操作系统会帮我们自动绑定客户端端口+IP地址sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));}}~udpClient(){}private:int _sockfd;uint16_t _serverPort;string _serverIp;pthread_t _reader;//读线程};
}
1.4udpClient.cc
#include <memory>
#include "udpClient.hpp"
using namespace Client;
static void Usage(string proc)
{cout<<"Usage:\n\t"<<proc<<"server_ip server_port\n\n";
}
int main(int argc,char* argv[])//./udpClient server_ip server_port
{if(argc!=3){Usage(argv[0]);exit(1);}string serverIp=argv[1];uint16_t serverPort=atoi(argv[2]);unique_ptr<udpClient> ucli(new udpClient(serverIp,serverPort));ucli->initClient();ucli->run();return 0;
}
1.5onlineUser.hpp
#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <sys/types.h>
#include <sys/socket.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <arpa/inet.h>
using namespace std;
class User
{
public:User(const string& ip,const uint16_t& port):_ip(ip),_port(port){}~User(){}string ip(){return _ip;}uint16_t port(){return _port;}
private:string _ip;//用户IPuint16_t _port;//用户端口号};
class OnlineUser
{
public:OnlineUser(){}~OnlineUser(){}void addUser(const string& ip,const uint16_t& port)//新增用户{string id=ip+"-"+to_string(port);users.insert(make_pair(id,User(ip,port)));}void delUser(const string& ip,const uint16_t& port)//删除用户{string id=ip+"-"+to_string(port);users.erase(id);}bool isOnline(const string& ip,const uint16_t& port)//是否在线{string id=ip+"-"+to_string(port);return users.find(id)==users.end()?false:true;}void broadcastMessage(int sockfd,const string& ip,const uint16_t& port,const string& message)//给所有的user广播消息{for(auto& user:users){//服务端向客户端回发数据struct sockaddr_in client;bzero(&client,sizeof(client));client.sin_family=AF_INET;client.sin_addr.s_addr=inet_addr(user.second.ip().c_str());client.sin_port=htons(user.second.port());string s=ip+"_"+to_string(port)+"# ";//id+"#"s+=message;sendto(sockfd,s.c_str(),s.size(),0,(struct sockaddr*)&client,sizeof(client));}}
private:unordered_map<string,User> users;//string:id=ip+"-"+to_string(port);User:User类
};
2、Windows客户端
可先让上方Linux服务器先运行起来,再让Windows客户端连接上该服务端,实现网络通信。
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
#include <WinSock2.h>
#include <string>
#include <cstring>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
uint16_t serverPort = 8080;
string serverIp = "43.XXX.105.XX";//你的云服务器IP
#define NUM 1024
int main()
{WSAData wsd;//启动Winsockif (WSAStartup(MAKEWORD(2, 2), &wsd) != 0){cout << "WSAStartUp Error = " << WSAGetLastError() << endl;return -1;}else{cout << "WSAStartup Success" << endl;}SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);//创建套接字if (sock == SOCKET_ERROR){cout<<"socket Error = "<< WSAGetLastError() << endl;return -2;}else{cout << "socket Success" << endl;}struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_addr.s_addr = inet_addr(serverIp.c_str());server.sin_family = AF_INET;server.sin_port = htons(serverPort);string line;char buffer[NUM];while (1){//发送数据cout << "Please Enter#";getline(cin, line);int n = sendto(sock, line.c_str(), line.size(), 0, (struct sockaddr*)&server, sizeof(server));if (n < 0){cerr << "sendto Error" << endl;break;}cout << "发送成功" << endl;//接收数据buffer[0] = 0;//C式清空数组struct sockaddr_in peer;int len = (int)sizeof(peer);n = recvfrom(sock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);if (n > 0){buffer[n] = 0;cout << "server 返回的消息是" << buffer << endl;}else break;}closesocket(sock);//关闭套接字WSACleanup();return 0;
}
二、TCP
1、单进程版的TCP客户端、服务器
单线程会一直在ServerIO读取写入数据,为一个客户端服务,如果此时连接的客户端不止一个,其他客户端发送的信息将不会被显示。需要使用多线程或多进程解决。
1.1tcpServer.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <cstring>
#include "log.hpp"
namespace Server
{enum {USAGE_ERR=1,SOCKET_ERR,BIND_ERR,LISTEN_ERR,};static const uint16_t gport=8080;//缺省的端口号static const int gbacklog=5;//最大连接数=5+1const static std::string defaultIp="0.0.0.0";//缺省的IPclass TcpServer{public:TcpServer(const uint16_t& port=gport,const std::string& ip=defaultIp ):_listenSocket(-1),_port(port) ,_ip(ip){}void InitServer()//初始化服务器{//1、创建sockrt套接字_listenSocket=socket(AF_INET,SOCK_STREAM,0);if(_listenSocket<0){LogMessage(FATAL,"create socket error");exit(SOCKET_ERR);}LogMessage(NORMAL,"create socket success");//2、绑定端口号+ip地址struct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_addr.s_addr=inet_addr(_ip.c_str());local.sin_family=AF_INET;local.sin_port=htons(_port);if(bind(_listenSocket,(struct sockaddr*)&local,sizeof(local))<0){LogMessage(FATAL,"bind socket error");exit(BIND_ERR);}LogMessage(NORMAL,"bind socket success");//3、设置监听状态if(-1==listen(_listenSocket,gbacklog)){LogMessage(FATAL,"listen socket error");exit(LISTEN_ERR);}LogMessage(NORMAL,"listen socket success");}void Start()//启动服务器{while(1){//4、服务器获取客户端连接请求struct sockaddr_in peer;//输出型参数,拿到客户端的信息socklen_t len=sizeof(peer);int sock=accept(_listenSocket,(struct sockaddr*)&peer,&len); if(-1==sock) {LogMessage(ERROR,"accept error,next");continue;} LogMessage(NORMAL,"accept a new link success");//5、使用accept的返回值sock进行通信,均为文件操作ServerIO(sock);close(sock);//必须关闭使用完毕的sock,否则文件描述符泄漏}}void ServerIO(int sock){char buffer[1024];while(1){//服务器读取客户端数据,通过套接字sock这个文件描述符读取数据ssize_t n=read(sock,buffer,sizeof(buffer)-1);if(n>0){buffer[n]=0;std::cout<<"recv message:"<<buffer<<std::endl;std::string outBuffer=buffer;outBuffer+="[server echo]";//服务器将数据处理后发送回客户端write(sock,outBuffer.c_str(),outBuffer.size());}else if(0==n)//服务器read返回值为0,说明客户端关闭了{LogMessage(NORMAL,"client quit,server quit");break;}}}~TcpServer(){}private:int _listenSocket;//监听客户端的连接请求,不用于数据通信uint16_t _port;//服务器端口号std::string _ip;//服务器ip地址};
}
1.2tcpServer.cc
#include "tcpServer.hpp"
#include "memory"
using namespace Server;
static void Usage(std::string proc)
{std::cout<<"Usage:\n\t"<<proc<<"serverPort\n\n";
}
//./tcpServer local_port
int main(int argc,char* argv[])
{if(argc!=2)//判断外部传入的参数是否为2{Usage(argv[0]);exit(USAGE_ERR);}uint16_t port=std::stoi(argv[1]);std::unique_ptr<TcpServer> tsvr(new TcpServer(port));tsvr->InitServer();tsvr->Start();return 0;
}
1.3tcpClient.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <cstring>
#define NUM 1024
class TcpClient
{
public:TcpClient(const std::string& serverIp,const uint16_t& serverPort):_serverIp(serverIp),_serverPort(serverPort),_sock(-1){}void InitClient(){//1、创建套接字_sock=socket(AF_INET,SOCK_STREAM,0);if(_sock<0){ std::cerr<<"cerete socket err"<<std::endl;exit(2);}//2、客户端需要bind,但是客户端的绑定不需要我们自己写,操作系统会去绑定;(无需程序员bind)}void Start(){//3、客户端发起连接struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_addr.s_addr=inet_addr(_serverIp.c_str());server.sin_family=AF_INET;server.sin_port=htons(_serverPort);if(connect(_sock,(struct sockaddr*)&server,sizeof(server))<0)//连接失败{std::cerr<<"sock connect error"<<std::endl;}else//连接成功{//4、客户端发送/接收消息,文件操作std::string msg;while(1){std::cout<<"Enter:";std::getline(std::cin,msg);write(_sock,msg.c_str(),msg.size());char buffer[NUM];int n=read(_sock,buffer,sizeof(buffer)-1);if(n>0){buffer[n]=0;std::cout<<"Server 回显消息:"<<buffer<<std::endl;}elsebreak;}}}~TcpClient(){if(_sock>=0){close(_sock);}}
private:int _sock;//客户端套接字uint16_t _serverPort;//服务器端口号std::string _serverIp;//服务器ip
};
1.4tcpClient.cc
#include "tcpClient.hpp"
#include <memory>
static void Usage(std::string proc)
{std::cout<<"Usage:\n\t"<<proc<<"local_ip local_port\n\n";
}
//./tcpClient 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]);std::unique_ptr<TcpClient> tcli(new TcpClient(serverIp,serverPort));tcli->InitClient();tcli->Start();return 0;
}
1.5log.hpp
#pragma once
#include <iostream>
#include <string>
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4
//日志功能
void LogMessage(int level,const std::string& message)
{//[日志等级][时间戳/时间][pid][message]std::cout<<message<<std::endl;
}
2、多进程版的TCP客户端、服务器
更换tcpServer.hpp即可,其他文件和单进程版一样。
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <cstring>
#include <cstdlib>
#include "log.hpp"
namespace Server
{enum {USAGE_ERR=1,SOCKET_ERR,BIND_ERR,LISTEN_ERR,};static const uint16_t gport=8080;//缺省的端口号static const int gbacklog=5;//最大连接数=5+1const static std::string defaultIp="0.0.0.0";//缺省的IPclass TcpServer{public:TcpServer(const uint16_t& port=gport,const std::string& ip=defaultIp ):_listenSocket(-1),_port(port) ,_ip(ip){}void InitServer()//初始化服务器{//1、创建sockrt套接字_listenSocket=socket(AF_INET,SOCK_STREAM,0);if(_listenSocket<0){LogMessage(FATAL,"create socket error");exit(SOCKET_ERR);}LogMessage(NORMAL,"create socket success");//2、绑定端口号+ip地址struct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_addr.s_addr=inet_addr(_ip.c_str());local.sin_family=AF_INET;local.sin_port=htons(_port);if(bind(_listenSocket,(struct sockaddr*)&local,sizeof(local))<0){LogMessage(FATAL,"bind socket error");exit(BIND_ERR);}LogMessage(NORMAL,"bind socket success");//3、设置监听状态if(-1==listen(_listenSocket,gbacklog)){LogMessage(FATAL,"listen socket error");exit(LISTEN_ERR);}LogMessage(NORMAL,"listen socket success");}void Start()//启动服务器{while(1){//4、服务器获取客户端连接请求struct sockaddr_in peer;//输出型参数,拿到客户端的信息socklen_t len=sizeof(peer);int sock=accept(_listenSocket,(struct sockaddr*)&peer,&len); if(-1==sock) {LogMessage(ERROR,"accept error,next");continue;} LogMessage(NORMAL,"accept a new link success");// //5、使用accept的返回值sock进行通信,均为文件操作pid_t id=fork();if(id==0)//子进程{close(_listenSocket);//子进程的if(fork()>0) exit(0);//让子进程退出,孙子进程成为孤儿进程,交给1号进程托管回收其退出资源ServerIO(sock);close(sock);//必须关闭使用完毕的sock,否则文件描述符泄漏(虽然下一句代码exit(0),孙子进程退出也会释放文件描述符,最好还是手动关一下)exit(0);}close(sock);//这是用于通信的套接字fd,父进程和孙子进程都有这个文件描述符,父进程关了,该文件描述符引用技术-1,直至孙子进程退出,该fd才会减为0,关闭//父进程//waitpid()pid_t ret=waitpid(id,nullptr,0);//这里不能用非阻塞等待,否则父进程先跑去执行其他代码,可能会被卡在accept出不来了(没有新的客户端来连接的话)if(ret>0){std::cout<<"waitsucceess"<<ret<<std::endl;}}} void ServerIO(int sock){char buffer[1024];while(1){//服务器读取客户端数据,通过套接字sock这个文件描述符读取数据ssize_t n=read(sock,buffer,sizeof(buffer)-1);if(n>0){buffer[n]=0;std::cout<<"recv message:"<<buffer<<std::endl;std::string outBuffer=buffer;outBuffer+="[server echo]";//服务器将数据处理后发送回客户端write(sock,outBuffer.c_str(),outBuffer.size());}else if(0==n)//服务器read返回值为0,说明客户端关闭了{LogMessage(NORMAL,"client quit,server quit");break;}}}~TcpServer(){}private:int _listenSocket;//监听客户端的连接请求,不用于数据通信uint16_t _port;//服务器端口号std::string _ip;//服务器ip地址};
}
区别在于这张图里的代码:
1、close(_listenSocket):关闭子进程的监听fd(虽然手动关不关都行,因为下一句代码就让子进程退出了,最好还是手动关一下)
2、if(fork()>0) exit(0):让子进程创建孙子进程,子进程退出。提前干掉子进程,这样父进程在外部就可以不用阻塞式等待子进程退出了。同时孙子进程成为孤儿进程,会被1号进程领养,程序员无需关心孤儿进程的退出善后工作。
3、孙子进程close(sock):必须关闭使用完毕的sock,否则文件描述符泄漏(虽然下一句代码exit(0),孙子进程退出也会释放文件描述符,最好还是手动关一下)
4、父进程close(sock):这是用于通信的套接字fd,父进程和孙子进程都有这个文件描述符,父进程关了,该文件描述符引用计数-1,直至孙子进程退出,该fd才会减为0,关闭,所以父进程提前关闭该fd不会影响孙子进程。但是这里父进程如果不关,客户端连一个fd+1,存在文件描述符泄露。
5、pid_t ret=waitpid(id,nullptr,0):这里不能用非阻塞等待,否则父进程先跑去执行其他代码,可能会被卡在accept出不来了(如果没有新的客户端来连接的话,将一直卡在accept)
3、多线程版的TCP客户端、服务器
更换tcpServer.hpp即可,其他文件和单进程版一样。
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <cstring>
#include <cstdlib>
#include <pthread.h>
#include "log.hpp"
namespace Server
{enum {USAGE_ERR=1,SOCKET_ERR,BIND_ERR,LISTEN_ERR,};static const uint16_t gport=8080;//缺省的端口号static const int gbacklog=5;//最大连接数=5+1const static std::string defaultIp="0.0.0.0";//缺省的IPclass TcpServer;struct ThreadData//用于线程函数传参{ThreadData(TcpServer* self,const int& sock):_self(self),_sock(sock){}TcpServer* _self;//thisint _sock;//通信fd};class TcpServer{public:TcpServer(const uint16_t& port=gport,const std::string& ip=defaultIp ):_listenSocket(-1),_port(port) ,_ip(ip){}void InitServer()//初始化服务器{//1、创建sockrt套接字_listenSocket=socket(AF_INET,SOCK_STREAM,0);if(_listenSocket<0){LogMessage(FATAL,"create socket error");exit(SOCKET_ERR);}LogMessage(NORMAL,"create socket success");//2、绑定端口号+ip地址struct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_addr.s_addr=inet_addr(_ip.c_str());local.sin_family=AF_INET;local.sin_port=htons(_port);if(bind(_listenSocket,(struct sockaddr*)&local,sizeof(local))<0){LogMessage(FATAL,"bind socket error");exit(BIND_ERR);}LogMessage(NORMAL,"bind socket success");//3、设置监听状态if(-1==listen(_listenSocket,gbacklog)){LogMessage(FATAL,"listen socket error");exit(LISTEN_ERR);}LogMessage(NORMAL,"listen socket success");}void Start()//启动服务器{while(1){//4、服务器获取客户端连接请求struct sockaddr_in peer;//输出型参数,拿到客户端的信息socklen_t len=sizeof(peer);int sock=accept(_listenSocket,(struct sockaddr*)&peer,&len); if(-1==sock) {LogMessage(ERROR,"accept error,next");continue;} LogMessage(NORMAL,"accept a new link success");//5、使用accept的返回值sock进行通信,均为文件操作//多线程版pthread_t tid;ThreadData* td=new ThreadData(this,sock);pthread_create(&tid,nullptr,threadRoutine,(void*)td);}} static void* threadRoutine(void* args){pthread_detach(pthread_self());//线程分离ThreadData* td=static_cast<ThreadData*>(args);td->_self->ServerIO(td->_sock);//线程调用服务函数close(td->_sock);delete td;return nullptr;}void ServerIO(int sock){char buffer[1024];while(1){//服务器读取客户端数据,通过套接字sock这个文件描述符读取数据ssize_t n=read(sock,buffer,sizeof(buffer)-1);if(n>0){buffer[n]=0;std::cout<<"recv message:"<<buffer<<std::endl;std::string outBuffer=buffer;outBuffer+="[server echo]";//服务器将数据处理后发送回客户端write(sock,outBuffer.c_str(),outBuffer.size());}else if(0==n)//服务器read返回值为0,说明客户端关闭了{LogMessage(NORMAL,"client quit,server quit");break;}}}~TcpServer(){}private:int _listenSocket;//监听客户端的连接请求,不用于数据通信uint16_t _port;//服务器端口号std::string _ip;//服务器ip地址};
}
在一个进程中的所有线程都可以访问到文件描述符表,属于共享资源,一个线程所对应的fd在使用完毕后需要进行关闭。
4、线程池版的TCP客户端、服务器
其他文件和单进程版一样。
4.1tcpServer.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <cstring>
#include <cstdlib>
#include <pthread.h>
#include "log.hpp"
#include "Task.hpp"
#include "ThreadPool.hpp"
namespace Server
{enum {USAGE_ERR=1,SOCKET_ERR,BIND_ERR,LISTEN_ERR,};static const uint16_t gport=8080;//缺省的端口号static const int gbacklog=5;//最大连接数=5+1const static std::string defaultIp="0.0.0.0";//缺省的IPclass TcpServer;struct ThreadData//用于线程函数传参{ThreadData(TcpServer* self,const int& sock):_self(self),_sock(sock){}TcpServer* _self;//thisint _sock;//通信fd};class TcpServer{public:TcpServer(const uint16_t& port=gport,const std::string& ip=defaultIp ):_listenSocket(-1),_port(port) ,_ip(ip){}void InitServer()//初始化服务器{//1、创建sockrt套接字_listenSocket=socket(AF_INET,SOCK_STREAM,0);if(_listenSocket<0){LogMessage(FATAL,"create socket error");exit(SOCKET_ERR);}LogMessage(NORMAL,"create socket success");//2、绑定端口号+ip地址struct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_addr.s_addr=inet_addr(_ip.c_str());local.sin_family=AF_INET;local.sin_port=htons(_port);if(bind(_listenSocket,(struct sockaddr*)&local,sizeof(local))<0){LogMessage(FATAL,"bind socket error");exit(BIND_ERR);}LogMessage(NORMAL,"bind socket success");//3、设置监听状态if(-1==listen(_listenSocket,gbacklog)){LogMessage(FATAL,"listen socket error");exit(LISTEN_ERR);}LogMessage(NORMAL,"listen socket success");}void Start()//启动服务器{//4、线程池初始化ThreadPool<Task>::getInstance()->run();//线程启动while(1){//5、服务器获取客户端连接请求struct sockaddr_in peer;//输出型参数,拿到客户端的信息socklen_t len=sizeof(peer);int sock=accept(_listenSocket,(struct sockaddr*)&peer,&len); if(-1==sock) {LogMessage(ERROR,"accept error,next");continue;} LogMessage(NORMAL,"accept a new link success");ThreadPool<Task>::getInstance()->push(Task(sock,ServerIO));}} ~TcpServer(){}private:int _listenSocket;//监听客户端的连接请求,不用于数据通信uint16_t _port;//服务器端口号std::string _ip;//服务器ip地址};
}
4.2ThreadPool.hpp
#pragma once
#include <vector>
#include <queue>
#include <pthread.h>
#include <unistd.h>
#include <mutex>
#include "Thread.hpp"
#include "LockGuard.hpp"
using namespace ThreadNs;
const int gnum =5;template <class T>//声明
class ThreadPool;template <class T>
struct ThreadData
{ThreadData(ThreadPool<T>* tp,const std::string& s):_threadPool(tp),_name(s){}ThreadPool<T>* _threadPool;std::string _name;
};
template <class T>
class ThreadPool
{
private://因为普通成员函数第一个参数是this指针,和回调方法不匹配,故改成static类型static void* handlerTask(void* args)//args是ThreadData对象指针{ThreadData<T>* td=static_cast<ThreadData<T>*>(args);while(1){T t;{ //RAII,出了作用域LockGuard会销毁,将析构锁LockGuard lockGuard(td->_threadPool->mutex());//加锁while(td->_threadPool->IsQueueEmpty())//如果队列为空,则等待{td->_threadPool->ThreadWait();}//线程能走到这里,说明队列一定有任务给线程t=td->_threadPool->Pop();//从队列中取出任务}t();//Task的operator()}delete td;//析构ThreadData对象return nullptr;}ThreadPool(const int& num=gnum):_num(num){pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_cond,nullptr);//创建线程for(int i=0;i<_num;++i){_threads.push_back(new Thread());}}ThreadPool(const ThreadPool<T>&)=delete;//禁用拷贝构造ThreadPool<T>& operator=(const ThreadPool<T>&)=delete;//禁用赋值运算符重载public://解决静态handlerTask是静态函数的问题,这几个都是偷家函数void LockQueue() {pthread_mutex_lock(&_mutex);}void UnLockQueue() {pthread_mutex_unlock(&_mutex);}bool IsQueueEmpty(){return _taskQueue.empty();}void ThreadWait() {pthread_cond_wait(&_cond,&_mutex);}T Pop() {T t=_taskQueue.front();_taskQueue.pop();return t;} pthread_mutex_t* mutex(){return &_mutex;}
public: void run()//线程启动{for(const auto& t:_threads){ThreadData<T>* td=new ThreadData<T>(this,t->threadName());t->start(handlerTask,(void*)td);std::cout<<t->threadName()<<"start..."<<std::endl;}}void push(const T& in){//RAII,出了作用域,锁将会被释放LockGuard lockGuard(&_mutex);_taskQueue.push(in);pthread_cond_signal(&_cond);std::cout<<"任务发送成功"<<std::endl;}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);for(const auto& t:_threads){delete t;}}static ThreadPool<T>* getInstance()//这里的static的作用是让这个函数只有一份,获取单例对象。tp是临界资源,需要加锁{if(nullptr==tp)//因为锁只创建一次,防止线程进来被锁阻塞{//只进来一次就够了_singletonLock.lock();if(nullptr==tp)//说明对象还没有被创建{tp=new ThreadPool<T>(); }_singletonLock.unlock();}return tp;}
private:int _num;//线程个数std::vector<Thread*> _threads;//使用vector存放线程std::queue<T> _taskQueue;//任务队列,往里面放任务,它是共享资源,需要加锁保护pthread_mutex_t _mutex;//互斥锁pthread_cond_t _cond;//条件变量static ThreadPool<T>* tp;//单例模式静态的对象指针static std::mutex _singletonLock;//获取单例对象使用的锁};
template <class T>
ThreadPool<T>* ThreadPool<T>::tp=nullptr;template <class T>
std::mutex ThreadPool<T>::_singletonLock;
4.3Task.hpp
#pragma once
#include <iostream>
#include <functional>
#include <string>
void ServerIO(int sock)
{char buffer[1024];while(1)//适应快速响应的任务,这个任务while其实不太合适{//服务器读取客户端数据,通过套接字sock这个文件描述符读取数据ssize_t n=read(sock,buffer,sizeof(buffer)-1);if(n>0){buffer[n]=0;std::cout<<"recv message:"<<buffer<<std::endl;std::string outBuffer=buffer;outBuffer+="[server echo]";//服务器将数据处理后发送回客户端write(sock,outBuffer.c_str(),outBuffer.size());}else if(0==n)//服务器read返回值为0,说明客户端关闭了{close(sock);LogMessage(NORMAL,"client quit,server quit");break;}}
}
class Task
{//using func_t=std::function<int(int,int,char)>;typedef std::function<void(int)> func_t;//函数对象
public:Task(){}Task(int sock,func_t func):_sock(sock),_callBack(func){}void operator()()//消费者调用{_callBack(_sock);}
private:int _sock;func_t _callBack;//回调函数
};
5、守护进程+多线程版的TCP客户端、服务器
其他文件和单进程版一样。
5.1daemon.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cstdlib>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define DEV "/dev/null"//数据黑洞,向它写入的数据会被吃掉,读取数据什么都读不到(不会使进程退出)
void DaemonSele(const char* currrPath=nullptr)
{//1、让调用进程屏蔽异常的信号//SIGPIPE信号会在进程向一个已经关闭的socket连接写数据时产生,如果不处理这个信号,进程会被强制退出。通过忽略SIGPIPE信号,可以避免进程因为这个信号而退出。signal(SIGPIPE,SIG_IGN);//2、让自己不是组长,调用setsidif(fork()>0) exit(0);//守护进程也称精灵进程,本质就是一个孤儿进程pid_t n=setsid();assert(n!=-1);//失败返回-1//3、守护进程脱离终端,所以要关闭或重定向进程默认打开的文件及文件描述符int fd=open(DEV,O_RDWR);//以读写的方式打开文件黑洞if(fd>=0)//创建成功:重定向{dup2(fd,0);//将fd覆盖标准输入dup2(fd,1);dup2(fd,2);close(fd);}else//创建失败:手动关闭文件描述符{close(0);close(1);close(2);}//4、进程执行路径更改(可改可不改)if(currrPath){chdir(currrPath);}
}
5.2tcpServer.cc
#include "tcpServer.hpp"
#include "memory"
#include "daemon.hpp"
using namespace Server;
static void Usage(std::string proc)
{std::cout<<"Usage:\n\t"<<proc<<"serverPort\n\n";
}
//./tcpServer local_port
int main(int argc,char* argv[])
{if(argc!=2)//判断外部传入的参数是否为2{Usage(argv[0]);exit(USAGE_ERR);}uint16_t port=std::stoi(argv[1]);std::unique_ptr<TcpServer> tsvr(new TcpServer(port));tsvr->InitServer();DaemonSele();//守护进程化,让这个独立的孤儿进程去启动服务器tsvr->Start();return 0;
}