需要云服务器等云产品来学习Linux的同学可以移步/-->腾讯云<--/-->阿里云<--/-->华为云<--/官网,轻量型云服务器低至112元/年,新用户首次下单享超低折扣。
目录
一、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";//缺省的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传入的对客户端数据处理的函数
,_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));//将一段内存初始化为全0
local.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);//初始化为全0
server.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;//用户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)
{
//服务端向客户端回发数据
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;
//启动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);//创建套接字
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+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()//初始化服务器
{
//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;
}
else
break;
}
}
}
~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+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()//初始化服务器
{
//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+1
const static std::string defaultIp="0.0.0.0";//缺省的IP
class TcpServer;
struct ThreadData//用于线程函数传参
{
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()//初始化服务器
{
//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客户端、服务器
其他文件和单进程版一样。文章来源:https://www.toymoban.com/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;//最大连接数=5+1
const static std::string defaultIp="0.0.0.0";//缺省的IP
class TcpServer;
struct ThreadData//用于线程函数传参
{
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()//初始化服务器
{
//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客户端、服务器
其他文件和单进程版一样。文章来源地址https://www.toymoban.com/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"//数据黑洞,向它写入的数据会被吃掉,读取数据什么都读不到(不会使进程退出)
void DaemonSele(const char* currrPath=nullptr)
{
//1、让调用进程屏蔽异常的信号
//SIGPIPE信号会在进程向一个已经关闭的socket连接写数据时产生,如果不处理这个信号,进程会被强制退出。通过忽略SIGPIPE信号,可以避免进程因为这个信号而退出。
signal(SIGPIPE,SIG_IGN);
//2、让自己不是组长,调用setsid
if(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;
}
到了这里,关于【网络编程】实现UDP/TCP客户端、服务器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!