Err.h
#pragma once
enum
{
USAGE_ERR = 1,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
CONNECT_ERR,
SETSID_ERR,
OPEN_ERR
};
Protocol.h
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <cstring>
#include <jsoncpp/json/json.h>//需要通过执行sudo yum install -y jsoncpp-devel来使用该库。系统默认的头文件只能到/usr/include这里。做为第三方库需要在编译的时候-ljsoncpp
#include "Util.hpp"
// #define MYSELF 1//定义了MYSELF就用我们自己的方法,没有定义我们会看到#ifdef MYSELF和#else之间我们自己定义的代码就相当于被注释掉了,采用线程的函数接口来进行序列化。
// 给网络版本计算机定制协议
namespace protocol_ns
{
#define SEP " "
#define SEP_LEN strlen(SEP) // 绝对不能写成sizeof
#define HEADER_SEP "\r\n"
#define HEADER_SEP_LEN strlen("\r\n")
// "长度"\r\n"协议号"\r\n""_x _op _y"\r\n,通过协议号我们一份代码可以存在多份协议,也就是对应多对request和response类。
// "10 + 20" => "7"\r\n""10 + 20"\r\n => 报头 + 有效载荷
// 请求/响应 = 报头\r\n有效载荷\r\n
// "10 + 20" => "7"\r\n""10 + 20"\r\n
std::string AddHeader(const std::string &str)
{
std::cout << "AddHeader 之前:\n"
<< str << std::endl;
std::string s = std::to_string(str.size());
s += HEADER_SEP;
s += str;
s += HEADER_SEP;
std::cout << "AddHeader 之后:\n"
<< s << std::endl;
return s;
}
// "7"\r\n""10 + 20"\r\n => "10 + 20"
std::string RemoveHeader(const std::string &str, int len)
{
std::cout << "RemoveHeader 之前:\n"
<< str << std::endl;
std::string res = str.substr(str.size() - HEADER_SEP_LEN - len, len);//从后面来提取有效荷载
std::cout << "RemoveHeader 之后:\n"
<< res << std::endl;
return res;
}
// const &: 输入
// *: 输出
// &: 输入输出
int ReadPackage(int sock, std::string &inbuffer, std::string *package)//从sock套接字里面读取报文,有完整报文就向上交付,否则一直读取。读到的报文如果不完整不应该丢弃,而是应该放在inbuffer里面,
{
std::cout << "ReadPackage inbuffer 之前:\n"
<< inbuffer << std::endl;
// 边读取
char buffer[1024];
ssize_t s = recv(sock, buffer, sizeof(buffer - 1), 0);
if (s <= 0)
return -1;//读取出错return-1
buffer[s] = 0;
inbuffer += buffer;
std::cout << "ReadPackage inbuffer 之中:\n"
<< inbuffer << std::endl;
// 边分析,从inbuffer中分析提取完整的报文 "7"\r\n""10 + 20"\r\n
auto pos = inbuffer.find(HEADER_SEP);
if (pos == std::string::npos)
return 0;//没有读取到完整的报文return 0 // inbuffer我没看可什么都没有动
std::string lenStr = inbuffer.substr(0, pos); // 获取了头部字符串, 里面以字符串呈现出来了有效荷载的长度,inbuffer我没看可什么都没有动
int len = Util::toInt(lenStr); // 有效荷载的长度,"123" -> 123 inbuffer此时可什么都没有动
int targetPackageLen = lenStr.size() + len + 2 * HEADER_SEP_LEN; // 完整报文的长度,inbuffer此时什么都没有动
if (inbuffer.size() < targetPackageLen)//说明inbuffer里面并灭有完整的报文
return 0;//没有读取到完整的报文return 0 // inbuffer我没看可什么都没有动
*package = inbuffer.substr(0, targetPackageLen); // 提取到了完整的报文包含有效再和和报头,inbuffer我没看可什么都没有动,package保存的是"7"\r\n""10 + 20"\r\n
inbuffer.erase(0, targetPackageLen); // 从inbuffer中直接移除整个报文
std::cout << "ReadPackage inbuffer 之后:\n"
<< inbuffer << std::endl;
return len;
}
///
// Request && Response都要提供序列化和反序列化功能
// 1. 自己手写 -- done
// 2. 用别人写的 --- json, xml, protobuf(精品课)
// class CalRequest
class Request
{
public:
Request() {}
Request(int x, int y, char op) : _x(x), _y(y), _op(op)
{
}
// struct->string
// 目前: "_x _op _y"
bool Serialize(std::string *outStr)
{
*outStr = "";
#ifdef MYSELF
std::string x_string = std::to_string(_x);
std::string y_string = std::to_string(_y);
// 手动序列化
*outStr = x_string + SEP + _op + SEP + y_string;
std::cout << "Request Serialize:\n"
<< *outStr << std::endl;
#else
//使用json进行序列化的工作
Json::Value root; // Value: 一种万能对象, 接受任意的kv类型
root["x"] = _x;
root["y"] = _y;
root["op"] = _op;
// Json::FastWriter writer; // Writer:是用来进行序列化的,将struct -> string
Json::StyledWriter writer;//和上面本质是一样的,只是采用了一种不一样的序列话方法而已。
*outStr = writer.write(root);//write对象的write方法
#endif
return true;
}
// string->struct
bool Deserialize(const std::string &inStr)
{
#ifdef MYSELF
// inStr : 10 + 20 => [0]=>10, [1]=>+, [2]=>20
// string -> vector
std::vector<std::string> result;
Util::StringSplit(inStr, SEP, &result);
if (result.size() != 3)
return false;
if (result[1].size() != 1)
return false;
_x = Util::toInt(result[0]);
_y = Util::toInt(result[2]);
_op = result[1][0];
#else
Json::Value root;
Json::Reader reader; // Reader: 用来进行反序列化的,将string->struct,名字很好,读数据之前一定要进行反序列化。
reader.parse(inStr, root);//喂给函数一个json串instr,将结果放到root万能对象里面
_x = root["x"].asInt();
_y = root["y"].asInt();
_op = root["op"].asInt();//op是char类型,在转的时候也当成整数进行处理就可以了。
#endif
Print();
return true;
}
void Print()
{
std::cout << "_x: " << _x << std::endl;
std::cout << "_y: " << _y << std::endl;
std::cout << "_z: " << _op << std::endl;
}
~Request() {}
public:
// _x op _y ==> 10 / 0 ? ==> 10 * 9 ?
int _x;
int _y;
char _op;
};
// class CalResponse
class Response
{
public:
Response() {}
Response(int result, int code) : _result(result), _code(code)
{
}
// struct->string
bool Serialize(std::string *outStr)
{
*outStr = "";
#ifdef MYSELF
//_result _code
std::string res_string = std::to_string(_result);
std::string code_string = std::to_string(_code);
*outStr = res_string + SEP + code_string;
std::cout << "Response Serialize:\n"
<< *outStr << std::endl;
#else
Json::Value root;
root["result"] = _result;
root["code"] = _code;
// Json::FastWriter writer;
Json::StyledWriter writer;
*outStr = writer.write(root);
#endif
return true;
}
// string->struct
bool Deserialize(const std::string &inStr)
{
#ifdef MYSELF
// 10 0, 10 1
std::vector<std::string> result;
Util::StringSplit(inStr, SEP, &result);
if (result.size() != 2)
return false;
_result = Util::toInt(result[0]);
_code = Util::toInt(result[1]);
#else
Json::Value root;
Json::Reader reader;
reader.parse(inStr, root);
_result = root["result"].asInt();
_code = root["code"].asInt();
#endif
Print();
return true;
}
void Print()
{
std::cout << "_result: " << _result << std::endl;
std::cout << "_code: " << _code << std::endl;
}
~Response() {}
public:
int _result;
int _code; // 0 success, 1,2,3,4代表不同的错误码
};
}
TcpClient.cc
#include "TcpClient.hpp"
#include "Sock.hpp"
#include "Protocol.hpp"
#include <iostream>
#include <string>
using namespace protocol_ns;
static void usage(std::string proc)
{
std::cout << "Usage:\n\t" << proc << " serverip serverport\n"
<< std::endl;
}
enum
{
LEFT,
OPER,
RIGHT
};
// 10+20
Request ParseLine(const std::string &line)
{
std::string left, right;
char op;
int status = LEFT;
int i = 0;
while(i < line.size())
{
// if(isdigit(e)) left.push_back;
switch (status)
{
case LEFT:
if (isdigit(line[i]))
left.push_back(line[i++]);
else
status = OPER;
break;
case OPER:
op = line[i++];
status = RIGHT;
break;
case RIGHT:
if (isdigit(line[i]))
right.push_back(line[i++]);
break;
}
}
Request req;
std::cout << "left: " << left << std::endl;
std::cout << "right: " << right << std::endl;
std::cout << "op: " << op << std::endl;
req._x = std::stoi(left);
req._y = std::stoi(right);
req._op = op;
return req;
}
// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{
if (argc != 3)
{
usage(argv[0]);
exit(USAGE_ERR);
}
std::string serverip = argv[1];
uint16_t serverport = atoi(argv[2]);
Sock sock;
sock.Socket();
int n = sock.Connect(serverip, serverport);
if (n != 0)
return 1;
std::string buffer;
while (true)
{
std::cout << "Enter# "; // 1+1,2*9
std::string line;
std::getline(std::cin, line);
// std::cout << "data1# ";
// std::cin >> req._x;
// std::cout << "data2# ";
// std::cin >> req._y;
// std::cout << "op# ";
// std::cin >> req._op;
Request req = ParseLine(line);//将“1+1”这种字符串解析到req当中
std::cout << "test: " << req._x << req._op << req._y << std::endl;
// 1. 序列化
std::string sendString;
req.Serialize(&sendString);
// 2. 添加报头
sendString = AddHeader(sendString);
// 3. send
send(sock.Fd(), sendString.c_str(), sendString.size(), 0);
// 4. 获取响应
std::string package;
int n = 0;
START:
n = ReadPackage(sock.Fd(), buffer, &package);
if (n == 0)//读取成功了,但是没有读到一个完整求,所以还要去start继续读取。
goto START;
else if (n < 0)//读取错误就直接break退出得了
break;
else
{
}
// 5. 去掉报头
package = RemoveHeader(package, n);
// 6. 反序列化
Response resp;
resp.Deserialize(package);
std::cout << "result: " << resp._result << "[code: " << resp._code << "]" << std::endl;
}
sock.Close();
return 0;
}
TcpServer.h
#pragma once
#include <iostream>
#include <pthread.h>
#include <functional>
#include "Sock.hpp"
#include "Protocol.hpp"
namespace tcpserver_ns
{
using namespace protocol_ns;
class TcpServer;
using func_t = std::function<Response(const Request &)>;
class ThreadData
{
public:
ThreadData(int sock, std::string ip, uint16_t port, TcpServer *tsvrp)
: _sock(sock), _ip(ip), _port(port), _tsvrp(tsvrp)
{
}
~ThreadData() {}
public:
int _sock;
std::string _ip;
uint16_t _port;
TcpServer *_tsvrp;
};
class TcpServer
{
public:
TcpServer(func_t func, uint16_t port) : _func(func), _port(port)
{
}
void InitServer()
{
// 1. 初始化服务器
_listensock.Socket();
_listensock.Bind(_port);
_listensock.Listen();
logMessage(Info, "init server done, listensock: %d", _listensock.Fd());
}
void Start()
{
for (;;)
{
std::string clientip;
uint16_t clientport;
int sock = _listensock.Accept(&clientip, &clientport);
if (sock < 0)
continue;
logMessage(Debug, "get a new client, client info : [%s:%d]", clientip.c_str(), clientport);
pthread_t tid;
ThreadData *td = new ThreadData(sock, clientip, clientport, this);
pthread_create(&tid, nullptr, ThreadRoutine, td);
}
}
static void *ThreadRoutine(void *args)
{
pthread_detach(pthread_self());
ThreadData *td = static_cast<ThreadData *>(args);
td->_tsvrp->ServiceIO(td->_sock, td->_ip, td->_port);
logMessage(Debug, "thread quit, client quit ...");
delete td;
return nullptr;
}
// 我们这个函数是被多线程调用的
void ServiceIO(int sock, const std::string &ip, const uint16_t &port)
{
std::string inbuffer;
while (true)
{
// 0. 你怎么保证你读到了一个完整的字符串报文?"7"\r\n""10 + 20"\r\n
std::string package;//用来存放一个完整的报文
int n = ReadPackage(sock, inbuffer, &package);
if (n == -1)//读取失败直接退出
break;
else if (n == 0)//读取成功,但是没有读取到完整的报文就继续循环再去读取
continue;
else//读取到一个完整的报文,并返回有效载荷的长度
{
// 一定得到了一个"7"\r\n""10 + 20"\r\n
// 1. 你需要的只是有效载荷"10 + 20"
package = RemoveHeader(package, n);
// decode
// 2. 假设已经读到了一个完整的string
Request req;
req.Deserialize(package); // 对读到的request字符串要进行反序列化
// 3. 直接提取用户的请求数据啦
Response resp = _func(req); // 业务逻辑!
// 4. 给用户返回响应 - 序列化
std::string send_string;
resp.Serialize(&send_string); // 对计算完毕的response结构要进行序列化,形成可发送字符串
// 5. 添加报头
send_string = AddHeader(send_string);
//encode
// 6. 发送
send(sock, send_string.c_str(), send_string.size(), 0);
}
}
close(sock);
}
~TcpServer()
{
_listensock.Close();
}
private:
uint16_t _port;
Sock _listensock;
func_t _func;
};
}
TcpServer.cc
#include "TcpServer.hpp"
#include <memory>
using namespace tcpserver_ns;
// ./calserver 8888
Response calculate(const Request &req)
{
// 走到这里,一定保证req是有具体数据的!
// _result(result), _code(code)
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 = 1;
else
resp._result = req._x / req._y;
break;
case '%':
if (req._y == 0)
resp._code = 2;
else
resp._result = req._x % req._y;
break;
default:
resp._code = 3;
break;
}
return resp;
}
int main()
{
uint16_t port = 8888;
std::unique_ptr<TcpServer> tsvr(new TcpServer(calculate, port)); // TODO
tsvr->InitServer();
tsvr->Start();
return 0;
}