需要云服務(wù)器等云產(chǎn)品來學習Linux的同學可以移步/-->騰訊云<--/-->阿里云<--/-->華為云<--/官網(wǎng),輕量型云服務(wù)器低至112元/年,新用戶首次下單享超低折扣。
?目錄
一、UDP
1、Linux客戶端、服務(wù)器
1.1udpServer.hpp
1.2udpServer.cc
1.3udpClient.hpp
1.4udpClient.cc
1.5onlineUser.hpp
2、Windows客戶端
二、TCP
1、單進程版的TCP客戶端、服務(wù)器
1.1tcpServer.hpp
1.2tcpServer.cc
1.3tcpClient.hpp
1.4tcpClient.cc
1.5log.hpp
2、多進程版的TCP客戶端、服務(wù)器
3、多線程版的TCP客戶端、服務(wù)器
4、線程池版的TCP客戶端、服務(wù)器
4.1tcpServer.hpp
4.2ThreadPool.hpp?
4.3Task.hpp
5、守護進程+多線程版的TCP客戶端、服務(wù)器
5.1daemon.hpp
5.2tcpServer.cc
UDP/TCP客戶端、服務(wù)器代碼可參考本人gitee
UDP/TCP套接字的創(chuàng)建流程可參考此處
一、UDP
1、Linux客戶端、服務(wù)器
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";//缺省的IP
const 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傳入的對客戶端數(shù)據(jù)處理的函數(shù)
,_port(port)
,_ip(ip)
,_sockfd(-1)
{}
void initServer()
{
//1、創(chuàng)建socket
_sockfd=socket(AF_INET,SOCK_DGRAM,0);//網(wǎng)絡(luò)通信+數(shù)據(jù)報
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));//將一段內(nèi)存初始化為全0
local.sin_family=AF_INET;//協(xié)議族設(shè)置為網(wǎng)絡(luò)通信
local.sin_port=htons(_port);//設(shè)置端口號,需要轉(zhuǎn)為大端,主機轉(zhuǎn)網(wǎng)絡(luò)
local.sin_addr.s_addr=inet_addr(_ip.c_str());//將IP字符串轉(zhuǎn)uint32_t的同時轉(zhuǎn)為網(wǎng)絡(luò)字節(jié)序
//local.sin_addr.s_addr=htonl(INADDR_ANY);//INADDR_ANY就是0,表明任何IP都可以訪問這個服務(wù)器的_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)
{
//循環(huán)讀取數(shù)據(jù)
struct sockaddr_in local;//輸出型參數(shù)
socklen_t len=sizeof(local);//必填
size_t s=recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&local,&len);//阻塞式讀取
//這里需要關(guān)心1、數(shù)據(jù)是什么2、數(shù)據(jù)是誰發(fā)的
if(s>0)
{
buffer[s]=0;//加上'\0'
//1、這是從網(wǎng)絡(luò)讀出來的IP,需要由網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)主機字節(jié)序2、整數(shù)轉(zhuǎn)點分十進制IP,用inet_ntoa進行轉(zhuǎn)換
string clientIp=inet_ntoa(local.sin_addr);//將32位IPv4地址(in_addr結(jié)構(gòu)體)轉(zhuǎn)換成點分十進制字符串形式的IP地址
uint16_t clientPort=ntohs(local.sin_port);//一樣需要轉(zhuǎn)換字節(jié)序
string message=buffer;
cout<<clientIp<<"["<<clientPort<<"]#"<<message<<endl;
//對數(shù)據(jù)進行處理
_callback(_sockfd,clientIp,clientPort,message);
}
}
}
~udpServer()
{
}
private:
uint16_t _port;//端口號
string _ip;//IP地址(服務(wù)器不建議固定的綁定一個IP地址,因為服務(wù)器需要接收所有的IP)
int _sockfd;//套接字文件描述符
func_t _callback;//回調(diào)函數(shù)
};
}
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()//測試打印函數(shù)
// {
// for(auto& dt:dict)
// {
// cout<<dt.first<<"/"<<dt.second<<endl;
// }
// }
// //客戶端單詞翻譯代碼
// void handMessage(int sockfd,string clientIp,uint16_t clientPort,string message)
// {
// //對客戶端的信息進行特定的業(yè)務(wù)處理,實現(xiàn)了server通信和業(yè)務(wù)的解耦
// string response_message;//將查找的字符串保存至此處
// unordered_map<string,string>::iterator iter=dict.find(message);
// if(iter==dict.end())
// {
// response_message="unknow";
// }
// else
// response_message=iter->second;
// //服務(wù)端向客戶端回發(fā)數(shù)據(jù)
// 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)
// {
// //對客戶端的信息進行特定的業(yè)務(wù)處理,實現(xiàn)了server通信和業(yè)務(wù)的解耦
// 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);
// //服務(wù)端向客戶端回發(fā)數(shù)據(jù)
// 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
{
//服務(wù)端向客戶端回發(fā)數(shù)據(jù)
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)//熱加載回調(diào)函數(shù)
// {
// (void)signo;
// initDict();
// }
int main(int argc,char* argv[])//./udpServer port
{
if(argc!=2)//判斷外部傳入的參數(shù)是否為3
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port=atoi(argv[1]);//需要轉(zhuǎn)uint16_t整型
// signal(2,reload);//發(fā)送信號,實現(xiàn)文本的熱加載
// 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()
{
//創(chuàng)建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)//類內(nèi)創(chuàng)建線程,有個this指針干擾
{
int sockfd=*static_cast<int*>(args);
pthread_detach(pthread_self());
while(1)
{
//接收服務(wù)端發(fā)送的數(shù)據(jù)
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'結(jié)尾
}
cout<<buffer<<endl;
}
return nullptr;
}
void run()
{
pthread_create(&_reader,nullptr,readMessage,(void*)&_sockfd);
struct sockaddr_in server;
memset(&server,sizeof(server),0);//初始化為全0
server.sin_family=AF_INET;
server.sin_addr.s_addr=inet_addr(_serverIp.c_str());
server.sin_port=htons(_serverPort);//主機轉(zhuǎn)網(wǎng)絡(luò)
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;
//發(fā)送數(shù)據(jù),sendto的時候,操作系統(tǒng)會幫我們自動綁定客戶端端口+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;//用戶IP
uint16_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)
{
//服務(wù)端向客戶端回發(fā)數(shù)據(jù)
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服務(wù)器先運行起來,再讓Windows客戶端連接上該服務(wù)端,實現(xiàn)網(wǎng)絡(luò)通信。
#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";//你的云服務(wù)器IP
#define NUM 1024
int main()
{
WSAData wsd;
//啟動Winsock
if (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);//創(chuàng)建套接字
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)
{
//發(fā)送數(shù)據(jù)
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 << "發(fā)送成功" << endl;
//接收數(shù)據(jù)
buffer[0] = 0;//C式清空數(shù)組
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);//關(guān)閉套接字
WSACleanup();
return 0;
}
二、TCP
1、單進程版的TCP客戶端、服務(wù)器
????????單線程會一直在ServerIO讀取寫入數(shù)據(jù),為一個客戶端服務(wù),如果此時連接的客戶端不止一個,其他客戶端發(fā)送的信息將不會被顯示。需要使用多線程或多進程解決。
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;//最大連接數(shù)=5+1
const static std::string defaultIp="0.0.0.0";//缺省的IP
class TcpServer
{
public:
TcpServer(const uint16_t& port=gport,const std::string& ip=defaultIp )
:_listenSocket(-1)
,_port(port)
,_ip(ip)
{
}
void InitServer()//初始化服務(wù)器
{
//1、創(chuàng)建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、設(shè)置監(jiān)聽狀態(tài)
if(-1==listen(_listenSocket,gbacklog))
{
LogMessage(FATAL,"listen socket error");
exit(LISTEN_ERR);
}
LogMessage(NORMAL,"listen socket success");
}
void Start()//啟動服務(wù)器
{
while(1)
{
//4、服務(wù)器獲取客戶端連接請求
struct sockaddr_in peer;//輸出型參數(shù),拿到客戶端的信息
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);//必須關(guān)閉使用完畢的sock,否則文件描述符泄漏
}
}
void ServerIO(int sock)
{
char buffer[1024];
while(1)
{
//服務(wù)器讀取客戶端數(shù)據(jù),通過套接字sock這個文件描述符讀取數(shù)據(jù)
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]";
//服務(wù)器將數(shù)據(jù)處理后發(fā)送回客戶端
write(sock,outBuffer.c_str(),outBuffer.size());
}
else if(0==n)//服務(wù)器read返回值為0,說明客戶端關(guān)閉了
{
LogMessage(NORMAL,"client quit,server quit");
break;
}
}
}
~TcpServer()
{}
private:
int _listenSocket;//監(jiān)聽客戶端的連接請求,不用于數(shù)據(jù)通信
uint16_t _port;//服務(wù)器端口號
std::string _ip;//服務(wù)器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)//判斷外部傳入的參數(shù)是否為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、創(chuàng)建套接字
_sock=socket(AF_INET,SOCK_STREAM,0);
if(_sock<0)
{
std::cerr<<"cerete socket err"<<std::endl;
exit(2);
}
//2、客戶端需要bind,但是客戶端的綁定不需要我們自己寫,操作系統(tǒng)會去綁定;(無需程序員bind)
}
void Start()
{
//3、客戶端發(fā)起連接
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、客戶端發(fā)送/接收消息,文件操作
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;
}
else
break;
}
}
}
~TcpClient()
{
if(_sock>=0)
{
close(_sock);
}
}
private:
int _sock;//客戶端套接字
uint16_t _serverPort;//服務(wù)器端口號
std::string _serverIp;//服務(wù)器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客戶端、服務(wù)器
????????更換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;//最大連接數(shù)=5+1
const static std::string defaultIp="0.0.0.0";//缺省的IP
class TcpServer
{
public:
TcpServer(const uint16_t& port=gport,const std::string& ip=defaultIp )
:_listenSocket(-1)
,_port(port)
,_ip(ip)
{
}
void InitServer()//初始化服務(wù)器
{
//1、創(chuàng)建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、設(shè)置監(jiān)聽狀態(tài)
if(-1==listen(_listenSocket,gbacklog))
{
LogMessage(FATAL,"listen socket error");
exit(LISTEN_ERR);
}
LogMessage(NORMAL,"listen socket success");
}
void Start()//啟動服務(wù)器
{
while(1)
{
//4、服務(wù)器獲取客戶端連接請求
struct sockaddr_in peer;//輸出型參數(shù),拿到客戶端的信息
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);//必須關(guān)閉使用完畢的sock,否則文件描述符泄漏(雖然下一句代碼exit(0),孫子進程退出也會釋放文件描述符,最好還是手動關(guān)一下)
exit(0);
}
close(sock);//這是用于通信的套接字fd,父進程和孫子進程都有這個文件描述符,父進程關(guān)了,該文件描述符引用技術(shù)-1,直至孫子進程退出,該fd才會減為0,關(guān)閉
//父進程
//waitpid()
pid_t ret=waitpid(id,nullptr,0);//這里不能用非阻塞等待,否則父進程先跑去執(zhí)行其他代碼,可能會被卡在accept出不來了(沒有新的客戶端來連接的話)
if(ret>0)
{
std::cout<<"waitsucceess"<<ret<<std::endl;
}
}
}
void ServerIO(int sock)
{
char buffer[1024];
while(1)
{
//服務(wù)器讀取客戶端數(shù)據(jù),通過套接字sock這個文件描述符讀取數(shù)據(jù)
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]";
//服務(wù)器將數(shù)據(jù)處理后發(fā)送回客戶端
write(sock,outBuffer.c_str(),outBuffer.size());
}
else if(0==n)//服務(wù)器read返回值為0,說明客戶端關(guān)閉了
{
LogMessage(NORMAL,"client quit,server quit");
break;
}
}
}
~TcpServer()
{}
private:
int _listenSocket;//監(jiān)聽客戶端的連接請求,不用于數(shù)據(jù)通信
uint16_t _port;//服務(wù)器端口號
std::string _ip;//服務(wù)器ip地址
};
}
區(qū)別在于這張圖里的代碼:
1、close(_listenSocket):關(guān)閉子進程的監(jiān)聽fd(雖然手動關(guān)不關(guān)都行,因為下一句代碼就讓子進程退出了,最好還是手動關(guān)一下)
2、if(fork()>0) exit(0):讓子進程創(chuàng)建孫子進程,子進程退出。提前干掉子進程,這樣父進程在外部就可以不用阻塞式等待子進程退出了。同時孫子進程成為孤兒進程,會被1號進程領(lǐng)養(yǎng),程序員無需關(guān)心孤兒進程的退出善后工作。
3、孫子進程close(sock):必須關(guān)閉使用完畢的sock,否則文件描述符泄漏(雖然下一句代碼exit(0),孫子進程退出也會釋放文件描述符,最好還是手動關(guān)一下)
4、父進程close(sock):這是用于通信的套接字fd,父進程和孫子進程都有這個文件描述符,父進程關(guān)了,該文件描述符引用計數(shù)-1,直至孫子進程退出,該fd才會減為0,關(guān)閉,所以父進程提前關(guān)閉該fd不會影響孫子進程。但是這里父進程如果不關(guān),客戶端連一個fd+1,存在文件描述符泄露。
5、pid_t ret=waitpid(id,nullptr,0):這里不能用非阻塞等待,否則父進程先跑去執(zhí)行其他代碼,可能會被卡在accept出不來了(如果沒有新的客戶端來連接的話,將一直卡在accept)
3、多線程版的TCP客戶端、服務(wù)器
更換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;//最大連接數(shù)=5+1
const static std::string defaultIp="0.0.0.0";//缺省的IP
class TcpServer;
struct ThreadData//用于線程函數(shù)傳參
{
ThreadData(TcpServer* self,const int& sock)
:_self(self)
,_sock(sock)
{}
TcpServer* _self;//this
int _sock;//通信fd
};
class TcpServer
{
public:
TcpServer(const uint16_t& port=gport,const std::string& ip=defaultIp )
:_listenSocket(-1)
,_port(port)
,_ip(ip)
{
}
void InitServer()//初始化服務(wù)器
{
//1、創(chuàng)建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、設(shè)置監(jiān)聽狀態(tài)
if(-1==listen(_listenSocket,gbacklog))
{
LogMessage(FATAL,"listen socket error");
exit(LISTEN_ERR);
}
LogMessage(NORMAL,"listen socket success");
}
void Start()//啟動服務(wù)器
{
while(1)
{
//4、服務(wù)器獲取客戶端連接請求
struct sockaddr_in peer;//輸出型參數(shù),拿到客戶端的信息
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);//線程調(diào)用服務(wù)函數(shù)
close(td->_sock);
delete td;
return nullptr;
}
void ServerIO(int sock)
{
char buffer[1024];
while(1)
{
//服務(wù)器讀取客戶端數(shù)據(jù),通過套接字sock這個文件描述符讀取數(shù)據(jù)
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]";
//服務(wù)器將數(shù)據(jù)處理后發(fā)送回客戶端
write(sock,outBuffer.c_str(),outBuffer.size());
}
else if(0==n)//服務(wù)器read返回值為0,說明客戶端關(guān)閉了
{
LogMessage(NORMAL,"client quit,server quit");
break;
}
}
}
~TcpServer()
{}
private:
int _listenSocket;//監(jiān)聽客戶端的連接請求,不用于數(shù)據(jù)通信
uint16_t _port;//服務(wù)器端口號
std::string _ip;//服務(wù)器ip地址
};
}
????????在一個進程中的所有線程都可以訪問到文件描述符表,屬于共享資源,一個線程所對應(yīng)的fd在使用完畢后需要進行關(guān)閉。
4、線程池版的TCP客戶端、服務(wù)器
????????其他文件和單進程版一樣。文章來源:http://www.zghlxwxcb.cn/news/detail-457606.html
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;//最大連接數(shù)=5+1
const static std::string defaultIp="0.0.0.0";//缺省的IP
class TcpServer;
struct ThreadData//用于線程函數(shù)傳參
{
ThreadData(TcpServer* self,const int& sock)
:_self(self)
,_sock(sock)
{}
TcpServer* _self;//this
int _sock;//通信fd
};
class TcpServer
{
public:
TcpServer(const uint16_t& port=gport,const std::string& ip=defaultIp )
:_listenSocket(-1)
,_port(port)
,_ip(ip)
{
}
void InitServer()//初始化服務(wù)器
{
//1、創(chuàng)建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、設(shè)置監(jiān)聽狀態(tài)
if(-1==listen(_listenSocket,gbacklog))
{
LogMessage(FATAL,"listen socket error");
exit(LISTEN_ERR);
}
LogMessage(NORMAL,"listen socket success");
}
void Start()//啟動服務(wù)器
{
//4、線程池初始化
ThreadPool<Task>::getInstance()->run();//線程啟動
while(1)
{
//5、服務(wù)器獲取客戶端連接請求
struct sockaddr_in peer;//輸出型參數(shù),拿到客戶端的信息
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;//監(jiān)聽客戶端的連接請求,不用于數(shù)據(jù)通信
uint16_t _port;//服務(wù)器端口號
std::string _ip;//服務(wù)器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:
//因為普通成員函數(shù)第一個參數(shù)是this指針,和回調(diào)方法不匹配,故改成static類型
static void* handlerTask(void* args)//args是ThreadData對象指針
{
ThreadData<T>* td=static_cast<ThreadData<T>*>(args);
while(1)
{
T t;
{ //RAII,出了作用域LockGuard會銷毀,將析構(gòu)鎖
LockGuard lockGuard(td->_threadPool->mutex());//加鎖
while(td->_threadPool->IsQueueEmpty())//如果隊列為空,則等待
{
td->_threadPool->ThreadWait();
}
//線程能走到這里,說明隊列一定有任務(wù)給線程
t=td->_threadPool->Pop();//從隊列中取出任務(wù)
}
t();//Task的operator()
}
delete td;//析構(gòu)ThreadData對象
return nullptr;
}
ThreadPool(const int& num=gnum)
:_num(num)
{
pthread_mutex_init(&_mutex,nullptr);
pthread_cond_init(&_cond,nullptr);
//創(chuàng)建線程
for(int i=0;i<_num;++i)
{
_threads.push_back(new Thread());
}
}
ThreadPool(const ThreadPool<T>&)=delete;//禁用拷貝構(gòu)造
ThreadPool<T>& operator=(const ThreadPool<T>&)=delete;//禁用賦值運算符重載
public://解決靜態(tài)handlerTask是靜態(tài)函數(shù)的問題,這幾個都是偷家函數(shù)
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<<"任務(wù)發(fā)送成功"<<std::endl;
}
~ThreadPool()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
for(const auto& t:_threads)
{
delete t;
}
}
static ThreadPool<T>* getInstance()//這里的static的作用是讓這個函數(shù)只有一份,獲取單例對象。tp是臨界資源,需要加鎖
{
if(nullptr==tp)//因為鎖只創(chuàng)建一次,防止線程進來被鎖阻塞
{
//只進來一次就夠了
_singletonLock.lock();
if(nullptr==tp)//說明對象還沒有被創(chuàng)建
{
tp=new ThreadPool<T>();
}
_singletonLock.unlock();
}
return tp;
}
private:
int _num;//線程個數(shù)
std::vector<Thread*> _threads;//使用vector存放線程
std::queue<T> _taskQueue;//任務(wù)隊列,往里面放任務(wù),它是共享資源,需要加鎖保護
pthread_mutex_t _mutex;//互斥鎖
pthread_cond_t _cond;//條件變量
static ThreadPool<T>* tp;//單例模式靜態(tài)的對象指針
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)//適應(yīng)快速響應(yīng)的任務(wù),這個任務(wù)while其實不太合適
{
//服務(wù)器讀取客戶端數(shù)據(jù),通過套接字sock這個文件描述符讀取數(shù)據(jù)
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]";
//服務(wù)器將數(shù)據(jù)處理后發(fā)送回客戶端
write(sock,outBuffer.c_str(),outBuffer.size());
}
else if(0==n)//服務(wù)器read返回值為0,說明客戶端關(guān)閉了
{
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;//函數(shù)對象
public:
Task()
{}
Task(int sock,func_t func)
:_sock(sock)
,_callBack(func)
{}
void operator()()//消費者調(diào)用
{
_callBack(_sock);
}
private:
int _sock;
func_t _callBack;//回調(diào)函數(shù)
};
5、守護進程+多線程版的TCP客戶端、服務(wù)器
????????其他文件和單進程版一樣。文章來源地址http://www.zghlxwxcb.cn/news/detail-457606.html
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"http://數(shù)據(jù)黑洞,向它寫入的數(shù)據(jù)會被吃掉,讀取數(shù)據(jù)什么都讀不到(不會使進程退出)
void DaemonSele(const char* currrPath=nullptr)
{
//1、讓調(diào)用進程屏蔽異常的信號
//SIGPIPE信號會在進程向一個已經(jīng)關(guān)閉的socket連接寫數(shù)據(jù)時產(chǎn)生,如果不處理這個信號,進程會被強制退出。通過忽略SIGPIPE信號,可以避免進程因為這個信號而退出。
signal(SIGPIPE,SIG_IGN);
//2、讓自己不是組長,調(diào)用setsid
if(fork()>0) exit(0);//守護進程也稱精靈進程,本質(zhì)就是一個孤兒進程
pid_t n=setsid();
assert(n!=-1);//失敗返回-1
//3、守護進程脫離終端,所以要關(guān)閉或重定向進程默認打開的文件及文件描述符
int fd=open(DEV,O_RDWR);//以讀寫的方式打開文件黑洞
if(fd>=0)//創(chuàng)建成功:重定向
{
dup2(fd,0);//將fd覆蓋標準輸入
dup2(fd,1);
dup2(fd,2);
close(fd);
}
else//創(chuàng)建失?。菏謩雨P(guān)閉文件描述符
{
close(0);
close(1);
close(2);
}
//4、進程執(zhí)行路徑更改(可改可不改)
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)//判斷外部傳入的參數(shù)是否為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();//守護進程化,讓這個獨立的孤兒進程去啟動服務(wù)器
tsvr->Start();
return 0;
}
到了這里,關(guān)于【網(wǎng)絡(luò)編程】實現(xiàn)UDP/TCP客戶端、服務(wù)器的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!