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;
}
相关文章:
Linux实现网络计数器
1.TcpServer.hpp文件 类TcpServer的私有成员变量有端口号,指向类Socket对象的指针,布尔值表示是否运行,以及回调函数,ioservice_t是表示参数为指向Socket对象的指针和InetAddr对象的函数,TcpServer类的构造函数接收端…...
数据分析:用Excel做周报
目录 1.初始模板 编辑 2.填充数据 2.1 日期以及表头 2.2 数据验证 2.3 计算数据填充 2.3.1 灵活计算 2.3.2 单独计算 2.3.3 总计 2.4 数据格式 2.5 周累计 2.6 周环比 2.7 业务进度 3 美化 1.初始模板 2.填充数据 2.1 日期以及表头 结果指标有以下这些&#…...
初阶数据结构--排序算法(全解析!!!)
排序 1. 排序的概念 排序:所谓排序,就是使一串记录,按照其中的某个或某些些关键字的大小,递增或递减的排列起来的操作。 2. 常见的排序算法 3. 实现常见的排序算法 以下排序算法均是以排升序为示例。 3.1 插入排序 基本思想:…...
SpringCloud 微服务复习笔记
文章目录 微服务概述单体架构微服务架构 微服务拆分微服务拆分原则拆分实战第一步:创建一个新工程第二步:创建对应模块第三步:引入依赖第四步:被配置文件拷贝过来第五步:把对应的东西全部拷过来第六步:创建…...
加油站小程序实战教程14会员充值页面搭建
目录 1 原型2 搭建充值金额选择功能3 搭建金额输入4 搭建支付方式5 充值按钮最终的效果 上一篇我们介绍了充值规则的后台功能,有了基础的规则,在会员充值页面就可以显示具体的充值规则。本篇我们介绍一下会员充值的开发过程。 1 原型 充值页面我们是分为…...
内卷的中国智驾,合资品牌如何弯道超车?
作者 |德新 编辑 |王博 上海车展前夕,一汽丰田举办重磅车型bZ5的技术发布会,脱口秀演员庞博透露了这款车型的一大重要特性,其搭载来自Momenta的智能辅助驾驶系统行驶里程已经超过20亿公里。 携手中国科技公司提高车型智能化的属性ÿ…...
【go】go run-gcflags常用参数归纳,go逃逸分析执行语句,go返回局部变量指针是安全的
go官方参考文档: https://pkg.go.dev/cmd/compile 基本语法 go run 命令用来编译并运行Go程序,-gcflags 后面可以跟一系列的编译选项,多个选项之间用空格分隔。基本语法如下: go run -gcflags "<flags>" main.…...
数据库11(触发器)
触发器有三种类型,包括删除触发器,更新触发器,添加触发器 触发器的作用是:当某个表发生某个操作时,自动触发触发器,进行触发器规定的操作 触发器语句 create trigger tname --创建触发器 on aa --创建在表…...
十大物联网平台-物联网十大品牌
物联网十大品牌及平台解析 物联网(IoT)作为当下极具影响力的技术,正逐步渗透至社会各领域,为人们生活与社会发展带来诸多便利与变革。如今,众多企业投身于物联网行业,致力于推动其发展。以下是对物联网相关…...
心智模式VS系统思考
很多人常说,“改变自己,从改变思维开始。”但事实上,打破一个人的心智模式,远比想象中要困难得多。我们的思维方式、行为习惯,甚至是对世界的认知,往往是多年积累下来的产物。那些曾经的经历、长期的学习与…...
QT 打包安装程序【windeployqt.exe】报错c000007d原因:Conda巨坑
一、命令行执行命令 E:\Project\GNCGC\Bin\Win32\Vc22\RS422地检>E:\SoftWare\Qt\5.14.2\msvc2017\bin\windeployqt.exe CGC170.exe二、安装了Conda的朋友,巨坑 无语,E:\SoftWare\Qt\5.14.2\msvc2017\bin\windeployqt.exe 优先把Conda环境关联的Qt动…...
Vue3祖先后代组件数据双向同步实现方法
在 Vue3 中实现祖先后代组件的双向数据同步,可以通过组合式 API 的 provide/inject 配合响应式数据实现。以下是两种常见实现方案: 方案一:共享响应式对象 方法 html <!-- 祖先组件 --> <script setup> import { ref, provide…...
OpenBayes 一周速览|EasyControl 高效控制 DiT 架构,助力吉卜力风图像一键生成;TripoSG 单图秒变高保真 3D 模型
公共资源速递 10 个教程: * 一键部署 R1-OneVision * UNO:通用定制化图像生成 * TripoSG:单图秒变高保真 3D * 使用 VASP 进行机器学习力场训练 * InfiniteYou 高保真图像生成 Demo * VenusFactory 蛋白质工程设计平台 * Qwen2.5-0mni…...
服务器-conda下载速度慢-国内源
文章目录 前言一、解决问题:使用国内conda镜像下载(差)二、解决问题:使用pip下载(优)总结 前言 conda频道中有无效频道导致下载失败 一、解决问题:使用国内conda镜像下载(差) 步骤 1ÿ…...
python的pip download命令-2
当然可以,下面我详细解释一下 pip download 的作用、用法和技术原理。 🧠 一句话总结: pip download 是 pip 提供的一个命令,用来下载 Python 包及其依赖项的安装文件,但不会安装。 🔍 和 pip install 的区别: 命令作用是否安装是否联网典型用途pip install安装指定包…...
【Java设计模式及实践学习-第4章节-结构型模式】
第4章节-结构型模式 笔记记录 1. 适配器模式2. 代理模式3. 装饰器模式4. 桥接模式5. 组合模式6. 外观模式7. 享元模式8. 总结 1. 适配器模式 2. 代理模式 3. 装饰器模式 4. 桥接模式 5. 组合模式 6. 外观模式 7. 享元模式 Java语言中的String字符串就使用了享元模式&…...
python:mido 提取 midi文件中某一音轨的音乐数据
pip install mido 使用 mido库可以方便地处理 MIDI 文件,提取其中音轨的音乐数据。 1.下面的程序会读取指定的 MIDI 文件,并提取指定编号音轨的音乐数据,主要包括音符事件等信息。 编写 mido_extract.py 如下 # -*- coding: utf-8 -*- &…...
将输入帧上下文打包到下一个帧的预测模型中用于视频生成
Paper Title: Packing Input Frame Context in Next-Frame Prediction Models for Video Generation 论文发布于2025年4月17日 Abstract部分 在这篇论文中,FramePack是一种新提出的网络结构,旨在解决视频生成中的两个主要问题:遗忘和漂移。 具体来说,遗忘指的是在生成视…...
第六章:Multi-Backend Configuration
Chapter 6: Multi-Backend Configuration 从交响乐团到变形金刚:如何让代理适应不同环境? 在上一章任务工作流编排,我们学会了如何像指挥家一样协调任务。但就像变形金刚能切换不同形态应对环境变化一样,你的AI代理也需要能灵活切…...
tomcat远程Debug
tomcat远程Debug -- /bin目录下 catalina.bat文件下加一行 SET CATALINA_OPTS-server -Xdebug -Xnoagent -Djava.compilerNONE -Xrunjdwp:transportdt_socket,servery,suspendn,address8088idea端配置如下...
Vue3:component(组件:uniapp版本)
目录 一、基本概述二、基本使用三、插槽 一、基本概述 在项目的开发过程中,页面上井场会出现一些通用的内容,例如头部的导航栏,如果我们每一个页面都去写一遍,那实在是太繁琐了,所以,我们使用组件来解决这…...
rust编程学习(三):8大容器类型
1简介 rust标准库std::collections也提供了像C STL库中的容器,分为4种通用的容器,8种类型,如下表所示。 线性容器类型: 名称简介Vec<T>内存空间连续,可变长度的数组,类似于C中Vector<T>容器…...
前端中阻止事件冒泡的几种方法
在 JavaScript 前端开发中,阻止事件冒泡是处理 DOM 事件时的常见需求。以下是几种阻止事件冒泡的方法: 1. 使用 event.stopPropagation() 这是最常用的阻止事件冒泡的方法。 element.addEventListener(click, function(event) {event.stopPropagation…...
ShenNiusModularity项目源码学习(20:ShenNius.Admin.Mvc项目分析-5)
ShenNiusModularity项目的系统管理模块主要用于配置系统的用户、角色、权限、基础数据等信息,上篇文章中学习的日志列表页面相对独立,而后面几个页面之间存在依赖关系,如角色页面依赖菜单页面定义菜单列表以便配置角色的权限,用户…...
前端js需要连接后端c#的wss服务
背景 前端js需要连接后端wss服务 前端:js 后端:c# - 控制台搭建wss服务器 步骤1 wss需要ssl认证,所以需要个证书,随便找一台linux的服务器(windows的话,自己安装下openssl即可),…...
MAGI-1自回归式大规模视频生成
1. 关于 MAGI-1 提出 MAGI-1——一种世界模型(world model),通过自回归方式预测一系列视频块(chunk,固定长度的连续帧片段)来生成视频。 模型被训练为在时间维度上单调递增噪声的条件下对每个块进行去噪&a…...
深入剖析TCP协议(内容一):从OSI与TCP/IP网络模型到三次握手、四次挥手、状态管理、性能优化及Linux内核源码实现的全面技术指南
文章目录 TCP网络模型OSI参考模型TCP/IP五层模型 TCP状态TIME_WAIT 连接过程TCP三次握手TCP四次挥手 TCP优化TCP三次握手优化TCP四次挥手优化TCP数据传输优化 TCP TCP是面向连接的、可靠的、基于字节流的传输层通信协议: 面向连接:一定是一对一才能连接…...
基于deepseek的模型微调
使用 DeepSeek 模型(如 DeepSeek-VL、DeepSeek-Coder、DeepSeek-LLM)进行微调,可以分为几个关键步骤,下面以 DeepSeek-LLM 为例说明,适用于 Q&A、RAG、聊天机器人等方向的应用。 一、准备工作 1. 环境依赖 建议使用 transformers + accelerate 或 LoRA 等轻量微调方…...
node.js 实战——(path模块 知识点学习)
path 模块 提供了操作路径的功能 说明path. resolve拼接规范的绝对路径path. sep获取操作系统的路径分隔符path. parse解析路径并返回对象path. basename获取路径的基础名称path. dirname获取路径的目录名path. extname获得路径的扩展名 resolve 拼接规范的绝对路径 const…...
【k8s】docker、k8s、虚拟机的区别以及使用场景
一、Docker (一)概念 Docker 是一个开源的应用容器引擎,允许开发者将应用及其依赖打包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可实现虚拟化。 (二)隔离性 Docker 的隔离…...
校园外卖服务系统的设计与实现(代码+数据库+LW)
摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理,然而,随着近些年信息技术的迅猛发展,让许多比较老套的信息管理模式进行了更新迭代,外卖信息因为其管理内容繁杂,管理数量繁多导致手工进行处理不能满足广…...
Windows上使用Python 3.10结合Appium-实现APP自动化
一、准备工作 所需条件: Windows 10/11 操作系统 Python 3.10.x(建议3.10.9) Java JDK 8 或以上(建议JDK 8u301) Node.js 14.x 或以上(建议14.21.3) Appium Server 1.22.x 或以上(建…...
【计算机视觉】CV项目实战- SiamMask 单阶段分割跟踪器
SiamMask 单阶段分割跟踪器 一、项目概述与技术原理1.1 核心技术创新1.2 性能优势 二、实战环境搭建2.1 系统要求与依赖安装2.2 项目编译与配置 三、模型推理实战3.1 快速体验Demo3.2 常见运行时错误处理 四、模型训练指南4.1 数据准备流程4.2 训练执行与监控 五、高级应用与优…...
计算机视觉基础
1. 数字图像的基本概念 像素(Pixel):图像的最小构成单元,每个像素存储亮度或颜色信息。 灰度图像:每个像素是 0(黑)~255(白) 的标量值(8位无符号整数&#x…...
系统编程_进程间通信机制_消息队列与共享内存
消息队列概述 消息有类型:每条消息都有一个类型,就像每封信都有一个标签,方便分类和查找。消息有格式:消息的内容有固定的格式,就像每封信都有固定的信纸格式。随机查询:你可以按类型读取消息,…...
一种免费的离线ocr-汉字识别率100%
一般我们手机中常用的ocr库有,Tesseract,paddle ocr,EasyOCR, ocrLite等等,这些ocr库中百度的paddle ocr效果最好,但是再好的效果也会偶尔识别错几个汉字。当我们在做自动化脚本过程中,如果识别…...
Maven 工程中的pom.xml 文件(图文)
基本信息 单工程项目【pom.xml文件】中最基本的信息。 依赖引入 可以在Maven 中央仓库查找所需依赖:【直达:https://mvnrepository.com/】。 在【dependencies】标签中添加所需依赖。 <dependency><groupId>com.baomidou</groupId&g…...
图像预处理-模板匹配
就是用模板图在目标图像中不断的滑动比较,通过某种比较方法来判断是否匹配成功,找到模板图所在的位置。 - 不会有边缘填充。 - 类似于卷积,滑动比较,挨个比较象素。 - 返回结果res大小是:目标图大小-模板图大小1(H-…...
操作系统学习笔记
2.4 死锁 在学习本节时,请读者思考以下问题: 1)为什么会产生死锁?产生死锁有什么条件? 2)有什么办法可以解决死锁问题? 学完本节,读者应了解死锁的由来、产…...
5.4.云原生与服务网格
目录 1. Kubernetes与微服务集成 1.1 容器化部署规范 • 多环境配置管理(ConfigMap与Nacos联动) • 健康检查探针配置(Liveness/Readiness定制策略) 1.2 弹性服务治理 • HPA自动扩缩容规则设计 • Sentinel指标驱动弹性伸缩 2…...
[特殊字符][特殊字符]Linux驱动开发入门 | 并发与互斥机制详解
文章目录 👨💻Linux驱动开发入门 | 并发与互斥机制详解📌为什么驱动中需要并发和互斥控制?💡常见的并发控制机制🔐自旋锁和信号量通俗理解🌀自旋锁(Spinlock)——“厕所…...
时序数据库IoTDB自研的Timer模型介绍
一、引言 时序数据库在支持时序特性写入、存储、查询等功能的基础上,正逐步向深度分析领域迈进。自动化异常监测与智能化趋势预测已成为时序数据管理的核心需求。为了满足这些需求,时序数据库IoTDB团队积极探索,成功自研推出了面向时间序列的…...
RabbitMQ 详解(核心概念)
本文是博主在梳理 RabbitMQ 知识的过程中,将所遇到和可能会遇到的基础知识记录下来,用作梳理 RabbitMQ 的整体架构和功能的线索文章,通过查找对应的知识能够快速的了解对应的知识而解决相应的问题。 文章目录 一、RabbitMQ 是什么?…...
【数据结构和算法】6. 哈希表
本文根据 数据结构和算法入门 视频记录 文章目录 1. 哈希表的概念1.1 哈希表的实现方式1.2 哈希函数(Hash Function)1.3 哈希表支持的操作 2. Java实现 在前几章的学习中,我们已经了解了数组和链表的基本特性,不管是数组还是链表…...
RHCE第三次作业 搭建dns的正向解析服务器
server为服务器 client为客户端 设置主配置文件 在server下: [rootServer ~]#vim /etc/named.conf #进入到配置页面,并修改 设置区域文件 [rootServer ~]# vim /etc/named.rfc1912.zones 设置域名解析文件 [rootServer named]# cd /var/named…...
【每天一个知识点】如何解决大模型幻觉(hallucination)问题?
解决大模型幻觉(hallucination)问题,需要从模型架构、训练方式、推理机制和后处理策略多方面协同优化。 🧠 1. 引入 RAG 框架(Retrieval-Augmented Generation) 思路: 模型生成前先检索知识库中…...
Python深拷贝与浅拷贝:避开对象复制的陷阱
目录 一、为什么需要区分深浅拷贝? 二、内存中的对象真相 三、浅拷贝的真相 四、深拷贝的奥秘 五、自定义对象的拷贝 六、性能对比实验 七、常见陷阱与解决方案 八、最佳实践指南 九、现代Python的拷贝优化 结语 一、为什么需要区分深浅拷贝? …...
批量处理多个 Word 文档:插入和修改页眉页脚,添加页码的方法
Word 页眉页脚的设置在日常工作中非常常见,尤其是需要统一格式的文档,如毕业论文、公司内部资料等。在这些文档中,页眉页脚通常包含时间、公司标志、文档标题、文件名或作者姓名等信息。有时,我们不仅需要简单的文字页眉页脚&…...
大语言模型(LLM)的Prompt Engineering:从入门到精通
大语言模型(LLM)的Prompt Engineering:从入门到精通 系统化学习人工智能网站(收藏):https://www.captainbed.cn/flu 引言:Prompt Engineering——解锁AI生产力的金钥匙 当ChatGPT在2023年引爆…...
poi生成横向文档以及复杂表头
代码: //创建页面并且创建横向A4XWPFDocument doc new XWPFDocument();CTDocument1 document doc.getDocument();CTBody body document.getBody();if (!body.isSetSectPr()) {body.addNewSectPr();}CTSectPr section body.getSectPr();if (!section.isSetPgSz()) {section.…...