文章目录
- 前言
- 一、 服务端
- 1.ServerCal.cc(服务器主文件)
- 2.ServerCal.hpp
- 3.Sock.hpp(套接字封装)
- 4.TcpServer.hpp(服务器)
- 5.Protocol(自定义协议)
- 二、用户端
- 1.ClientCal
- 三、Log.hpp(日志)
- 四、makefile
前言
我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端`
一、 服务端
1.ServerCal.cc(服务器主文件)
#include "TcpServer.hpp"
#include "ServerCal.hpp"
#include <unistd.h>
#include "Daemon.hpp"
using namespace std;
static void Usage(const string &proc){
cout<<"\nUsage: "<<proc<<" port\n"<<endl;
}
int main(int argc,char*argv[]){
if(argc!=2){
Usage(argv[0]);
exit(0);
}
uint16_t port=stoi(argv[1]);
ServerCal cal;
TcpServer*tsvp=new TcpServer(port,bind(&ServerCal::Calculator,&cal,placeholders::_1));
//绑定服务器的回调函数
tsvp->InitServer();//初始化服务器
Daemon();//守护进程化
tsvp->Start();//启动
return 0;
}
2.ServerCal.hpp
#pragma once
#include <iostream>
#include "Protocol.hpp"
enum
{
Div_Zero = 1,
Mod_Zero,
Other_Oper
};
class ServerCal
{
public:
ServerCal()
{
}
Response CalculatorHelper(const Request &req)
{
Response resp(0, 0);
switch (req.op)
{
case '+':
resp.result = req.x + req.y;
break;
case '-':
resp.result = req.x - req.y;
break;
case '*':
resp.result = req.x * req.y;
break;
case '/':
{
if (req.y == 0)
resp.code = Div_Zero;
else
resp.result = req.x / req.y;
}
break;
case '%':
{
if (req.y == 0)
resp.code = Mod_Zero;
else
resp.result = req.x % req.y;
}
break;
default:
resp.code = Other_Oper;
break;
}
return resp;
}
// 传入的package形式:"len"\n"10 + 20"\n
std::string Calculator(std::string &package)
{
std::string content;
//Decode:去掉字符大小len和\n
bool r = Decode(package, &content);
// "len"\n"10 + 20"\n
if (!r)
return "";
// Decode后:"10 + 20"
Request req;
r = req.Deserialize(content);
// 反序列化过程:"10 + 20" ->x=10 op=+ y=20
if (!r)
return "";
content = "";
//进行计算
Response resp = CalculatorHelper(req); // result=30 code=0;
//result与code进行序列化放入content
resp.Serialize(&content); // "30 0"
//Encode:序列化后加上len与\n
content = Encode(content); // "len"\n"30 0"
return content;
}
~ServerCal()
{
}
};
3.Sock.hpp(套接字封装)
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
enum
{
SocketErr = 2,
BindErr,
ListenErr
};
const int backlog = 10;
class Sock
{
public:
Sock() {}
~Sock()
{
}
public:
void Socket()
{
sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd_ < 0)
{
lg(Fatal, "socker error,%s: %d", strerror(errno), errno);
exit(SocketErr);
}
}
void Bind(uint16_t port)
{
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0)
{
lg(Fatal, "bind error ,%s: %d", strerror(errno), errno);
exit(BindErr);
}
}
void Listen()
{
if (listen(sockfd_, backlog) < 0)
{
lg(Fatal, "listen error,%s: %d", strerror(errno), errno);
exit(ListenErr);
}
}
int Accept(string *clientip, uint16_t *clientport)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int newfd = accept(sockfd_, (struct sockaddr *)&peer, &len);
if (newfd < 0)
{
lg(Warning, "accept error,%s: %d", strerror(errno), errno);
return -1;
}
char ipstr[64];
inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));
*clientip = ipstr;
*clientport = ntohs(peer.sin_port);
return newfd;
}
bool Connect(const string &ip, const uint16_t port)
{
struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_port = htons(port);
inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));
int n = connect(sockfd_, (struct sockaddr *)&peer, sizeof(peer));
if (n == -1)
{
std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;
return false;
}
return true;
}
void Close()
{
close(sockfd_);
}
int Fd()
{
return sockfd_;
}
private:
int sockfd_;
};
4.TcpServer.hpp(服务器)
#pragma once
#include <functional>
#include <string>
#include <signal.h>
#include "Log.hpp"
#include "Socket.hpp"
using func_t = std::function<std::string(std::string &package)>;
class TcpServer
{
public:
TcpServer(uint16_t port, func_t callback) : port_(port), callback_(callback)
{
}
bool InitServer()
{
listensock_.Socket();
listensock_.Bind(port_);
listensock_.Listen();
lg(Info, "init server .... done");
return true;
}
void Start()
{
signal(SIGCHLD, SIG_IGN);
//防止子进程僵尸,子进程执行完PCB自动释放
signal(SIGPIPE, SIG_IGN);
//程序将忽略 SIGPIPE 信号,这意味着即使发生了向已关闭的管道或者 socket
//连接中写数据的情况,程序也不会因为收到 SIGPIPE 而终止。
//这样可以让程序继续正常执行,而不受这种情况的影响。
while (true)
{
std::string clientip;
uint16_t clientport;
int sockfd = listensock_.Accept(&clientip, &clientport);
if (sockfd < 0)
continue;
lg(Info, "accept a new link, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);
// 子进程提供服务
if (fork() == 0)
{
listensock_.Close();
std::string inbuffer_stream;
// 数据计算
while (true)
{
//读到客户端发送的报文
char buffer[1280];
ssize_t n = read(sockfd, buffer, sizeof(buffer));
if (n > 0)
{
//说明读取报文成功
buffer[n] = 0;
//每次都加上读取的数据是因为有可能我的一个完整报文分两次读取
//加上第二次的才能算上一个完整的报文
inbuffer_stream += buffer;
lg(Debug, "debug:\n%s", inbuffer_stream.c_str());
while (true)
{
std::string info = callback_(inbuffer_stream);
//调用string Calculator(std::string &package)
if (info.empty())//没有读出说明数据处理失败
break;
lg(Debug, "debug, response:\n%s", info.c_str());
lg(Debug, "debug:\n%s", inbuffer_stream.c_str());
//write写入处理好后的计算过的数据(经过了序列化)
write(sockfd, info.c_str(), info.size());
}
}
else if (n == 0)
break;
else
break;
}
exit(0);
}
//父进程关闭多余的文件描述符
close(sockfd);
}
}
~TcpServer()
{
}
private:
uint16_t port_;
Sock listensock_;
func_t callback_;
};
5.Protocol(自定义协议)
#pragma once
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
const std::string blank_space_sep = " ";
const std::string protocol_sep = "\n";
//编码:给序列化后的字符串加上字符长度与\n
std::string Encode(std::string &content)
{
std::string package = std::to_string(content.size());
package += protocol_sep;
package += content;
package += protocol_sep;
// "len"\n"x op y"\nXXXXXX
return package;
}
// "protocolnumber"\n"len"\n"x op y"\nXXXXXX
//目的:截取字符串“x op y”放入content中,并把与此字符串
//相关的数据从总报文package中移除
bool Decode(std::string &package, std::string *content)
{
std::size_t pos = package.find(protocol_sep);
if(pos == std::string::npos) return false;
//len_str:len的字符串形式
std::string len_str = package.substr(0, pos);
std::size_t len = std::stoi(len_str);
// package = len_str + content_str + 2
//
std::size_t total_len = len_str.size() + len + 2;
if(package.size() < total_len) return false;
*content = package.substr(pos+1, len);
// earse 移除报文 package.erase(0, total_len);
package.erase(0, total_len);
return true;
}
// json, protobuf
class Request
{
public:
Request(int data1, int data2, char oper) : x(data1), y(data2), op(oper)
{
}
Request()
{}
public:
bool Serialize(std::string *out)
{
#ifdef MySelf
// 构建报文的有效载荷
// struct => string, "x op y"
std::string s = std::to_string(x);
s += blank_space_sep;
s += op;
s += blank_space_sep;
s += std::to_string(y);
*out = s;
return true;
#else
Json::Value root;
root["x"] = x;
root["y"] = y;
root["op"] = op;
// Json::FastWriter w;
Json::StyledWriter w;
*out = w.write(root);
return true;
#endif
}
bool Deserialize(const std::string &in)
// 目的:从"x op y"中分离出x=?,y=?,op=?填入类的成员变量
{
#ifdef MySelf
std::size_t left = in.find(blank_space_sep);
if (left == std::string::npos)
return false;
std::string part_x = in.substr(0, left);
std::size_t right = in.rfind(blank_space_sep);
if (right == std::string::npos)
return false;
std::string part_y = in.substr(right + 1);
if (left + 2 != right)
return false;
op = in[left + 1];
x = std::stoi(part_x);
y = std::stoi(part_y);
return true;
#else
Json::Value root;
Json::Reader r;
r.parse(in, root);
x = root["x"].asInt();
y = root["y"].asInt();
op = root["op"].asInt();
return true;
#endif
}
void DebugPrint()
{
std::cout << "新请求构建完成: " << x << op << y << "=?" << std::endl;
}
public:
// x op y
int x;
int y;
char op; // + - * / %
};
class Response
{
public:
Response(int res, int c) : result(res), code(c)
{
}
Response()
{}
public:
bool Serialize(std::string *out)
{
#ifdef MySelf
// "result code"
// 构建报文的有效载荷
std::string s = std::to_string(result);
s += blank_space_sep;
s += std::to_string(code);
*out = s;
return true;
#else
Json::Value root;
root["result"] = result;
root["code"] = code;
// Json::FastWriter w;
Json::StyledWriter w;
*out = w.write(root);
return true;
#endif
}
bool Deserialize(const std::string &in) // "result code"
{
#ifdef MySelf
std::size_t pos = in.find(blank_space_sep);
if (pos == std::string::npos)
return false;
std::string part_left = in.substr(0, pos);
std::string part_right = in.substr(pos+1);
result = std::stoi(part_left);
code = std::stoi(part_right);
return true;
#else
Json::Value root;
Json::Reader r;
r.parse(in, root);
result = root["result"].asInt();
code = root["code"].asInt();
return true;
#endif
}
void DebugPrint()
{
std::cout << "结果响应完成, result: " << result << ", code: "<< code << std::endl;
}
public:
int result;
int code; // 0,可信,否则!0具体是几,表明对应的错误原因
};
二、用户端
1.ClientCal
#include <iostream>
#include <string>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include "Socket.hpp"
#include "Protocol.hpp"
static void Usage(const std::string &proc)
{
std::cout << "\nUsage: " << proc << " serverip serverport\n"
<< std::endl;
}
// ./clientcal ip port
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(0);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
Sock sockfd;
sockfd.Socket();
bool r = sockfd.Connect(serverip, serverport);
if(!r) return 1;
srand(time(nullptr) ^ getpid());
int cnt = 1;
const std::string opers = "+-*/%=-=&^";
std::string inbuffer_stream;
while(cnt <= 10)
{
std::cout << "===============第" << cnt << "次测试....., " << "===============" << std::endl;
int x = rand() % 100 + 1;
usleep(1234);
int y = rand() % 100;
usleep(4321);
char oper = opers[rand()%opers.size()];
Request req(x, y, oper);//构造需求
req.DebugPrint();
std::string package;
req.Serialize(&package);
package = Encode(package);
//经过序列化传给服务器
write(sockfd.Fd(), package.c_str(), package.size());
char buffer[128];
ssize_t n = read(sockfd.Fd(), buffer, sizeof(buffer)); // 我们也无法保证我们能读到一个完整的报文
if(n > 0)
{
//读取服务器处理后的报文
buffer[n] = 0;
inbuffer_stream += buffer; // "len"\n"result code"\n
std::cout << inbuffer_stream << std::endl;
std::string content;
bool r = Decode(inbuffer_stream, &content);
// 目的:得到字符串"result code"
assert(r);
Response resp;
。。反序列化
r = resp.Deserialize(content);
assert(r);
resp.DebugPrint();
}
std::cout << "=================================================" << std::endl;
sleep(1);
cnt++;
}
sockfd.Close();
return 0;
}
三、Log.hpp(日志)
详细可参考我之前写的博客【Linux】记录错误信息日志的实现
#pragma once
#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
using namespace std;
#define SIZE 1024
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4
#define Screen 1
#define Onefile 2
#define ClassFile 3
#define LogFile "log.txt"
class Log{
public:
Log(){
printMethod=Screen;
path="./log/";
}
void Enable(int method){
printMethod=method;
}
string levelToString(int level)
{
switch (level)
{
case Info:
return "Info";
case Debug:
return "Debug";
case Warning:
return "Warning";
case Error:
return "Error";
case Fatal:
return "Fatal";
default:
return "None";
}
}
void printLog(int level,const string&logtxt){
switch(printMethod){
case Screen:
cout<<logtxt<<endl;
break;
case Onefile:
printOneFile(LogFile,logtxt);
break;
case ClassFile:
printClassFile(level,logtxt);
break;
default:
break;
}
}
void printOneFile(const string&logname,const string&logtxt){
string _logname=path+logname;
int fd=open(_logname.c_str(),O_WRONLY|O_CREAT|O_APPEND,0666);
if(fd<0){
return ;
}
write(fd,logtxt.c_str(),logtxt.size());
close(fd);
}
void printClassFile(int level,const string&logtxt){
string filename=LogFile;
filename+=".";
filename+=levelToString(level);
printOneFile(filename,logtxt);
}
~Log(){
}
void operator()(int level, const char *format, ...)
{
time_t t = time(nullptr);
struct tm *ctime = localtime(&t);
char leftbuffer[SIZE];
snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
va_list s;
va_start(s, format);
char rightbuffer[SIZE];
vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
va_end(s);
// 格式:默认部分+自定义部分
char logtxt[SIZE * 2];
snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);
// printf("%s", logtxt); // 暂时打印
printLog(level, logtxt);
}
private:
int printMethod;
string path;
};
Log lg;
四、makefile
.PHONY:all
all:servercal clientcal
Flag=#-DMySelf=1
Lib=-ljsoncpp
servercal:ServerCal.cc
g++ -o $@ $^ -std=c++11 $(Lib) $(Flag)
clientcal:ClientCal.cc
g++ -o $@ $^ -std=c++11 $(Lib) $(Flag)
.PHONY:clean
clean:
rm -f clientcal servercal