目录
1、应用层
2、再谈“协议”
3、网络版计算器
Socket.hpp
TcpServer.hpp
ServerCal.hpp
ServerCal.cc
Protocol.hpp
ClientCal.cc
Log.hpp
Makefile
1、应用层
我们程序员写的一个个解决我们实际问题,满足我们日常需求的网络程序,都是在应用层。
2、再谈“协议”
协议是一种
"
约定
"。 socket api
的接口
,
在读写数据时
,
都是按
"
字符串
"
的方式来发送接收的
.
如果我们要传输一些 "结构化的数据
"
怎么办呢
?
这个时候就要进行序列化和反序列化,什么意思呢?如图:
左边的客户端在给右边的服务器想发送这样一个msg的结构化的数据,那么服务端就必须也要有一个完全相同的结构可以接受数据,然后在客户端向网络发送数据时会先转换成字符串,然后服务器接收时再将字符串转换成结构化的数据,所以只要确保一端发送的数据,另一端能够识别,这种约定,就是应用层的协议。
3、网络版计算器
Socket.hpp
这是一个封装的套接字接口,可以创建套接字,进行绑定、监听、接收、连接以及关闭套接字。
//Socket.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,
};
//TODO
const int backlog = 10;
class Sock
{
public:
Sock()
{}
~Sock()
{}
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, "bind error, %s: %d", strerror(errno), errno);
exit(ListenErr);
}
}
int Accept(std::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 std::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 << std::endl;
return false;
}
return true;
}
void Close()
{
close(sockfd_);
}
int Fd()
{
return sockfd_;
}
private:
int sockfd_;
};
TcpServer.hpp
下面是一个创建tcp服务器的接口,实现了启动服务器的功能,在里面调用其他接口处理收到的数据然后写回给客户端。
//TcpServer.hpp
#pragma once
#include <functional>
#include <string>
#include "Log.hpp"
#include "Socket.hpp"
#include <signal.h>
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);
signal(SIGPIPE, SIG_IGN);
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[128];
ssize_t n = read(sockfd, buffer, sizeof(buffer));
if (n > 0)
{
buffer[n] = 0;
inbuffer_stream += buffer;
lg(Debug, "debug: %s", inbuffer_stream.c_str());
std::string info = callback_(inbuffer_stream);
if(info.empty()) continue;
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_;
};
ServerCal.hpp
这是计算器服务器的接口,里面定义了计算器的基本功能加减乘除取模,调用了Protocol接口中的序列化反序列化来将数据标准化。
//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;
}
// "len"\n"10 + 20"\n
std::string Calculator(std::string &package)
{
std::string content;
bool r = Decode(package,&content); // "len"\n"10 + 20"\n
if(!r) return "";
// "10 + 20"
Request req;
r = req.Deserialize(content);// "10 + 20" -> x=10 op=+ y=20
if(!r) return "";
content = ""; //
Response resp = CalculatorHelper(req);
resp.Serialize(&content);
content = Encode(content);
return content;
}
~ServerCal()
{}
};
ServerCal.cc
这里是服务器启动的程序,通过绑定端口号启动服务器。
//ServerCal.cc
#include "TcpServer.hpp"
#include "ServerCal.hpp"
static void Usage(const std::string &proc)
{
std::cout << "\nUsage: " << proc << "port\n" << std::endl;
}
//./servercal 8080
int main(int argc, char *argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(0);
}
uint16_t port = std::stoi(argv[1]);
ServerCal cal;
TcpServer *tsvp = new TcpServer(8080, std::bind(&ServerCal::Calculator, &cal, std::placeholders::_1));
tsvp->InitServer();
tsvp->Start();
return 0;
}
Protocol.hpp
这是对数据进行序列化反序列化的接口。
//Protocol.hpp
#pragma once
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
// #define MySelf 1
const std::string blank_space_sep = " ";
const std::string protocol_sep = "\n";
std::string Encode(std::string &content)
{
// 封装报头
std::string package = std::to_string(content.size());
package += protocol_sep;
package += content;
package += protocol_sep;
return package;
}
//"len"\n"x op y"
bool Decode(std::string &package, std::string *content)
{
std::size_t pos = package.find(protocol_sep);
if (pos == std::string::npos) return false;
std::string len_str = package.substr(0, pos);
std::size_t len = std::stoi(len_str);
// package = len_str + content + 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()
{}
bool Serialize(std::string *out)
{
#ifdef MySelf
// 构建报文的有效载荷
// struct => string,x op y => "len"\n"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"
{
#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()
{}
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 cpde"
{
#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具体是几,表明对应的错误原因
};
ClientCal.cc
这是客户端启动的程序,通过服务器ip和端口连接服务器。
//ClientCal.cc
#include <iostream>
#include <string>
#include <ctime>
#include <assert.h>
#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);
std::cout << "这是最新的发出去的请求: \n" << 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::string content;
bool r = Decode(inbuffer_stream, &content); // "result code"
assert(r);
Response resp;
r = resp.Deserialize(content);
assert(r);
resp.DebugPrint();
}
sleep(1);
}
sockfd.Close();
return 0;
}
Log.hpp
这是日志接口。
//Log.hpp
#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>
#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;
}
std::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 logmessage(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\n", leftbuffer, rightbuffer);
// // printf("%s", logtxt); // 暂时打印
// printLog(level, logtxt);
// }
void printLog(int level, const std::string &logtxt)
{
switch (printMethod)
{
case Screen:
std::cout << logtxt << std::endl;
break;
case Onefile:
printOneFile(LogFile, logtxt);
break;
case Classfile:
printClassFile(level, logtxt);
break;
default:
break;
}
}
void printOneFile(const std::string &logname, const std::string &logtxt)
{
std::string _logname = path + logname;
int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
if (fd < 0)
return;
write(fd, logtxt.c_str(), logtxt.size());
close(fd);
}
void printClassFile(int level, const std::string &logtxt)
{
std::string filename = LogFile;
filename += ".";
filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"
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;
std::string path;
};
Log lg;
// int sum(int n, ...)
// {
// va_list s; // char*
// va_start(s, n);
// int sum = 0;
// while(n)
// {
// sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123);
// n--;
// }
// va_end(s); //s = NULL
// return sum;
// }
Makefile
//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 -g $(Lib) #$(Flag)
.PHONY:clean
clean:
rm -f servercal clientcal