目录
编写序列化和反序列化的代码,客户端和服务器通信实现网络计算器
1.准备工作:日志信息,makefile
2.封装Socket
tip:
1.创建套接字Socket()
2.bind()绑定
3.设置套接字为监听状态
5.客户端connect()
4.设置accept()
5.Sock()完整代码
3.封装TcpServer.hpp
1.成员变量
2.ThreadDate
3.初始化TcpServer
4.为服务端绑定服务
5.执行方法
6.启动服务器
7.TcpServer服务器完整代码
4.用封装好的TcpServer编写计算服务器CalServer.hpp之前先编写服务器和客户端的通讯协议
简单的protocol.hpp
5.编写计算服务端
6.编写计算客户端
设计思路:客户端向服务器发送信息,服务端处理客户端的信息,并将结果返回给客户端
需要Sock创建套接字,让双方实现通信,由tcpServer发起通信;需要定制协议(Protocol.hpp)将结构化的数据转为字符串,发送到网络,字符串的数据转为结构化的数据由客户端转给上层,服务器(CalServer.cc)编写和客户端(CalClient.cc)编写
#pragma once
#include
#include
#include
#include
#include
// 日志是有日志级别的
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4
const char *gLevelMap[] = {
"DEBUG",
"NORMAL",
"WARNING",
"ERROR",
"FATAL"
};
#define LOGFILE "./calculator.log"
// 完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名)
void logMessage(int level, const char *format, ...)
{
#ifndef DEBUG_SHOW
if(level== DEBUG) return;
#endif
char stdBuffer[1024]; //标准部分
time_t timestamp = time(nullptr);
snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%ld] ", gLevelMap[level], timestamp);
char logBuffer[1024]; //自定义部分
va_list args;
va_start(args, format);
vsnprintf(logBuffer, sizeof logBuffer, format, args);
va_end(args);
FILE *fp = fopen(LOGFILE, "a");
fprintf(fp, "%s%s\n", stdBuffer, logBuffer);
fclose(fp);
}
.PHONY:all
all:client CalServer
client:CalClient.cc
g++ -o $@ $^ -std=c++11
CalServer:CalServer.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f client CalServer
一般经验
const std::string &: 输入型参数
std::string *: 输出型参数
std::string &: 输入输出型参数
socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符;
应用程序可以像读写文件一样用read/write在网络上收发数据;
如果socket()调用出错则返回-1;
对于IPv4, family参数指定为AF_INET;
对于TCP协议,type参数指定为SOCK_STREAM, 表示面向流的传输协议 protocol参数的介绍从略,指定为0即可。
int Socket()
{
int listensock = socket(AF_INET, SOCK_STREAM, 0);//初始化套接字,后续需要将该套接字处于监听状态
if (listensock < 0)
{
logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
exit(2);
}//差错处理
logMessage(NORMAL, "create socket success, listensock: %d", listensock);
return listensock;
}
void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0")
{
struct sockaddr_in local;
memset(&local, 0, sizeof local);
local.sin_family = AF_INET;
local.sin_port = htons(port);
inet_pton(AF_INET, ip.c_str(), &local.sin_addr);
if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
exit(3);
}
}
服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接;
服务器需要调用bind绑定一个固定的网络地址和端口号; bind()成功返回0,失败返回-1。
bind()的作用是将参数sockfd和local绑定在一起, 使sockfd这个用于网络通讯的文件描述local所描述的地址和端口号;
struct sockaddr *是一个通用指针类型,local参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度
const static int gbacklog = 20;
void Listen(int sock)
{
if (listen(sock, gbacklog) < 0)
{
logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
exit(4);
}
logMessage(NORMAL, "init server success");
}
listen()声明sockfd处于监听状态, 并且最多允许有backlog个客户端处于连接等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是5)
成功返回0,失败返回-1
bool Connect(int sock, const std::string &server_ip, const uint16_t &server_port)
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(server_port);
server.sin_addr.s_addr = inet_addr(server_ip.c_str());
if(connect(sock, (struct sockaddr*)&server, sizeof(server)) == 0) return true;
else return false;
}
由客户端发起连接,connect通过传进来的服务器的ip和端口号,帮助客户端通过sock建立和服务器的连接
客户端需要调用connect()连接服务器;
connect和bind的参数形式一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址;
connect()成功返回0,出错返回-1
三次握手完成后, 服务器调用accept()接受连接;
如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
addr是一个传出参数,accept()返回时传出客户端的地址和端口号;
如果给addr 参数传NULL,表示不关心客户端的地址;
addrlen参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区addr的长度 以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区);
int Accept(int listensock, std::string *ip, uint16_t *port)
{
struct sockaddr_in src;
socklen_t len = sizeof(src);
int servicesock = accept(listensock, (struct sockaddr *)&src, &len);
if (servicesock < 0)
{
logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
return -1;
}
if(port) *port = ntohs(src.sin_port);
if(ip) *ip = inet_ntoa(src.sin_addr);
return servicesock;
}
本函数创建了servicesock,专门为用户提供服务,accept获取了来自客户端的信息,客户端的ip和端口号
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "Log.hpp"
class Sock
{
private:
const static int gbacklog = 20;
public:
Sock() {}
int Socket()
{
int listensock = socket(AF_INET, SOCK_STREAM, 0);
if (listensock < 0)
{
logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
exit(2);
}
logMessage(NORMAL, "create socket success, listensock: %d", listensock);
return listensock;
}
void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0")
{
struct sockaddr_in local;
memset(&local, 0, sizeof local);
local.sin_family = AF_INET;
local.sin_port = htons(port);
inet_pton(AF_INET, ip.c_str(), &local.sin_addr);
if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
exit(3);
}
}
void Listen(int sock)
{
if (listen(sock, gbacklog) < 0)
{
logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
exit(4);
}
logMessage(NORMAL, "init server success");
}
// 一般经验
// const std::string &: 输入型参数
// std::string *: 输出型参数
// std::string &: 输入输出型参数
int Accept(int listensock, std::string *ip, uint16_t *port)
{
struct sockaddr_in src;
socklen_t len = sizeof(src);
int servicesock = accept(listensock, (struct sockaddr *)&src, &len);
if (servicesock < 0)
{
logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
return -1;
}
if(port) *port = ntohs(src.sin_port);
if(ip) *ip = inet_ntoa(src.sin_addr);
return servicesock;
}
bool Connect(int sock, const std::string &server_ip, const uint16_t &server_port)
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(server_port);
server.sin_addr.s_addr = inet_addr(server_ip.c_str());
if(connect(sock, (struct sockaddr*)&server, sizeof(server)) == 0) return true;
else return false;
}
~Sock() {}
};
int listensock_;//监听套接字,本质为文件描述符
Sock sock_;//用于和端口号ip绑定
std::vector func_;//传入函数方法,用于让服务端执行
// std::unordered_map func_;
class ThreadData
{
public:
ThreadData(int sock, TcpServer *server):sock_(sock), server_(server)
{}
~ThreadData() {}
public:
int sock_;
TcpServer *server_;
};
TcpServer(const uint16_t &port, const std::string &ip = "0.0.0.0")
{
listensock_ = sock_.Socket();
sock_.Bind(listensock_, port, ip);
sock_.Listen(listensock_);
}
void BindService(func_t func)
{
func_.push_back(func);
}
因为数据是从sock里面来的,依此接收用户的数据执行函数
void Excute(int sock)
{
for(auto &f : func_)
{
f(sock);
}
}
static void *ThreadRoutine(void *args)
{
pthread_detach(pthread_self());
ThreadData *td = static_cast(args);
td->server_->Excute(td->sock_);
close(td->sock_);
// delete td;
return nullptr;
}
void Start()
{
for (;;)
{
std::string clientip;
uint16_t clientport;
int sock = sock_.Accept(listensock_, &clientip, &clientport);
if (sock == -1)
continue;//失败了就继续accept
logMessage(NORMAL, "create new link success, sock: %d", sock);
pthread_t tid;//创建一个线程来执行任务
ThreadData *td = new ThreadData(sock, this);
pthread_create(&tid, nullptr, ThreadRoutine, td);
}
}
#pragma once
#include "Sock.hpp"
#include
#include
#include
namespace ns_tcpserver
{
using func_t = std::function;
class TcpServer;
class ThreadData
{
public:
ThreadData(int sock, TcpServer *server):sock_(sock), server_(server)
{}
~ThreadData() {}
public:
int sock_;
TcpServer *server_;
};
class TcpServer
{
private:
static void *ThreadRoutine(void *args)
{
pthread_detach(pthread_self());
ThreadData *td = static_cast(args);
td->server_->Excute(td->sock_);
close(td->sock_);
// delete td;
return nullptr;
}
public:
TcpServer(const uint16_t &port, const std::string &ip = "0.0.0.0")
{
listensock_ = sock_.Socket();
sock_.Bind(listensock_, port, ip);
sock_.Listen(listensock_);
}
void BindService(func_t func)
{
func_.push_back(func);
}
void Excute(int sock)
{
for(auto &f : func_)
{
f(sock);
}
}
void Start()
{
for (;;)
{
std::string clientip;
uint16_t clientport;
int sock = sock_.Accept(listensock_, &clientip, &clientport);
if (sock == -1)
continue;
logMessage(NORMAL, "create new link success, sock: %d", sock);
pthread_t tid;
ThreadData *td = new ThreadData(sock, this);
pthread_create(&tid, nullptr, ThreadRoutine, td);
}
}
~TcpServer()
{
if (listensock_ >= 0)
close(listensock_);
}
private:
int listensock_;
Sock sock_;
std::vector func_;
// std::unordered_map func_;
};
}
namespace ns_protocol
{
#define MYSELF 0//自己定制的协议
#define SPACE " "
#define SPACE_LEN strlen(SPACE)
class Request
{
public:
// 1. 自主实现 "x_ op_ y_"
// 2. 使用现成的方案
std::string Serialize()//结构体转为字符串
{
#ifdef MYSELF
std::string str;
str = std::to_string(x_);
str += SPACE;
str += op_; // TODO
str += SPACE;
str += std::to_string(y_);
return str;
#else
#endif
}
// "x_ op_ y_"
// "1234 + 5678"
bool Deserialized(const std::string &str)
//字符串转为结构体,在内部根据传进来的字符串设置成员变量
{
#ifdef MYSELF
std::size_t left = str.find(SPACE);
if (left == std::string::npos)
return false;
std::size_t right = str.rfind(SPACE);
if (right == std::string::npos)
return false;
x_ = atoi(str.substr(0, left).c_str());
y_ = atoi(str.substr(right + SPACE_LEN).c_str());
if (left + SPACE_LEN > str.size())
return false;
else
op_ = str[left + SPACE_LEN];
return true;
#else
#endif
}
public:
Request()
{
}
Request(int x, int y, char op) : x_(x), y_(y), op_(op)
{
}
~Request() {}
public:
// 约定
// x_ op y_ ? y_ op x_?
int x_; // 是什么?
int y_; // 是什么?
char op_; // '+' '-' '*' '/' '%'
};
class Response
{
public:
//服务器传过来"code_ result_"
std::string Serialize()
{
#ifdef MYSELF
std::string s;
s = std::to_string(code_);
s += SPACE;
s += std::to_string(result_);
return s;
#else
#endif
}
// "111 100"
bool Deserialized(const std::string &s)
{
#ifdef MYSELF
std::size_t pos = s.find(SPACE);
if (pos == std::string::npos)
return false;
code_ = atoi(s.substr(0, pos).c_str());
result_ = atoi(s.substr(pos + SPACE_LEN).c_str());
return true;
#else
#endif
}
public:
Response()
{
}
Response(int result, int code)
: result_(result), code_(code)
{
}
~Response() {}
public:
// 约定!
// result_? code_? code_ 0? 1?2?3?
int result_; // 计算结果
int code_; // 计算结果的状态码
};
std::string Recv(int sock)
{
char buffer[1024];
ssize_t s = recv(sock, buffer, sizeof(buffer), 0);
return buffer;
}
void Send(int sock, const std::string str)
{
// std::cout << "sent in" << std::endl;
int n = send(sock, str.c_str(), str.size(), 0);
if (n < 0)
std::cout << "send error" << std::endl;
}
}
#include "TcpServer.hpp"
#include "Protocol.hpp"
#include
#include
using namespace ns_tcpserver;
using namespace ns_protocol;
static void Usage(const std::string &process)
{
std::cout << "\nUsage: " << process << " port\n"
<< std::endl;
}
static 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 (0 == req.y_)
resp.code_ = 1;
else
resp.result_ = req.x_ / req.y_;
break;
case '%':
if (0 == req.y_)
resp.code_ = 2;
else
resp.result_ = req.x_ % req.y_;
break;
default:
resp.code_ = 3;
break;
}
return resp;
}
void calculator(int sock)
{
while (true)
{
std::string str=Recv(sock);
Request req;
req.Deserialized(str);
Response resp=calculatorhelper(req);
std::string respString =resp.Serialize();
Send(sock,respString);
}
}
// ./CalServer port
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(1);
}
std::unique_ptr server(new TcpServer(atoi(argv[1])));
server->BindService(calculator);
server->Start();
return 0;
}
#include
#include "Sock.hpp"
#include "Protocol.hpp"
using namespace ns_protocol;
static void Usage(const std::string &process)
{
std::cout << "\nUsage: " << process << " serverIp serverPort\n"
<< std::endl;
}
// ./client server_ip server_port
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(1);
}
std::string server_ip = argv[1];
uint16_t server_port = atoi(argv[2]);
Sock sock;
int sockfd = sock.Socket();
if (!sock.Connect(sockfd, server_ip, server_port))
{
std::cerr << "Connect error" << std::endl;
exit(2);
}
// 5. 正常读取
while (true)
{
Request req(10,20,'+');
std::string s=req.Serialize();
Send(sockfd,s);
std::string r=Recv(sockfd);
Response resp;
resp.Deserialized(r);
std::cout<
客户端退出,服务端也退出,why?!需要改进