Util.h
//存放工具方法
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
using namespace std;
class Util
{
public:
// 纯输入,在函数内部不会做修改: const &
// 输出: *
// 输入输出: &
static bool StringSplit(const string &str, const string &sep, vector<std::string> *result)//sep是分隔符
{
size_t start = 0;
// 10 + 20
// "abcd efg" -> for(int i = 0; i < 10; i++) != for(int i = 0; i <= 9; i++)
while (start < str.size())//循环切割
{
auto pos = str.find(sep, start);
if (pos == string::npos) break;
result->push_back(str.substr(start, pos-start));
// 位置的重新reload
start = pos + sep.size();
}
if(start < str.size()) result->push_back(str.substr(start));//将最后一个结构化的变量放入vector当中
return true;
}
static int toInt(const std::string &s)
{
return atoi(s.c_str());//字符串转整数
}
};
Sock.h
//对tcpsocket做一下封装
#pragma once
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include "Log.hpp"
#include "Err.hpp"
static const int gbacklog = 32;
static const int defaultfd = -1;
class Sock
{
public:
Sock() : _sock(defaultfd)
{
}
void Socket()
{
_sock = socket(AF_INET, SOCK_STREAM, 0);
if (_sock < 0)
{
logMessage(Fatal, "socket error, code: %d, errstring: %s", errno, strerror(errno));
exit(SOCKET_ERR);
}
}
void Bind(const 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(_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
logMessage(Fatal, "bind error, code: %d, errstring: %s", errno, strerror(errno));
exit(BIND_ERR);
}
}
void Listen()//将套接字设置成为listen状态
{
if (listen(_sock, gbacklog) < 0)
{
logMessage(Fatal, "listen error, code: %d, errstring: %s", errno, strerror(errno));
exit(LISTEN_ERR);
}
}
int Accept(std::string *clientip, uint16_t *clientport)//获取连接,第二三个参数是输出型参数,获得客户端的信息。
{
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
int sock = accept(_sock, (struct sockaddr *)&temp, &len);
if (sock < 0)
{
logMessage(Warning, "accept error, code: %d, errstring: %s", errno, strerror(errno));
}
else
{
*clientip = inet_ntoa(temp.sin_addr);//网络转主机序列
*clientport = ntohs(temp.sin_port);//网络转主机序列
}
return sock;
}
int Connect(const std::string &serverip, const uint16_t &serverport)//为tcp客户端准备的接口
{
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());
return connect(_sock, (struct sockaddr *)&server, sizeof(server));
}
int Fd()//用来获取套接字
{
return _sock;
}
~Sock()
{
if (_sock != defaultfd)
close(_sock);
}
private:
int _sock;//这个套接字是用来监听、connect、还是获取连接等等由用户自己来决定
};
Protocol.h
//制定协议,协议就是结构话的数据
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <cstring>
#include "Util.hpp"
// 给网络版本计算机定制协议
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""_x _op _y"\r\n
// "10 + 20" => "7"\r\n""10 + 20"\r\n => 报头 + 有效载荷,添加报头是为了保证用户正确的获得一个完整的报文string
// 请求/响应 = 报头\r\n有效载荷\r\n
// "10 + 20" => "7"\r\n""10 + 20"\r\n
std::string AddHeader(const std::string &str)//添加报头
{
std::string s = std::to_string(str.size());
s += HEADER_SEP;
s += str;
s += HEADER_SEP;
}
// "7"\r\n""10 + 20"\r\n => "10 + 20"
std::string RemoveHeader(const std::string &str)//去除报头
{
//?
}
// Request && Response都要提供序列化和反序列化功能
// 1. 自己手写
// 2. 用别人写的
class Request//客户端发送请求
{
public:
Request(){}
Request(int x, int y, char op): _x(x), _y(y), _op(op)
{}
// 序列化之后的字符串构成(用空格做为成员之间的分隔符SEP): "_x _op _y"
bool Serialize(std::string *outStr)// struct->string将结构话的数据转换成为字符串,这里的outStr是输出型参数,是序列化之后的结果
{
*outStr = "";
std::string x_string = std::to_string(_x);
std::string y_string = std::to_string(_y);
//手动序列化
*outStr = x_string + SEP + _op + SEP + y_string;
return true;
}
// string->struct
bool Deserialize(const std::string &inStr)
{
//inStr : 10 + 20 => [0]=>10, [1]=>+, [2]=>20
//string -> vector
std::vector<std::string> result;
Util::StringSplit(inStr, SEP, &result);//Util::使用类内的某个公共方法,对字符串进行切割成各个结构化的成员变量再放入vector容器里面
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];//result[1]是一个string类型,不能直接赋值给一个字符变量
}
~Request() {}
public:
// _x op _y ==> 10 / 0 ? ==> 10 * 9 ?
int _x;
int _y;
char _op;
};
class Response//响应
{
public:
Response() {}
Response(int result, int code):_result(result), _code(code)
{}
// struct->string
bool Serialize(std::string *outStr)
{
//_result _code
*outStr = "";
std::string res_string = std::to_string(_result);
std::string code_string = std::to_string(_code);
*outStr = res_string + SEP + code_string;
return true;
}
// string->struct
bool Deserialize(const std::string &inStr)
{
// 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]);
return true;
}
~Response() {}
public:
int _result;//肯定要有结果
int _code; //但是不能光有结果,还要有任务处理合法性的判断。0 success, 1,2,3,4代表不同的错误码
};
}
Calserver.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&)>;//函数返回类型是Response,参数是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);//td做为线程执行函数的参数,里面由很多信息,比如说this指针、套接字sock、客户端的ip和端口号等等,this指针里面还有ServiceIO函数用来处理来自客户端的数据
}
}
static void *ThreadRoutine(void *args)
{
pthread_detach(pthread_self());//省略了主线程的join操作
ThreadData *td = static_cast<ThreadData *>(args);
td->_tsvrp->ServiceIO(td->_sock, td->_ip, td->_port);//该服务器的线程要开始执行处理客户端的任务了
// logMessage(Debug, "thread running ...");
delete td;
return nullptr;
}
// 我们这个函数是被多线程调用的,将sock文件描述符的创建工作和sock的读取工作让不同的线程进行。,
void ServiceIO(int sock, const std::string &ip, const uint16_t &port)//负责从sock读客户端的数据以及将处理号的数据write到sock,第二三个参数是让函数知道我是在和哪个客户端进行通信
{
// 1. read/recv - 如何正确的读,继续设计协议,该协议的目的是为了保证用户正确的获得一个完整的报文string!
// 你怎么保证你读到的string 就是一个完整的string风格的请求呢??不能!
// 我们进行一直循环读取,边读取,边检测,测试
char buffer[1024];
ssize_t s = recv(sock, buffer, sizeof(buffer), 0); // ssize_t recv(int sockfd, void *buf, size_t len, int flags)第四个参数flag代表读取方式,默认为零即可。
// 2. 假设已经读到了一个完整的string
Request req;
req.Deserialize(str); // 对读到的request字符串要进行反序列化。将字符串str反序列化成为结构化的数据存储到req的x、y、op成员变量里面。
// 3. 直接提取用户的请求数据x、y、op啦,处理逻辑是客户端发个我一个request,服务器返回一个response
Response resp = _func(req); // 业务逻辑!
// 4. 给用户返回响应 - 序列化
std::string send_string;
resp.Serialize(&send_string); // 对计算完毕的response结构要进行序列化,形成可发送字符串
// 5. 发送到网络
}
~TcpServer()
{
}
private:
uint16_t _port;//任何一个服务器都需要一个端口号
Sock _listensock;//创建listen套接字
func_t _func;
};
}
CalServer.cc
#include "TcpServer.hpp"
#include <memory>
using namespace tcpserver_ns;
// ./calserver 8888
Response calculate(const Request &req)//服务器对于客户端发送的request的处理逻辑,以后只要将该函数以及Protocol.h文件一改,就当与是一个替换协议的过程。
{
// 走到这里,一定保证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;
}
CalClient.cc
//telnet是一个命令,和接口要区分,可以向服务其发起tcp登录入telnet 127.0.0.1 8888
#include "TcpClient.hpp"
int main()
{
std::cout << "cal client" << std::endl;
return 0;
}