Linux实现网络计数器
1.TcpServer.hpp文件
类TcpServer的私有成员变量有端口号,指向类Socket对象的指针,布尔值表示是否运行,以及回调函数,ioservice_t是表示参数为指向Socket对象的指针和InetAddr对象的函数,TcpServer类的构造函数接收端口号和回调函数的实现,会指向Socket类的一个函数方法,要传入端口号作为参数,
start函数执行,创建InetAddr对象,用->操作符调用指向对象的内部方法Accept函数,进行连接客服端,返回值是一个新的套接字,创建子进程,接着子进程会再创建一个子进程并退出,让创建子进程的子进程指向任务,执行回调函数传入前面返回的sock和client,执行Socket类的内部方法Close关闭套接字,执行exit退出,主线程等待子进程结束,这里不会阻塞,因为子进程退出由其子进程执行任务。
#include "Socket.hpp"
#include <iostream>
#include <memory>
#include <sys/wait.h>
#include <functional>using namespace SocketModule;
using namespace LogModule;using ioservice_t=std::function<void(std::shared_ptr<Socket>& sock,InetAddr& client)>;class TcpServer
{
public:TcpServer(uint16_t port,ioservice_t service):_port(port),_listensockptr(std::make_unique<TcpSocket>()),_isrunning(false),_service(service){_listensockptr->BuildTcpSocketMethod(_port);}void Start(){_isrunning=true;while(true){InetAddr client;auto sock=_listensockptr->Accept(&client);if(sock==nullptr){continue;}LOG(LogLevel::DEBUG)<<"accept success...";pid_t id=fork();if(id<0){LOG(LogLevel::FATAL)<<"fork error...";exit(FORK_ERR);}else if(id==0){_listensockptr->Close();if(fork()>0)exit(OK);_service(sock,client);sock->Close();exit(OK);}else{sock->Close();pid_t rid=::waitpid(id,nullptr,0);(void)rid;}}_isrunning=false;}~TcpServer() {}
private:uint16_t _port;std::unique_ptr<Socket> _listensockptr;bool _isrunning;ioservice_t _service;};
2.Socket.hpp文件
这里写法是用模板方法设计模式,类Socket由很多方法,而前面由virtual关键字的就需要实现,如果是被继承,而没有的就是继承不用实现,Socket的方法都是设置初始值就要带上virtual,因为不同模式对应不同的初始化,这样就可以用一个模板写出多种模式,不加virtual公共方法,可以实现出多种模式的初始化组合函数,把要用的函数放到里面,有因为派生类要自己实现virtual关键字的方法,就可以减少代码量,且可读性高。
#pragma once#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"namespace SocketModule
{using namespace LogModule;const static int gbacklog = 16;// 模版方法模式// 基类socket, 大部分方法,都是纯虚方法class Socket{public:virtual ~Socket() {}virtual void SocketOrDie() = 0;virtual void BindOrDie(uint16_t port) = 0;virtual void ListenOrDie(int backlog) = 0;virtual std::shared_ptr<Socket> Accept(InetAddr *client) = 0;virtual void Close() = 0;virtual int Recv(std::string *out) = 0;virtual int Send(const std::string &message) = 0;virtual int Connect(const std::string &server_ip, uint16_t port) = 0;public:void BuildTcpSocketMethod(uint16_t port, int backlog = gbacklog){SocketOrDie();BindOrDie(port);ListenOrDie(backlog);}void BuildTcpClientSocketMethod(){SocketOrDie();}// void BuildUdpSocketMethod()// {// SocketOrDie();// BindOrDie();// }};
派生类TcpSocket实现
派生类需要把要用到的函数且有virtual的函数实现,两个构造函数,一个无参一个有参,有参需要接收一个文件描述符,SocketOrDie函数是创建套接字,TCP协议就是SOCK_STREAM,BindOrDie函数是创建类InetAddr并把端口号传进去,然后bind函数进行绑定,需要套接字和ip地址以及大小,ListenOriDie是调用listen函数变成监听状态,Accept函数要传入InetAddr*类型参数,内部创建sockaddr_in类型对象,调用accept函数,传入端口号和强制转换的peer,以及长度地址,
接着调用SetAddr函数把peer作为参数传入,执行accetp函数时peer就是一个输出型参数,所以peer结构体的数据会在SetAddr函数中被提取出来,函数结束时返回TcpSocket对象,使用make_shared创建的并传入参数fd,fd是accept返回的一个文件描述符。
Recv函数是接收函数,要传入一个string*类型的对象创建一个缓冲区大小为1024的,执行recv函数从套接字中读取内容,参数要创建的套接字,存储读取到内容的容器,容器大小-1(最后一位设置0),返回值n是实际读取大小,读取成功就把buffer最后一位设置为0,然后与*out拼接在一起,
Send函数传入string&型,调用send函数往指定套接字写入信息,Connect函数传入ip和端口号,传入的参数构建InetAddr类型对象,调用connect函数与指定的服务器进行尝试连接。
派生类方法实现都是封装系统调用函数,这样是为了可操作性的提高,有时可能不止使用一个,而且可以加入日志来清晰知道那一步走了,那里没走。
const static int defaultfd=-1;class TcpSocket:public Socket{public:TcpSocket():_sockfd(defaultfd){}TcpSocket(int fd):_sockfd(fd){}~TcpSocket() {}void SocketOrDie() override{_sockfd=::socket(AF_INET,SOCK_STREAM,0);if(_sockfd<0){LOG(LogLevel::FATAL)<<"socket error";exit(SOCKET_ERR);}LOG(LogLevel::INFO)<<"socket success";}void BindOrDie(uint16_t port) override{InetAddr localaddr(port);int n=::bind(_sockfd,localaddr.NetAddrPtr(),localaddr.NetAddrLen());if(n<0){LOG(LogLevel::FATAL)<<"bind error";exit(BIND_ERR);}LOG(LogLevel::INFO)<<"bind success";}void ListenOrDie(int backlog) override{int n=::listen(_sockfd,backlog);if(n<0){LOG(LogLevel::FATAL)<<"listen error";exit(LISTEN_ERR);}LOG(LogLevel::INFO)<<"listen success";}std::shared_ptr<Socket> Accept(InetAddr* client) override{struct sockaddr_in peer;socklen_t len=sizeof(peer);int fd=::accept(_sockfd,CONV(peer),&len);if(fd<0){LOG(LogLevel::WARNING)<<"accept warning...";return nullptr;}client->SetAddr(peer);return std::make_shared<TcpSocket>(fd);}int Recv(std::string* out) override{char buffer[1024];ssize_t n=::recv(_sockfd,buffer,sizeof(buffer)-1,0);if(n>0){buffer[n]=0;*out+=buffer;}return n;}int Send(const std::string& message) override{return send(_sockfd,message.c_str(),message.size(),0);}void Close() override{if(_sockfd>=0){::close(_sockfd);}}int Connect(const std::string& server_ip,uint16_t port) override{InetAddr server(server_ip,port);return ::connect(_sockfd,server.NetAddrPtr(),server.NetAddrLen());}private:int _sockfd;};
3.InetAddr文件
这个文件里主要是对sockaddr_in的设置操作,InetAddr类构造函数是调用SetAddr函数,而SetAddr函数是传入的sockaddr_in类对象进行提取,得到addr的值,得到端口号,调用inet_ntop函数将网络地址从网络字节序的二进制形式转换为可读的点分十进制字符,ipbuffer就存储了转换后的ip,再把转换后的值赋值给_ip。接着是有参构造,要传入地址和端口号,设置协议信息以及ip地址准换并写入到_addr.sin_addr中,以及端口号转换并写入到sin.port中。还有一个构造函数是传入端口号,不用传入地址是因为地址设置为INADDR_ANY,接收然后信息,端口号转换并写入到sin_port中。Port,Ip,NetAddr,NetAddrPtr和NetAddrLen都是接口,提供端口号,地址,sockaddr_in对象,强制转换类型和地址长度,这里重载是否相等通过判断ip地址和端口号是否一样,返回布尔值表示是否相等。StringAddr是返回ip地址与端口号的结合的字符串。这里类的私有成员变量是sockaddr_in型,ip和端口号,都是与网络系统调用函数相关的参数。
#pragma once
#include "Common.hpp"
// 网络地址和主机地址之间进行转换的类class InetAddr
{
public:InetAddr(){}InetAddr(struct sockaddr_in& addr){SetAddr(addr);}InetAddr(const std::string& ip,uint16_t port):_ip(ip),_port(port){memset(&_addr,0,sizeof(_addr));_addr.sin_family=AF_INET;inet_pton(AF_INET,_ip.c_str(),&_addr.sin_addr);_addr.sin_port=htons(port);}InetAddr(uint16_t port):_port(port),_ip(){memset(&_addr,0,sizeof(_addr));_addr.sin_family=AF_INET;_addr.sin_addr.s_addr=INADDR_ANY;_addr.sin_port=htons(_port);}void SetAddr(struct sockaddr_in& addr){_addr=addr;_port=ntohs(_addr.sin_port);char ipbuffer[64];inet_ntop(AF_INET,&_addr.sin_addr,ipbuffer,sizeof(_addr));_ip=ipbuffer;}uint16_t Port() {return _port;}std::string Ip() {return _ip;}const struct sockaddr_in& NetAddr() {return _addr;}const struct sockaddr* NetAddrPtr(){return CONV(_addr);}socklen_t NetAddrLen(){return sizeof(_addr);}bool operator==(const InetAddr& addr){return addr._ip==_ip&&addr._port==_port;}std::string StringAddr(){return _ip+":"+std::to_string(_port);}~InetAddr(){}
private:struct sockaddr_in _addr;std::string _ip;uint16_t _port;};
在网络编程中,`inet_pton` 函数需要指定协议族(如 `AF_INET` 或 `AF_INET6`),原因在于不同的协议族有不同的地址格式和处理方式。以下是详细解释为什么需要传递协议族参数:
### **1. 不同协议族的地址格式不同**
#### **IPv4 地址**
- IPv4 地址是 32 位的二进制数,通常表示为点分十进制格式(如 `192.168.1.1`)。
- 在二进制形式中,IPv4 地址存储在 `struct in_addr` 类型中:
```c
struct in_addr {
uint32_t s_addr; // 32位二进制地址
};
```#### **IPv6 地址**
- IPv6 地址是 128 位的二进制数,通常表示为冒号分隔的十六进制格式(如 `2001:0db8:85a3:0000:0000:8a2e:0370:7334`)。
- 在二进制形式中,IPv6 地址存储在 `struct in6_addr` 类型中:
```c
struct in6_addr {
uint8_t s6_addr[16]; // 128位二进制地址
};
```### **2. `inet_pton` 的功能**
`inet_pton` 函数的作用是将字符串形式的 IP 地址转换为二进制形式。由于 IPv4 和 IPv6 的地址格式不同,函数需要知道目标地址的类型,以便正确解析和存储。### **3. 参数 `af` 的作用**
`af` 参数(地址族)告诉 `inet_pton` 函数如何解析输入字符串:
- **`AF_INET`**:表示 IPv4 地址。函数会将点分十进制字符串(如 `192.168.1.1`)转换为 32 位二进制形式,并存储在 `struct in_addr` 中。
- **`AF_INET6`**:表示 IPv6 地址。函数会将冒号分隔的十六进制字符串(如 `2001:0db8:85a3:0000:0000:8a2e:0370:7334`)转换为 128 位二进制形式,并存储在 `struct in6_addr` 中。### **6. 为什么需要传递协议族**
传递协议族参数的原因包括:
- **明确地址类型**:`inet_pton` 需要知道目标地址是 IPv4 还是 IPv6,以便正确解析和存储。
- **兼容性**:支持多种协议族,使函数更加通用,适用于不同的网络环境。
- **安全性**:避免因错误解析地址而导致的安全问题,例如将 IPv4 地址误解析为 IPv6 地址。**总结**
传递协议族参数(如 `AF_INET` 或 `AF_INET6`)是必要的,因为 IPv4 和 IPv6 的地址格式不同。`inet_pton` 函数需要根据协议族参数来正确解析和存储 IP 地址。这不仅提高了函数的通用性和兼容性,还增强了代码的安全性。
`_addr.sin_addr` 和 `_addr.sin_addr.s_addr` 都是与 `sockaddr_in` 结构体相关的成员,但它们在层次和用途上有所不同。让我们详细分析它们的区别和联系。
### **`sockaddr_in` 结构体**
`sockaddr_in` 是一个用于表示 IPv4 地址和端口的结构体,定义如下:
```c
struct sockaddr_in {
short sin_family; // 地址族,AF_INET
unsigned short sin_port; // 端口号,网络字节序
struct in_addr sin_addr; // IPv4 地址
char sin_zero[8]; // 填充字节,通常不使用
};
```### **`sin_addr` 成员**
`sin_addr` 是 `sockaddr_in` 结构体中的一个成员,类型为 `struct in_addr`。它用于存储 IPv4 地址的二进制形式。### **`in_addr` 结构体**
`in_addr` 是一个用于表示 IPv4 地址的结构体,定义如下:
```c
struct in_addr {
uint32_t s_addr; // 32位二进制地址
};
```
- **`s_addr`**:是一个 32 位的无符号整数,存储 IPv4 地址的二进制形式。### **`_addr.sin_addr` 和 `_addr.sin_addr.s_addr` 的区别**
#### **`_addr.sin_addr`**
- **类型**:`struct in_addr`
- **用途**:表示 IPv4 地址的二进制形式。
- **操作**:`sin_addr` 是一个结构体,通常用于整体操作 IPv4 地址。例如,你可以将一个 `in_addr` 类型的变量赋值给它:
```c
struct in_addr addr;
inet_pton(AF_INET, "192.168.1.1", &addr);
_addr.sin_addr = addr;
```#### **`_addr.sin_addr.s_addr`**
- **类型**:`uint32_t`
- **用途**:表示 IPv4 地址的二进制形式的具体值。
- **操作**:`s_addr` 是一个 32 位的无符号整数,用于直接操作 IPv4 地址的二进制值。例如,你可以直接设置或获取 IP 地址:
```c
_addr.sin_addr.s_addr = inet_addr("192.168.1.1");
```### **示例代码**
以下是一个示例,展示如何使用 `_addr.sin_addr` 和 `_addr.sin_addr.s_addr`:```cpp
#include <iostream>
#include <cstring>
#include <arpa/inet.h> // 包含 inet_pton 和 inet_ntoa 函数int main()
{
struct sockaddr_in addr;
addr.sin_family = AF_INET; // 地址族// 使用 sin_addr.s_addr 设置 IP 地址
addr.sin_addr.s_addr = inet_addr("192.168.1.1");// 使用 sin_addr 获取 IP 地址
char ip_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &addr.sin_addr, ip_str, INET_ADDRSTRLEN);
std::cout << "IP Address: " << ip_str << std::endl;// 使用 sin_addr 设置 IP 地址
struct in_addr addr2;
inet_pton(AF_INET, "192.168.1.2", &addr2);
addr.sin_addr = addr2;// 使用 sin_addr 获取 IP 地址
inet_ntop(AF_INET, &addr.sin_addr, ip_str, INET_ADDRSTRLEN);
std::cout << "IP Address: " << ip_str << std::endl;return 0;
}
```### **输出示例**
运行上述代码,输出可能如下:
```
IP Address: 192.168.1.1
IP Address: 192.168.1.2
```### **总结**
- **`_addr.sin_addr`**:是一个 `struct in_addr` 类型的成员,用于整体操作 IPv4 地址。
- **`_addr.sin_addr.s_addr`**:是 `sin_addr` 结构体中的一个 32 位无符号整数,用于直接操作 IPv4 地址的二进制值。在实际使用中,你可以根据需要选择使用 `_addr.sin_addr` 或 `_addr.sin_addr.s_addr`。例如:
- 如果你需要将一个 `in_addr` 类型的变量赋值给 `_addr.sin_addr`,直接使用 `_addr.sin_addr`。
- 如果你需要直接操作 IPv4 地址的二进制值,使用 `_addr.sin_addr.s_addr`。
4.Protocol.hpp文件
类Request主要是接收请求,要计算值,所以构造函数部分就需要接收两个值以及运算符号是什么,因为TCP传输是面向字节流的,把数视为一个连续的字节续流,不会对数据进行分隔或者添加边界标记,所以需要定制协议来传输,先统一变成字符串形式,那么就需要实现把类型变成字符串且拼接在一起,Json是第三方库的,内有这种实现,把_x,_y和_oper变成字符,把拼接在一起的结果存储到s中并返回s,完成序列化转换。反序列化构建Json::Value 类型对象,Json::Value root
:用于存储解析后的 JSON 数据。Json::Reader reader
:创建一个解析器对象。reader.parse(in, root)
:将输入字符串 in
解析为 JSON 数据,并存储到 root
中。解析完后就可以提取出反序列化后的数据,.asInt()是把root中提取键为"x"的值,并将其转换为整数类型,其余都是提供接口出来获取成员值。
class Request
{
public:Request(){}Request(int x,int y,char oper):_x(x),_y(y),_oper(oper){}std::string Serialize(){Json::Value root;root["x"]=_x;root["y"]=_y;root["oper"]=_oper;Json::FastWriter writer;std::string s=writer.write(root);return s;}bool Deserialize(std::string& in){Json::Value root;Json::Reader reader;bool ok=reader.parse(in,root);if(ok){_x=root["x"].asInt();_y=root["y"].asInt();_oper=root["oper"].asInt();}return ok;}~Request() {}int X() {return _x;}int Y() {return _y;}char Oper() {return _oper;}
private:int _x;int _y;char _oper;
};
Response是结果值处理,有两个成员result和code,code是表示result是否正确的,0表示正确1表示错误,因为可能遇到除零操作,就把值设为0且code为1。这个类也需要实现序列化和反序列化操作,把结果发送过去要序列化,接收结果要反序列化,SetResult和SetCode是直接设置result和code值。
class Response
{
public:Response() {}Response(int result,int code):_result(result),_code(code){}std::string Serialize(){Json::Value root;root["result"]=_result;root["code"]=_code;Json::FastWriter writer;return writer.write(root);}bool Deserialize(std::string& in){Json::Value root;Json::Reader reader;bool ok=reader.parse(in,root);if(ok){_result=root["result"].asInt();_code=root["code"].asInt();}return ok;}~Response() {}void SetResult(int res){_result=res;}void SetCode(int code){_code=code;}void ShowResult(){std::cout << "计算结果是: " << _result << "[" << _code << "]" << std::endl;}private:int _result;int _code;
};
这个类是实现协议,因为TCP发送数据是面向字节流的,需要定制协议来保证数据的接收发送准确性,Encode函数把数据的长度变成字符串,接着把长度和间隔号和内容拼接一起,这样就可以完成接收了,先读取长度值,知道数据具体的长度,间隔号也知道长度就可以把数据完整读取,因为发送也是按照这个格式发送。Decode就是解析传入的字符串数据,先找到sep分隔号,然后调用substr函数把0到sep之间内容读取,在用stoi函数把字符串变成整数就可以得到传入数据长度,然后在计算两个分隔号的长度和数据长度和表示数据长度的字符大小总和,如果buffer的大小小于这个总和那么就说明数据传输错误,大小不一致,一定要按照格式发送的才接收,格式正确就用substr把数据给提取出来放到package中,package是输出型参数,调用buffer把0到总和长度之间删除掉,因为这一段已经被读取出来了。GetRequest函数要传入Socket类型指针和InetAddr类型对象,里面则是while循环,调用socket里的recv函数读取套接字的内容,读取成功就使用Decode解析得到的内容,解析成功才往下走,创建Request类型对象,在调用这个类的Deserialize函数把json_package进行反序列化,req对象的xy与oper都确定了,创建Response类型对象resp,resp的值是回调函数的结果(得到计算值),创建json_str接收resp调用Serialize函数的返回值,创建send_str接收返回值包装后的值(头为数据长度,数据被俩个分隔号包裹),把包装好的值用Send函数发送,else if和else是对应没有读取成功做出的反应。
GetResponse函数接收Socket类型的指针和string类型对象与Response的指针,里面一直循环接收,接收套接字的内容并存储在resp_buff内,接收到就解析,得到数据的字符串然后再用Deserialize函数去反序列化得到数据内容,n=0就表示退出。
BuildRequestString函数是把xy与oper的值进行序列化,然后再把以协议的形式封装返回,就是把请求的数据做处理,按照协议的方式处理。
const std::string sep="\r\n";using func_t =std::function<Response (Request& req)>;class Protocol
{
public:Protocol() {}Protocol(func_t func):_func(func){}std::string Encode(const std::string& jsonstr){std::string len=std::to_string(jsonstr.size());return len+sep+jsonstr+sep;}bool Decode(std::string& buffer,std::string* package){ssize_t pos=buffer.find(sep);if(pos==std::string::npos){return false;}std::string package_len_str=buffer.substr(0,pos);int package_len_int=std::stoi(package_len_str);int target_len=package_len_str.size()+package_len_int+2*sep.size();if(buffer.size()<target_len)return false;*package=buffer.substr(pos+sep.size(),package_len_int);buffer.erase(0,target_len);return true;}void GetRequest(std::shared_ptr<Socket>& sock,InetAddr& client){std::string buffer_queue;while(true){int n=sock->Recv(&buffer_queue);if(n>0){std::string json_package;while(Decode(buffer_queue,&json_package)){LOG(LogLevel::DEBUG)<<client.StringAddr()<<"请求:"<<json_package;Request req;bool ok=req.Deserialize(json_package);if(!ok){continue;}Response resp=_func(req);std::string json_str=resp.Serialize();std::string send_str=Encode(json_str);sock->Send(send_str);}}else if(n<=0){LOG(LogLevel::INFO)<<"client:"<<client.StringAddr()<<"QUIT!";break;}else{LOG(LogLevel::WARNING)<<"client:"<<client.StringAddr()<<"recv error";break;}}}bool GetResponse(std::shared_ptr<Socket>& client,std::string& resp_buff,Response* resp){while(true){int n=client->Recv(&resp_buff);if(n>0){std::string json_package;while(Decode(resp_buff,&json_package)){resp->Deserialize(json_package);}return true;}else if(n==0){std::cout<<"server quit"<<std::endl;return false;}else{std::cout<<"recv error"<<std::endl;return false;}}}std::string BuildRequestString(int x,int y,char oper){Request req(x,y,oper);std::string json_req=req.Serialize();return Encode(json_req);}~Protocol(){}
private:func_t _func;
};
5.NetCal.hpp文件
这里是应用层,是具体实现计数器部分,Execute函数是接收以Request对象,根据Oper接口提供的运算符来决定是哪一个处理方式,每一个处理方式都会调用SetResult函数,把结果写入,然后返回resp对象。
#pragma once#include "Protocol.hpp"
#include <iostream>class Cal
{
public:Response Execute(Request& req){Response resp(0,0);//code:0表示成功switch(req.Oper()){case '+':resp.SetResult(req.X()+req.Y());break;case '-':resp.SetResult(req.X()-req.Y());break;case '*':resp.SetResult(req.X()*req.Y());break;case '/':{if(req.Y()==0)resp.SetCode(1);elseresp.SetResult(req.X()/req.Y());}break;case '%':{if(req.Y()==0)resp.SetCode(2);elseresp.SetResult(req.X()%req.Y());}break;default:resp.SetCode(3);break;}return resp; }};
6.main.cc文件
这里是把各个模块都启动的地方,用make_unique<Cal>创建一个指向Cal对象的指针,接着同样用这个方法创建一个指向Protocol对象的指针,参数是一个lambda对象,捕捉了指向Cal的指针cal,这个lambda参数是Request类型的,返回值为Response类型的req,执行部分是调用cal的Execute函数传入req,接着还是用make的方法创建一个指针指向TcpServer类型对象,参数是端口号和lambda表达式,捕获protocol对象,参数为Socket指针和InetAddr对象,执行部分是调用Procotol类的GetRequest函数,参数为sock和client。
这样写的层次感就很明显,先是应用层的初始话,接着是协议层的初始化,最后是网络层的初始化,一层一层往下走。Protocol的回调函数就是lambad,会执行Execute函数,可以得到计算结果,TcpServer类的回调函数就是执行GetRequest函数,而GetRequest又会执行创建protocl对象的传入的lambda表达式的执行部分,回调中执行回调。
#include "NetCal.hpp"
#include "Protocol.hpp"
#include "TcpServer.hpp"
#include <memory>void Usage(std::string proc)
{std::cerr<<"Usage"<<proc<<"port"<<std::endl;}int main(int argc,char* argv[])
{if(argc!=2){Usage(argv[0]);exit(USAGE_ERR);}std::unique_ptr<Cal> cal=std::make_unique<Cal>();std::unique_ptr<Protocol> protocol=std::make_unique<Protocol>([&cal](Request& req)->Response{return cal->Execute(req);});std::unique_ptr<TcpServer> tsvr=std::make_unique<TcpServer>(std::stoi(argv[1]),[&protocol](std::shared_ptr<Socket>& sock,InetAddr& client){protocol->GetRequest(sock,client);});tsvr->Start();return 0;
}
7.TcpClient.hpp文件
主函数从命令行参数获取端口号和ip地址,make_unique创建一个TcpSocket对象,调用BuildTcpClientScoketMethod函数,把套接字创建完成,接着创建一个Protocol对象,主循环一直从输入端读取内容,把读到的内容作为参数调用BuildRequestString函数,会把读取的数据进行反序列化和按照协议形式封装,调用ShowResult函数显示结果。
#include "Socket.hpp"
#include "Common.hpp"
#include <iostream>
#include <string>
#include "Protocol.hpp"
#include <memory>using namespace SocketModule;void GetDataFromStdin(int* x,int* y,char* oper)
{std::cout<<"Please Enter x:";std::cin>>*x;std::cout<<"Please Enter y:";std::cin>>*y;std::cout<<"Please Enter oper:";std::cin>>*oper;}void Usage(std::string proc)
{std::cerr<<"Usage:"<<proc<<"server_ip server_port"<<std::endl;}int main(int argc, char* argv[])
{if(argc!=3){Usage(argv[0]);exit(USAGE_ERR);}std::string server_ip=argv[1];uint16_t server_port=std::stoi(argv[2]);std::shared_ptr<Socket> client=std::make_unique<TcpSocket>();//这里用unique_ptr指针会报错,用shared_ptr才不会报错client->BuildTcpClientSocketMethod();if(client->Connect(server_ip,server_port)!=0){std::cerr<<"Connect error"<<std::endl;exit(CONNECT_ERR);}std::unique_ptr<Protocol> protocol=std::make_unique<Protocol>();std::string resp_buffer;while(true){int x,y;char oper;GetDataFromStdin(&x,&y,&oper);std::string req_str=protocol->BuildRequestString(x,y,oper);client->Send(req_str);Response resp;bool res=protocol->GetResponse(client,resp_buffer,&resp);if(res==false)break;resp.ShowResult();}client->Close();return 0;
}