目录
UDP网络程序
简单通信版本(UDP)
准备工作(接口学习、分析)
整体代码(Server.hpp/Server.cpp/Client.hpp/Client.cpp)
添加“婴儿版”业务逻辑
英译汉翻译
my_shell
聊天室
linux和windows通信
TCP网络程序
简单通信版本(TCP)
准备工作(接口学习、分析)
版本改进
多进程版本
多线程版本
线程池版本
日志
守护进程编辑
UDP网络程序
简单通信版本(UDP)
准备工作(接口学习、分析)
●最终效果:客户端发送信息,服务端读取到客户端发送来的内容。
●接口学习和准备工作
1.socket:创建用于通信的端点。(创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
下述代码中要传的参数:
2.struct sockaddr_in:这个结构里主要有三部分信息: 地址类型, 端口号, IP地址。
3.in_addr:用来表示一个IPv4的IP地址. 其实就是一个32位的整数。
4.bzero:将每个字节初始化为0。
例:
struct sockaddr_in local;
bzero(&local,sizeof(local));
5.bind:绑定端口号 (TCP/UDP, 服务器)。
例:
int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
6.recvfrom:从套接字接收消息。
//读取发送过来的内容
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
7.sendto:在套接字上发送消息。
例:
//发送数据和其他必要的东西
sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
●代码分析
1.服务端和客户端逻辑框架
2.服务端创建socket
a.调用socket接口(创建 socket 文件描述符)。
b.结构体填充端口号、IP地址。
c.调用bind,绑定端口号 (TCP/UDP, 服务器)。服务端显示的绑定。
3.客户端创建socket
a.调用socket接口(创建 socket 文件描述符)。
b.客户端不用显示的绑定端口号,原因是客户端的端口号是多少并不重要,只要确保唯一性即可。对于客户端而言,操作系统帮我们完成了这项工作。
4.启动服务器
a.服务器一般而言都是死循环。
b.通过recvfrom从套接字接收消息。
5.启动客户端
a.通过sendto,在套接字上发送消息。
整体代码(Server.hpp/Server.cpp/Client.hpp/Client.cpp)
makefile
cc=g++
.PHONY:all
all:UdpServer UdpClient
UdpServer:UdpServer.cpp
$(cc) -o $@ $^ -std=c++11
UdpClient:UdpClient.cpp
$(cc) -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f UdpClient UdpServer
服务端
udpServer.hpp:
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;
//服务端IP的默认值
static const string defaultIp = "0.0.0.0";
static const int gnum = 1024;
//枚举一些错误码
enum {USAGE_ERR = 1,SOCKET_ERR,BIND_ERR};
class UdpServer
{
public:
//构造,完成对端口号,服务端Ip和_sockfd(类似文件描述符,作用是取代IP和端口号标识服务进程唯一性)
UdpServer(const uint16_t& port,const string &ip = defaultIp)
:_port(port)
,_ip(ip)
,_sockfd(-1)
{}
//创建socket
void initServer()
{
//1.创建socket,网络套接字标识,数据报
_sockfd = socket(AF_INET,SOCK_DGRAM,0);
//
if(_sockfd == -1)
{
cerr<<"socket error:"<<errno<<" : "<<strerror(errno)<<endl;
exit(SOCKET_ERR);
}
cout<<"socket success: "<<" : "<<_sockfd<<endl;
//2.绑定端口号和ip
struct sockaddr_in local;
bzero(&local,sizeof(local));
//填充结构体
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = inet_addr(_ip.c_str());
//绑定
int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
if(n == -1)
{
cout<<"bind error:"<<errno<<" : "<<strerror(errno)<<endl;
exit(BIND_ERR);
}
}
//启动服务器
void start()
{
char buffer[gnum];
while(true)
{
//读取数据
struct sockaddr_in peer;
socklen_t len = sizeof(peer); //必填
//读取发送过来的内容
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
//读取成功
if(s > 0)
{
buffer[s] = 0;
//客户端ip
string clientip = inet_ntoa(peer.sin_addr);
//客户端端口号
uint16_t clientpoot = ntohs(peer.sin_port);
//发送过来的内容
string message = buffer;
cout<<clientip<<"["<<clientpoot<<"]# "<<message<<endl;
}
}
}
~UdpServer(){}
private:
uint16_t _port;//端口号
string _ip;//服务端ip
int _sockfd;//套接字fd
};
udpServer.cpp
#include "UdpServer.hpp"
#include <memory>
using namespace std;
//使用手册,服务端要指明端口号
static void Usage(string proc)
{
cout<<"\nUsage:\n\t"<<proc<<"local_port\n\n";
}
int main(int argc,char* argv[])
{
//argv表中的元素个数如果不是二
//未按照规定使用,打印手册提示
if(argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
//正确使用,获取端口号,将字符串转换成uint16_t类型
uint16_t port = atoi(argv[1]);
//创建服务端对象,用智能指针管理
unique_ptr<UdpServer> user(new UdpServer(port));
//创建套接字
user->initServer();
//启动服务器
user->start();
return 0;
}
客户端
udpClient.hpp
#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;
class udpClient
{
public:
//构造:初始化ip+端口号+_sockfd
udpClient(const string &serverip,const uint16_t &_serverport)
:_serverip(serverip)
,_serverport(_serverport)
,_sockfd(-1)
,_quit(false)
{}
//创建套接字
void initClient()
{
//创建socket
_sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(_sockfd == -1)
{
cerr<<"socker error: "<<errno<<" : "<<strerror(errno)<<endl;
exit(2);
}
cout<<"socket success: "<<" : "<<_sockfd<<endl;
//不用显示的绑定端口号,操作系统帮我们完成这项工作,原因是服务端需要保证唯一性,要让大家都能看到
//不能随意改变,而客户端不被关心,是剁多少都可以,显示写可能被重复使用,出现冲突。
}
//启动客户端
void run()
{
struct sockaddr_in server;
memset(&server,0,sizeof(server));
//结构体天成
server.sin_family=AF_INET;
server.sin_addr.s_addr = inet_addr(_serverip.c_str());
server.sin_port = htons(_serverport);
string message;
while(!_quit)
{
cout<<"Please Enter# ";
cin>>message;//要发送的内容
//发送数据和其他必要的东西
sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
}
}
~udpClient(){}
private:
int _sockfd;//套接字fd
string _serverip;//要发送给哪个主机
uint16_t _serverport;//确定服务进程
bool _quit;
};
udpClient.cpp
#include "UdpClient.hpp"
#include <memory>
using namespace std;
//客户端使用手册
static void Usage(string proc)
{
cout<<"\nUsage:\n\t"<<proc<<"local_port\n\n";
}
int main(int argc,char* argv[])
{
//判断是否正确使用
if(argc != 3)
{
Usage(argv[0]);
exit(1);
}
//获取目的主机(服务端)的ip
string serverip = argv[1];
//获取目的主机目标程序端口号(服务进程端口号)
uint16_t serverport = atoi(argv[2]);
//创建客户端对象,并用智能指针管理
unique_ptr<udpClient> ucli(new udpClient(serverip,serverport));
//创建客户端套接字
ucli->initClient();
//启动客户端
ucli->run();
return 0;
}
测试结果:多个客户端向服务端发送消息,服务端接收消息。
添加“婴儿版”业务逻辑
英译汉翻译
●思路分析:
a.业务逻辑要和网络通信的代码解耦,这样写的好处就是当有新增业务,或者需要改动的时候,只对业务部分代码进行处理。服务端增加一个成员变量,一个指向回调方法的函数指针。回调函数实现业务逻辑。
b.在下述代码的实现中,将单词和翻译按照“good:好”的形式添加到配置文件dict.txt中。查找到单词后向客户端返回翻译后的结果。反之返回"unknown"!
c.对2号信号进行捕捉,执行加载字典的动作。也就说,如果某个单词没有查询到,可以动态的将该单词填入字典中,通过发送信号重新加载字典。
d.对于字典的加载以及处理,和业务逻辑详见下述代码。其余部分和上述基础版通信基本相同。
e.“业务”逻辑
const string dictTxt = "./dict.txt";
unordered_map<string,string> dict;
static void Usage(string proc)
{
cout<<"\nUsage:\n\t"<<proc<<"local_port\n\n";
}
static bool cutString(const string &target,string* s1,string* s2,const string& sep)
{
auto pos = target.find(sep);
if(pos == string::npos)return false;
*s1 = target.substr(0,pos);
*s2 = target.substr(pos+sep.size());
return true;
}
static void initDict()
{
ifstream in(dictTxt,std::ios::binary);
if(!in.is_open())
{
cerr<<"open file"<<dictTxt<<"error"<<endl;
exit(1);
}
string line,key,value;
while(getline(in,line))
{
if(cutString(line,&key,&value,":"))
{
dict.insert(make_pair(key,value));
}
}
in.close();
cout<<"load dict success"<<endl;
}
void handlerMessage(int sockfd,string clientip,uint16_t clientport,string message)
{
//对message进行业务处理,解耦
string response_message;
auto iter = dict.find(message);
if(iter == dict.end()) response_message = "unknown";
else response_message = iter->second;
//返回
struct sockaddr_in client;
bzero(&client,sizeof(client));
client.sin_family = AF_INET;
client.sin_port = htons(clientport);
client.sin_addr.s_addr = inet_addr(clientip.c_str());
//cout<<response_message<<endl;
sendto(sockfd,response_message.c_str(),response_message.size(),0,(struct sockaddr*)&client,sizeof(client));
}
static void debugPrint()
{
//cout<<"1111"<<endl;
for(auto& dt : dict)
{
cout<<dt.first<<" # "<<dt.second<<endl;
}
}
//捕捉方法
void reload(int signo)
{
(void)signo;
initDict();
}
●整体代码
makefiel
.PHONY:all
all:UdpServer UdpClient
UdpServer:UdpServer.cpp
g++ -o $@ $^ -std=c++11
UdpClient:UdpClient.cpp
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -rf UdpClient UdpServer
服务端
UdpServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;
static const string defaultIp = "0.0.0.0";
static const int gnum = 1024;
enum {USAGE_ERR = 1,SOCKET_ERR,BIND_ERR};
class UdpServer
{
public:
UdpServer(const uint16_t& port,const string &ip = defaultIp)
:_port(port)
,_ip(ip)
,_sockfd(-1)
{}
//创建socket
void initServer()
{
//1.创建socket
_sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(_sockfd == -1)
{
cerr<<"socket error:"<<errno<<" : "<<strerror(errno)<<endl;
exit(SOCKET_ERR);
}
cout<<"socket success: "<<" : "<<_sockfd<<endl;
//2.绑定端口号和ip
struct sockaddr_in local;
bzero(&local,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = inet_addr(_ip.c_str());
int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
if(n == -1)
{
cout<<"bind error:"<<errno<<" : "<<strerror(errno)<<endl;
exit(BIND_ERR);
}
}
//启动服务器
void start()
{
char buffer[gnum];
while(true)
{
//读取数据
//struct sockaddr_in peer;
//socklen_t len = sizeof(peer);
struct sockaddr_in peer;
socklen_t len = sizeof(peer); //必填
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
//ssize_t s = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
if(s > 0)
{ //cout<<"2222"<<endl;
buffer[s] = 0;
string clientip = inet_ntoa(peer.sin_addr);
uint16_t clientpoot = ntohs(peer.sin_port);
string message = buffer;
cout<<clientip<<"["<<clientpoot<<"]# "<<message<<endl;
}
sleep(1);
}
}
~UdpServer(){}
private:
uint16_t _port;
string _ip;
int _sockfd;
};
UdpServer.cpp
#include "UdpServer.hpp"
#include <memory>
#include <fstream>
#include <unordered_map>
#include <signal.h>
const string dictTxt = "./dict.txt";
unordered_map<string,string> dict;
static void Usage(string proc)
{
cout<<"\nUsage:\n\t"<<proc<<"local_port\n\n";
}
static bool cutString(const string &target,string* s1,string* s2,const string& sep)
{
auto pos = target.find(sep);
if(pos == string::npos)return false;
*s1 = target.substr(0,pos);
*s2 = target.substr(pos+sep.size());
return true;
}
static void initDict()
{
ifstream in(dictTxt,std::ios::binary);
if(!in.is_open())
{
cerr<<"open file"<<dictTxt<<"error"<<endl;
exit(1);
}
string line,key,value;
while(getline(in,line))
{
if(cutString(line,&key,&value,":"))
{
dict.insert(make_pair(key,value));
}
}
in.close();
cout<<"load dict success"<<endl;
}
void handlerMessage(int sockfd,string clientip,uint16_t clientport,string message)
{
//对message进行业务处理,解耦
string response_message;
auto iter = dict.find(message);
if(iter == dict.end()) response_message = "unknown";
else response_message = iter->second;
//返回
struct sockaddr_in client;
bzero(&client,sizeof(client));
client.sin_family = AF_INET;
client.sin_port = htons(clientport);
client.sin_addr.s_addr = inet_addr(clientip.c_str());
//cout<<response_message<<endl;
sendto(sockfd,response_message.c_str(),response_message.size(),0,(struct sockaddr*)&client,sizeof(client));
}
static void debugPrint()
{
//cout<<"1111"<<endl;
for(auto& dt : dict)
{
cout<<dt.first<<" # "<<dt.second<<endl;
}
}
//捕捉方法
void reload(int signo)
{
(void)signo;
initDict();
}
//服务端
int main(int argc,char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
signal(2,reload);
initDict();
//debugPrint();
unique_ptr<UdpServer> user(new UdpServer(handlerMessage,port));
user->initServer();
user->start();
return 0;
}
客户端
UdpClient.hpp
#include <iostream>
#include <cerrno>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;
enum
{
USAGE_ERR = 1,
SOCKET_ERR,
BIDD_ERR
};
class UdpClient
{
public:
UdpClient(const string& ip,const uint16_t& port)
:_serverip(ip)
,_serverport(port)
,_sockfd(-1)
{}
void initClient()
{
_sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(_sockfd == -1)
{
cout<<"SOCKET_ERR:"<<errno<<" : "<<strerror(errno)<<endl;
exit(SOCKET_ERR);
}
cout<<"socket success "<<_sockfd<<endl;
}
void run()
{
struct sockaddr_in server;
bzero(&server,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(_serverport);
server.sin_addr.s_addr = inet_addr(_serverip.c_str());
string message;
char cmdline[1024];
while(true)
{
cout<<"Plase Enter# ";
cin>>message;
sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
char buffer[1024];
struct sockaddr_in temp;
socklen_t temp_len = sizeof(temp);
size_t n = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&temp_len);
if(n > 0)buffer[n] = '\0';
cout<<"翻译结果为# \n"<<buffer<<endl;
}
}
~UdpClient(){}
private:
int _sockfd;//套接字fd
string _serverip;//服务端ip
uint16_t _serverport;//服务端端口号
};
UdpClient.cpp
#include "UdpClient.hpp"
#include <memory>
static void Usage(string proc)
{
cout<<"\nUsage:\n\t"<<proc<<"local_ip and local_port\n\n";
}
int main(int argc,char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(SOCKET_ERR);
}
string serverip = argv[1];
uint16_t serverport = atoi(argv[2]);
unique_ptr<UdpClient> ucli(new UdpClient(serverip,serverport));
ucli->initClient();
ucli->run();
return 0;
}
my_shell
●思路分析:
a.对比英汉翻译业务,只需要在Server.cpp中添加业务。这也能体现代码解耦的好处。
b.对于my_shell,客户端向服务端发送命令(只处理一些简单命令),服务端接收命令后,将在服务器执行该命令后的结果返回给客户端。
c.popen(打开、关闭通往或离开进程的管道流)。popen = pipe + fork + exec*。
d.“业务”逻辑
//demo2
void execCommand(int sockfd,string clientip,uint16_t clientport,string cmd)
{
string response;
FILE* fp = popen(cmd.c_str(),"r");
if(fp == nullptr)response = cmd + "exe failed";
char line[1024];
while(fgets(line,sizeof(line),fp))
{
response += line;
}
pclose(fp);
//返回
struct sockaddr_in client;
bzero(&client,sizeof(client));
client.sin_family = AF_INET;
client.sin_port = htons(clientport);
client.sin_addr.s_addr = inet_addr(clientip.c_str());
sendto(sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&client,sizeof(client));
}
●整体代码
服务端
#include "UdpServer.hpp"
#include <memory>
#include <fstream>
#include <unordered_map>
#include <signal.h>
static void Usage(string proc)
{
cout<<"\nUsage:\n\t"<<proc<<"local_port\n\n";
}
//demo2
void execCommand(int sockfd,string clientip,uint16_t clientport,string cmd)
{
string response;
FILE* fp = popen(cmd.c_str(),"r");
if(fp == nullptr)response = cmd + "exe failed";
char line[1024];
while(fgets(line,sizeof(line),fp))
{
response += line;
}
pclose(fp);
//返回
struct sockaddr_in client;
bzero(&client,sizeof(client));
client.sin_family = AF_INET;
client.sin_port = htons(clientport);
client.sin_addr.s_addr = inet_addr(clientip.c_str());
sendto(sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&client,sizeof(client));
}
//服务端
int main(int argc,char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
unique_ptr<UdpServer> user(new UdpServer(execCommand,port));
user->initServer();
user->start();
return 0;
}
客户端
#include <iostream>
#include <cerrno>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;
enum
{
USAGE_ERR = 1,
SOCKET_ERR,
BIDD_ERR
};
class UdpClient
{
public:
UdpClient(const string& ip,const uint16_t& port)
:_serverip(ip)
,_serverport(port)
,_sockfd(-1)
{}
void initClient()
{
_sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(_sockfd == -1)
{
cout<<"SOCKET_ERR:"<<errno<<" : "<<strerror(errno)<<endl;
exit(SOCKET_ERR);
}
cout<<"socket success "<<_sockfd<<endl;
}
void run()
{
struct sockaddr_in server;
bzero(&server,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(_serverport);
server.sin_addr.s_addr = inet_addr(_serverip.c_str());
string message;
char cmdline[1024];
while(true)
{
cout<<"[zxy@VM-2023_6_11]$ ";
fgets(cmdline,sizeof(cmdline),stdin);
message = cmdline;
sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
char buffer[1024];
struct sockaddr_in temp;
socklen_t temp_len = sizeof(temp);
size_t n = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&temp_len);
if(n > 0)buffer[n] = '\0';
cout<<"\n"<<buffer<<endl;
}
}
~UdpClient(){}
private:
int _sockfd;//套接字fd
string _serverip;//服务端ip
uint16_t _serverport;//服务端端口号
};
聊天室
●思路分析:
1.增加onlineUsetr.hpp模块,主要包括用户的属性,如:用户ip、端口号(还可以添加昵称等)。用map存储用户标识和上线用户的结构体,提供添加和删除用户的成员方法,提供将消息发送给所有在线用户的成员方法们提供判断一个用户是否在线的成员方法。
2.服务端只增加聊天室业务,主要进行逻辑判断,如果用户未上线,打印提示信息。如果服务端发来的是上线或者下线请求,完成对应任务。如果是在线用户在正常发送消息,将该消息路由给所有的在线用户。
3.客户端创建了一个线程,主线程向服务端发送消息。创建的新线程接收服务端返回的信息。
4."业务“逻辑
OnlineUser onlineuser;
void routeMessage(int sockfd,string clientip,uint16_t clientport,string message)
{
//上线
if(message == "online")onlineuser.addUser(clientip,clientport);
//下线
if(message == "offline")onlineuser.delUser(clientip,clientport);
//已在线,消息要路由给所有的在线用户
if(onlineuser.isOnline(clientip,clientport))
{
//消息的路由
onlineuser.broadcastMessage(sockfd,clientip,clientport,message);
}
else//先上线
{
struct sockaddr_in client;
bzero(&client,sizeof(client));
client.sin_family = AF_INET;
client.sin_port = htons(clientport);
client.sin_addr.s_addr = inet_addr(clientip.c_str());
string response = "你还没有上线,请先上线,运行:online";
sendto(sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&client,sizeof(client));
}
}
●整体代码
makefile
.PHONY:all
all:Server Client
Server:Server.cpp
g++ -o $@ $^ -std=c++11
Client:Client.cpp
g++ -o $@ $^ -lpthread -std=c++11
.PHONY:clean
clean:
rm -rf Server Client
onlineUser.hpp
#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <cerrno>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
class User
{
public:
//构造
User(const string& ip,const uint16_t& port)
:_ip(ip)
,_port(port)
{}
~User()
{}
//返回ip
string ip(){return _ip;}
//返回端口号
uint16_t port(){return _port;}
private:
string _ip;//用户ip
uint16_t _port;//登录用户端口号
};
class OnlineUser
{
public:
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)
{
for(auto& user : users)
{
struct sockaddr_in client;
bzero(&client,sizeof(client));
//填充结构体
client.sin_family = AF_INET;
client.sin_port = htons(user.second.port());
client.sin_addr.s_addr = inet_addr(user.second.ip().c_str());
string s = ip + "-" + to_string(port) + "#";
s += message;
//发送
sendto(sockfd,s.c_str(),s.size(),0,(struct sockaddr*)&client,sizeof(client));
}
}
~OnlineUser(){}
private:
unordered_map<string,User> users;
};
服务端
UdpServer.cpp
#include "Server.hpp"
#include "onlineUser.hpp"
#include <memory>
static void Usage(string proc)
{
cout<<"\nUsage:\n\t"<<proc<<"local_port\n\n";
}
OnlineUser onlineuser;
void routeMessage(int sockfd,string clientip,uint16_t clientport,string message)
{
//上线
if(message == "online")onlineuser.addUser(clientip,clientport);
//下线
if(message == "offline")onlineuser.delUser(clientip,clientport);
//已在线,消息要路由给所有的在线用户
if(onlineuser.isOnline(clientip,clientport))
{
//消息的路由
onlineuser.broadcastMessage(sockfd,clientip,clientport,message);
}
else//先上线
{
struct sockaddr_in client;
bzero(&client,sizeof(client));
client.sin_family = AF_INET;
client.sin_port = htons(clientport);
client.sin_addr.s_addr = inet_addr(clientip.c_str());
string response = "你还没有上线,请先上线,运行:online";
sendto(sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&client,sizeof(client));
}
}
int main(int argc,char* argv[])
{
//只需要一个端口号
if(argc != 2)
{
//错误的使用,打印手册
Usage(argv[0]);
exit(USAGE_ERR);
}
//将端口号进行转换
uint16_t pport = atoi(argv[1]);
//创建服务端对象,并用智能指针管理
unique_ptr<Server> user(new Server(routeMessage,pport));
//创建套接字
user->initServer();
//启动服务器
user->start();
return 0;
}
客户端
UdpClient.hpp
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
enum{USAGE_ERR = 1,SOCK_ERR,BIND_ERR};
class Client
{
public:
Client(const string& serverip,const uint16_t& serverport)
:_serverip(serverip)
,_serverport(serverport)
,_sockfd(-1)
{}
//创建套接字
void initClinet()
{
//创建socket
_sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(_sockfd == -1)
{
cerr<<"socket errno"<<errno<<" : "<<strerror(errno)<<endl;
exit(SOCK_ERR);
}
cout<<"socket success: "<<" : "<<_sockfd<<endl;
//不用显示的绑定,操作系统完成这项工作。因为对于客户端而言,端口号是
//多少不被关心
}
static void* readMessage(void* args)
{
int sockfd = *(static_cast<int*>(args));
pthread_detach(pthread_self());
while(true)
{
char buffer[1024];
struct sockaddr_in temp;
socklen_t temp_len = sizeof(temp);
size_t n = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&temp_len);
if(n >= 0)
{
buffer[n] = 0;
}
cout<<buffer<<endl;
}
return nullptr;
}
//客户端发送请求
void run()
{
pthread_create(&_reader,nullptr,readMessage,(void*)&_sockfd);
struct sockaddr_in server;
bzero(&server,sizeof(&server));
//填充结构体
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(true)
{
//cout<<"Plase Say# "<<endl;
//cin>>message;
fprintf(stderr,"Enter# ");
fflush(stderr);
fgets(cmdline,sizeof(cmdline),stdin);
cmdline[strlen(cmdline)-1] = 0;
message = cmdline;
sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
}
}
~Client(){}
private:
int _sockfd;
uint16_t _serverport;
string _serverip;
pthread_t _reader;
};
服务端和客户端1:
客户端2(本机)
客户端3(另一台机器)
linux和windows通信
linux环境代码
server.hpp
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <functional>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;
static const string defaultIp = "0.0.0.0";
static const int gnum = 1024;
enum {USAGE_ERR = 1,SOCKET_ERR,BIND_ERR};
typedef function<void(int,string,uint16_t,string)> func_t;
class UdpServer
{
public:
UdpServer(const func_t& func,const uint16_t& port,const string &ip = defaultIp)
:_port(port)
,_ip(ip)
,_sockfd(-1)
,_callback(func)
{}
//创建socket
void initServer()
{
//1.创建socket
_sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(_sockfd == -1)
{
cerr<<"socket error:"<<errno<<" : "<<strerror(errno)<<endl;
exit(SOCKET_ERR);
}
cout<<"socket success: "<<" : "<<_sockfd<<endl;
//2.绑定端口号和ip
struct sockaddr_in local;
bzero(&local,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = inet_addr(_ip.c_str());
int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
if(n == -1)
{
cout<<"bind error:"<<errno<<" : "<<strerror(errno)<<endl;
exit(BIND_ERR);
}
}
//启动服务器
void start()
{
char buffer[gnum];
while(true)
{
//读取数据
//struct sockaddr_in peer;
//socklen_t len = sizeof(peer);
struct sockaddr_in peer;
socklen_t len = sizeof(peer); //必填
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
//ssize_t s = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
if(s > 0)
{
buffer[s] = 0;
string clientip = inet_ntoa(peer.sin_addr);
uint16_t clientport = ntohs(peer.sin_port);
string message = buffer;
cout<<clientip<<"["<<clientport<<"]# "<<message<<endl;
_callback(_sockfd,clientip,clientport,message);
}
sleep(1);
}
}
~UdpServer(){}
private:
uint16_t _port;
string _ip;
int _sockfd;
func_t _callback;
};
server.cpp
#include <iostream>
#include "Server.hpp"
#include <memory>
using namespace std;
static void Usage(string proc)
{
cout<<"\nUsage:\n\t"<<proc<<"local_port\n\n";
}
void routeMessage(int sockfd,string clientip,uint16_t clientport,string message)
{
string response = message;
response += "[server echo]";
//返回
struct sockaddr_in client;
bzero(&client,sizeof(client));
client.sin_family = AF_INET;
client.sin_port = htons(clientport);
client.sin_addr.s_addr = inet_addr(clientip.c_str());
sendto(sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&client,sizeof(client));
}
int main(int argc,char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
unique_ptr<UdpServer> user(new UdpServer(routeMessage,port));
user->initServer();
user->start();
return 0;
}
windows环境代码(client)
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <cstring>
#include <string>
#include <WinSock2.h>
#include <Ws2tcpip.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
uint16_t serverport = 8080;
string serverip = "43.138.30.78";
int main()
{
WSAData wsd;
//启动Winsock
if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
{
cout << "WSAStartup Error = " << WSAGetLastError() << endl;
}
else
{
cout << "WSAStartup Success" << endl;
}
SOCKET csock = socket(AF_INET, SOCK_DGRAM, 0);
if (csock == SOCKET_ERROR)
{
cout << "Sock Error = " << WSAGetLastError() << endl;
return 1;
}
else
{
cout << "socket Success" << endl;
}
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
inet_pton(AF_INET, serverip.c_str(), &server.sin_addr.s_addr);//vs2013版本以上使用新的函数转换IP地址
#define NUM 1024
char inbuffer[NUM];
string line;
while (true)
{
cout << "Please Enter# ";
getline(cin, line);
int n = sendto(csock, line.c_str(), line.size(), 0, (struct sockaddr*)&server, sizeof(server));
if (n < 0)
{
cerr << "sendto error" << endl;
break;
}
struct sockaddr_in peer;
int peerlen = sizeof(peer);
inbuffer[0] = 0;
n = recvfrom(csock, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&peer, &peerlen);
if (n > 0)
{
inbuffer[n] = 0;
cout << "client# " << inbuffer << endl;
}
else break;
}
closesocket(csock);
WSACleanup();
return 0;
}
效果展示:
TCP网络程序
简单通信版本(TCP)
准备工作(接口学习、分析)
●最终效果:客户端发送消息,服务端读取到客户端发来的消息,在将收到消息回显给客户端。
●接口学习和准备工作
1.socket:创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
a.socket()打开一个网络通讯端口,如果打开成功的话就像open一样返回一个文件描述符。打开失败的话返回-1。
b.应用程序可以像读文件一样用read/write在网络上收发数据。
c.对于IPv4,family参数指定为AF_INET。
d.对于TCP协议,type参数指定为SOCK_STREAM,表示面向流的传输协议。
e.第三个参数指定为0即可。
2.bind:绑定端口号 (TCP/UDP, 服务器)。
a.服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接; 服务器需要调用bind绑定一个固定的网络地址和端口号;
b. bind()成功返回0,失败返回-1。
c. bind()的作用是将参数sockfd和myaddr绑定在一起, 使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号;
d. struct sockaddr *是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度。
e.结构体初始化,例如:
3.listen:开始监听socket (TCP, 服务器)。
a. listen()声明sockfd处于监听状态, 并且最多允许有backlog个客户端处于连接等待状态, 如果接收到更多的连接请求就忽略;
b.listen()成功返回0,失败返回-1;
4.accept:接收请求 (TCP, 服务器)。
a. 三次握手完成后, 服务器调用accept()接受连接。
b. 如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。
c. addr是一个传出参数,accept()返回时传出客户端的地址和端口号。
d. 如果给addr 参数传NULL,表示不关心客户端的地址。
e. addrlen参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区addr的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。
5.connect:建立连接 (TCP, 客户端)。
a.客户端需要调用connect()连接服务器;
b. connect和bind的参数形式一致, bind的参数是自己的地址, 而connect的参数是对方的地址;
c. connect()成功返回0,出错返回-1;
●整体代码
makefile
.PHONY:all
all:tcpClient tcpServer
tcpClient:tcpClient.cpp
g++ -o $@ $^ -std=c++11
tcpServer:tcpServer.cpp
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -rf tcpServer tcpClient
log.hpp
#pragma once
#include <iostream>
using namespace std;
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4
void logMessage(int level,const char* format)
{
cout<<level<<": "<<format<<endl;
}
server.hpp
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "log.hpp"
using namespace std;
enum
{
USAGE_ERR = 1,
SOCK_ERR,
BIND_ERR,
LISTEN_ERR
};
static const uint16_t pport = 8080;
static const int gbacklog = 5;
class tcpServer
{
public:
tcpServer(const uint16_t& port = pport)
:_port(port)
,_listensock(-1)
{}
void initServer()
{
//创建套接字
_listensock = socket(AF_INET,SOCK_STREAM,0);
if(_listensock < 0)
{
logMessage(FATAL,"creat socker error");
exit(SOCK_ERR);
}
logMessage(NORMAL,"creat socker error");
//填充结构体绑定网络信息
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;
int n = bind(_listensock,(struct sockaddr*)&local,sizeof(local));
if(n < 0)
{
logMessage(FATAL,"bind socket error");
exit(BIND_ERR);
}
logMessage(NORMAL,"bind socket success");
//设置监听状态
if(listen(_listensock,gbacklog) < 0)
{
logMessage(FATAL,"listen socket error");
exit(LISTEN_ERR);
}
logMessage(NORMAL,"listen socket success");
}
void start()
{
while(true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(_listensock,(struct sockaddr *)&peer,&len);
if(sock < 0)
{
logMessage(ERROR,"accept error,next");
continue;
}
serviceIO(sock);
close(sock);
}
}
void serviceIO(int sock)
{
char buffer[1024];
while(true)
{
ssize_t n = read(sock,buffer,sizeof(buffer)-1);
if(n > 0)
{
buffer[n] = 0;
cout<<"recv message: "<<buffer<<endl;
string outbuffer = buffer;
outbuffer += "server[echo]";
write(sock,outbuffer.c_str(),outbuffer.size());
}
else if(n == 0)
{
logMessage(NORMAL,"client quit,me too!");
break;
}
}
}
~tcpServer(){}
private:
int _listensock;//揽客张三,监听链接到来获取新链接
uint16_t _port;
};
server.cpp
#include "tcpServer.hpp"
#include <stdlib.h>
#include <memory>
using namespace std;
static void Usage(string prve)
{
cout<<"\n\tUsage: loca_prot\n\n";
}
int main(int argc,char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
unique_ptr<tcpServer> tsvr(new tcpServer(port));
tsvr->initServer();
tsvr->start();
return 0;
}
client.hpp
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define NUM 1024
using namespace std;
class tcpClient
{
public:
tcpClient(const string& serverip,const uint16_t &serverport)
:_sock(-1)
,_serverip(serverip)
,_serverport(serverport)
{}
void initClient()
{
//创建socket
_sock = socket(AF_INET,SOCK_STREAM,0);
if(_sock < 0)
{
cerr<<"socket creat error"<<endl;
exit(2);
}
}
void start()
{
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());
if(connect(_sock,(struct sockaddr*)&server,sizeof(server)) != 0)
{
cerr<<"socket connect error"<<endl;
}
else
{
string msg;
while(true)
{
cout<<"Enter# ";
getline(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;
cout<<"Server回显# "<<buffer<<endl;
}
else
{
break;
}
}
}
}
~tcpClient()
{
if(_sock >= 0)
{
close(_sock);
}
}
private:
int _sock;
string _serverip;
uint16_t _serverport;
};
client.cpp
#include "tcpClient.hpp"
#include <memory>
using namespace std;
static void Usage(string prve)
{
cout<<"\n\tUsage: loca_ip and loca_port\n\n";
}
int main(int argc,char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(1);
}
string serverip = argv[1];
uint16_t serverport = atoi(argv[2]);
unique_ptr<tcpClient> tcli(new tcpClient(serverip,serverport));
tcli->initClient();
tcli->start();
return 0;
}
问题:客户端的访问是串行的。
版本改进
多进程版本
普通版本的基础上,在服务端做简单的改动。
整体代码
server.cpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <unistd.h>
#include "log.hpp"
using namespace std;
enum
{
USAGE_ERR = 1,
SOCK_ERR,
BIND_ERR,
LISTEN_ERR,
ACCEPT_ERR
};
static const uint16_t pport = 8080;
static const int gbacklog = 5;
class TcpServer
{
public:
TcpServer(const uint16_t &port = pport)
:_listensock(-1)
,_port(pport)
{}
void initServer()
{
//创建套接字
_listensock = socket(AF_INET,SOCK_STREAM,0);
if(_listensock < 0)
{
logMessage(FATAL,"creat socker error");
exit(SOCK_ERR);
}
logMessage(NORMAL,"creat socker success");
//填充结构体,绑定网络信息
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;
int n = bind(_listensock,(struct sockaddr*)&local,sizeof(local));
if(n < 0)
{
logMessage(FATAL,"bind socket error");
exit(BIND_ERR);
}
logMessage(NORMAL,"bind socket success");
//开始监听
if(listen(_listensock,gbacklog) <0)
{
logMessage(FATAL,"listen socket error");
exit(LISTEN_ERR);
}
logMessage(NORMAL,"listen socket success");
}
void start()
{
while(true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(_listensock,(struct sockaddr*)&peer,&len);
if(sock < 0)
{
logMessage(ACCEPT_ERR,"accept error,next");
continue;
}
pid_t id = fork();
if(id == 0)
{
//子进程
close(_listensock);
if(fork()>0) exit(0);
serviceIO(sock);
close(sock);
exit(0);
}
close(sock);
pid_t ret = waitpid(id,nullptr,0);
if(ret>0)
{
cout<<"waitsuccess: "<<ret<<endl;
}
}
}
void serviceIO(int sock)
{
char buffer[1024];
while(true)
{
ssize_t n = read(sock,buffer,sizeof(buffer)-1);
if(n>0)
{
buffer[n] = 0;
cout<<"recv message: "<<buffer<<endl;
string outbuffer = buffer;
outbuffer += "server[echo]";
write(sock,outbuffer.c_str(),outbuffer.size());
}
else if(n == 0)
{
logMessage(NORMAL,"client quit,me too!");
break;
}
}
}
~TcpServer(){}
private:
int _listensock;//揽客张三,负责建立链接
uint16_t _port;//服务端绑定端口号
};
多线程版本
普通版本的基础上,在服务端做简单的改动。
TreeadDate
创建多线程
线程方法
server.cpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <unistd.h>
#include <pthread.h>
#include "log.hpp"
using namespace std;
enum
{
USAGE_ERR = 1,
SOCK_ERR,
BIND_ERR,
LISTEN_ERR,
ACCEPT_ERR
};
static const uint16_t pport = 8080;
static const int gbacklog = 5;
class TcpServer;
class ThreadData
{
public:
ThreadData(TcpServer* self,int sock)
:_self(self)
,_sock(sock)
{}
public:
TcpServer* _self;
int _sock;
};
class TcpServer
{
public:
TcpServer(const uint16_t &port = pport)
:_listensock(-1)
,_port(pport)
{}
void initServer()
{
//创建套接字
_listensock = socket(AF_INET,SOCK_STREAM,0);
if(_listensock < 0)
{
logMessage(FATAL,"creat socker error");
exit(SOCK_ERR);
}
logMessage(NORMAL,"creat socker success");
//填充结构体,绑定网络信息
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;
int n = bind(_listensock,(struct sockaddr*)&local,sizeof(local));
if(n < 0)
{
logMessage(FATAL,"bind socket error");
exit(BIND_ERR);
}
logMessage(NORMAL,"bind socket success");
//开始监听
if(listen(_listensock,gbacklog) <0)
{
logMessage(FATAL,"listen socket error");
exit(LISTEN_ERR);
}
logMessage(NORMAL,"listen socket success");
}
void start()
{
while(true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(_listensock,(struct sockaddr*)&peer,&len);
if(sock < 0)
{
logMessage(ACCEPT_ERR,"accept error,next");
continue;
}
logMessage(NORMAL,"accept success");
cout<<"sock: "<<sock<<endl;
//多线程版本
pthread_t tid;
ThreadData* td = new ThreadData(this,sock);
pthread_create(&tid,nullptr,threadRoutine,td);
}
}
static void* threadRoutine(void* args)
{
//线程分离
pthread_detach(pthread_self());
ThreadData* td = static_cast<ThreadData*>(args);
td->_self->serviceIO(td->_sock);
close(td->_sock);
delete td;
return nullptr;
}
void serviceIO(int sock)
{
char buffer[1024];
while(true)
{
ssize_t n = read(sock,buffer,sizeof(buffer)-1);
if(n>0)
{
buffer[n] = 0;
cout<<"recv message: "<<buffer<<endl;
string outbuffer = buffer;
outbuffer += "server[echo]";
write(sock,outbuffer.c_str(),outbuffer.size());
}
else if(n == 0)
{
logMessage(NORMAL,"client quit,me too!");
break;
}
}
}
~TcpServer(){}
private:
int _listensock;//揽客张三,负责建立链接
uint16_t _port;//服务端绑定端口号
};
线程池版本
●除了服务端和客户端外,还增加了线程池,“简陋”任务,RAII风格锁的模块。
Server.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <unistd.h>
#include <pthread.h>
#include "SingletonTP.hpp"
#include "log.hpp"
#include "Task.hpp"
using namespace std;
enum
{
USAGE_ERR = 1,
SOCK_ERR,
BIND_ERR,
LISTEN_ERR,
ACCEPT_ERR
};
static const uint16_t pport = 8080;
static const int gbacklog = 5;
class TcpServer;
class ThreadData
{
public:
ThreadData(TcpServer* self,int sock)
:_self(self)
,_sock(sock)
{}
public:
TcpServer* _self;
int _sock;
};
class TcpServer
{
public:
TcpServer(const uint16_t &port = pport)
:_listensock(-1)
,_port(pport)
{}
void initServer()
{
//创建套接字
_listensock = socket(AF_INET,SOCK_STREAM,0);
if(_listensock < 0)
{
logMessage(FATAL,"creat socker error");
exit(SOCK_ERR);
}
logMessage(NORMAL,"creat socker success");
//填充结构体,绑定网络信息
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;
int n = bind(_listensock,(struct sockaddr*)&local,sizeof(local));
if(n < 0)
{
logMessage(FATAL,"bind socket error");
exit(BIND_ERR);
}
logMessage(NORMAL,"bind socket success");
//开始监听
if(listen(_listensock,gbacklog) <0)
{
logMessage(FATAL,"listen socket error");
exit(LISTEN_ERR);
}
logMessage(NORMAL,"listen socket success");
}
void start()
{
//线程池版本,线程初始化
ThreadPool<CalTask>::getInstance()->run();
logMessage(NORMAL,"Thread init success");
while(true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(_listensock,(struct sockaddr*)&peer,&len);
if(sock < 0)
{
logMessage(ACCEPT_ERR,"accept error,next");
continue;
}
logMessage(NORMAL,"accept success");
cout<<"sock: "<<sock<<endl;
//线程池版本
ThreadPool<CalTask>::getInstance()->push(CalTask(sock,serviceIO));
}
}
~TcpServer(){}
private:
int _listensock;//揽客张三,负责建立链接
uint16_t _port;//服务端绑定端口号
};
线程池(单例模式)
#pragma once
#include <cstring>
#include <vector>
#include <string>
#include <mutex>
#include <queue>
#include "Thread.hpp"
#include "LockGuard.hpp"
const int gnum = 10;
using namespace zxy;
template<class T>
class ThreadPool;
template<class T>
class ThreadDate
{
public:
ThreadDate(ThreadPool<T>* tp,const std::string& n)
:threadpool(tp),name(n)
{}
public:
ThreadPool<T>* threadpool;//指向线程池对象的指针
std::string name;//线程名
};
template<class T>
class ThreadPool
{
private:
//构造,将一批线程放入线程池中
//初始化锁和条件变量
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());
}
}
void operator=(const ThreadPool &) = delete;
ThreadPool(const ThreadPool &) = delete;
//回调方法
static void* handlerTask(void* args)
{
ThreadDate<T>* td = static_cast<ThreadDate<T>*>(args);
while(true)
{
T t;
{
LockGuard lockguard(td->threadpool->mutex());
while(td->threadpool->IsQueueEmpty())
{
//没有任务
td->threadpool->threadWait();
}
//走到这一定有任务
t = td->threadpool->pop();
}//临界资源域
//std::cout<<td->name<<"获取一个任务"<<t.ToString()<<"start..." ;
t();
}
delete td;
return nullptr;
}
public:
//添加任务
void push(const T& in)
{
LockGuard lockguard(&_mutex);
//向任务队列中添加数据
_task_queue.push(in);
//走到这里,任务队列中一定有数据
pthread_cond_signal(&_cond);
}
//获取任务
T pop()
{
T t = _task_queue.front();
_task_queue.pop();
return t;
}
//启动线程
void run()
{
for(auto& thd : _threads)
{
ThreadDate<T>* td = new ThreadDate<T>(this,thd->TreadName());
thd->Start(handlerTask,td);
std::cout<<thd->TreadName()<<"start..."<<std::endl;
}
}
//析构
~ThreadPool()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
for(const auto &t : _threads)
{
delete t;
}
}
static ThreadPool<T>* getInstance()
{
if(tp == nullptr)
{
_singleton_lock.lock();
if(tp == nullptr)
{
tp = new ThreadPool<T>();
}
_singleton_lock.unlock();
}
return tp;
}
public:
void lockQueue(){pthread_mutex_lock(&_mutex);}
void unlockQueue(){pthread_mutex_unlock(&_mutex);}
bool IsQueueEmpty(){return _task_queue.empty();}
void threadWait(){pthread_cond_wait(&_cond,&_mutex);}
pthread_mutex_t* mutex(){return &_mutex;}
private:
int _num;
std::vector<Thread*> _threads;//线程池
std::queue<T> _task_queue;//任务队列
pthread_mutex_t _mutex;//互斥量
pthread_cond_t _cond;//条件变量
static ThreadPool<T>* tp;
static std::mutex _singleton_lock;
};
template <class T>
ThreadPool<T>* ThreadPool<T>::tp = nullptr;
template <class T>
std::mutex ThreadPool<T>::_singleton_lock;
通信任务
#pragma once
#include <iostream>
#include <string>
#include <cstdio>
#include <map>
#include <functional>
void serviceIO(int sock)
{
char buffer[1024];
while(true)
{
ssize_t n = read(sock,buffer,sizeof(buffer)-1);
if(n>0)
{
buffer[n] = 0;
cout<<"recv message: "<<buffer<<endl;
string outbuffer = buffer;
outbuffer += "server[echo]";
write(sock,outbuffer.c_str(),outbuffer.size());
}
else if(n == 0)
{
logMessage(NORMAL,"client quit,me too!");
break;
}
}
}
class CalTask
{
//指向任务的函数指针
typedef std::function<void(int)> func_t;
public:
CalTask(){}
//构造
CalTask(int sock,func_t func)
:_sock(sock)
,_func(func)
{
}
//处理任务后的结果
void operator()()
{
_func(_sock);
}
private:
int _sock;
func_t _func;
};
RAII风格的锁
#pragma once
#include <iostream>
#include <pthread.h>
class Mutex
{
public:
Mutex(pthread_mutex_t* lock_p = nullptr)
:_lock_p(lock_p)
{}
void lock()
{
if(_lock_p)
{
pthread_mutex_lock(_lock_p);
}
}
void unlock()
{
if(_lock_p)
{
pthread_mutex_unlock(_lock_p);
}
}
~Mutex(){}
private:
pthread_mutex_t* _lock_p;
};
class LockGuard
{
public:
LockGuard(pthread_mutex_t* mutex):_mutex(mutex)
{
_mutex.lock();//在构造函数中进行加锁
}
~LockGuard()
{
_mutex.unlock();//在析构函数中进行解锁
}
private:
Mutex _mutex;
};
日志
#pragma once
#include <iostream>
#include <stdarg.h>
#include <string>
#include <ctime>
#include <unistd.h>
using namespace std;
#define LOG_NORMAL "log.txt"
#define LOG_ERR "log.error"
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4
const char* to_levelstr(int level)
{
switch(level)
{
case DEBUG: return "DEBUG";
case NORMAL: return "NORMAL";
case WARNING:return "WARNING";
case ERROR: return "ERROR";
case FATAL: return "FATAL";
default: return nullptr;
}
}
void logMessage(int lever,const char* format,...)
{
//cout<<lever<<" : "<<format<<endl;
#define NUM 1024
char logprefix[NUM];
snprintf(logprefix,sizeof(logprefix),"[%s][%ld][pid:%d]",
to_levelstr(lever),(long int)time(nullptr),getpid());
char logcontent[NUM];
va_list arg;
va_start(arg,format);
vsnprintf(logcontent,sizeof(logcontent),format,arg);
//cout<<logprefix<<logcontent<<endl;
FILE* log = fopen(LOG_NORMAL,"a");
FILE* err = fopen(LOG_ERR,"a");
if(log != nullptr && err != nullptr)
{
FILE* curr = nullptr;
if(lever == DEBUG || lever == NORMAL || lever == WARNING)curr = log;
if(lever == ERROR || lever == FATAL) curr = err;
if(curr)fprintf(curr,"%s%s\n",logprefix,logcontent);
fclose(log);
fclose(err);
}
}
守护进程![](https://img-blog.csdnimg.cn/d305dbd07946406195491c9b83635458.png)
1.守护进程(Daemon)是在操作系统后台运行的一种特殊进程,通常用于执行一些系统级别的任务,如网络服务、定时任务、日志记录等。它们通常在系统启动时启动,并一直运行,直到系统关闭或手动停止。
2.守护进程通常不与用户交互,也不会在屏幕上显示任何信息。它们在后台默默地运行,不会占用用户的资源或干扰用户的工作。守护进程通常以root权限运行,因为它们需要访问系统资源和执行特权操作。
3.守护进程的好处是可以在系统空闲时执行任务,避免了用户手动执行任务的繁琐和错误。同时,它们可以在系统崩溃或重启后自动恢复,确保系统的稳定性和可靠性。
daemon.hpp
#pragma once
#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 daemomSelf(const char* currPath = nullptr)
{
//让调用进程忽略掉异常的信号
signal(SIGPIPE,SIG_IGN);
//让自己不是组长
if(fork() > 0)
{
exit(0);
}
//子进程,守护进程当组长
pid_t n = setsid();
assert(n != 1);
//守护进程脱离终端
int fd = open(DEV,O_RDWR);
if(fd > 0)
{
dup2(fd,0);
dup2(fd,1);
dup2(fd,2);
close(fd);
}
else
{
close(0);
close(1);
close(2);
}
//可选:执行路径发生改变
if(currPath)chdir(currPath);
}
服务器关掉,依然能回显消息。
查看日志