文章目录
- 使用模板方法设计模式封装套接字
- 使用封装后的套接字实现Tcp服务器和客户端
- 实现Tcp服务器
- 实现Tcp客户端
- 工厂模式
使用模板方法设计模式封装套接字
可以使用模块方法
设计模式来设计套接字 socket 的封装
模板方法(Template Method)设计模式是一种行为设计模式,它在一个方法中定义了一个算法的骨架,并允许子类为一个或多个步骤提供实现。模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。
- 抽象类(Abstract Class):定义了一个或多个抽象操作,以及一个模板方法。这个模板方法调用了一个或多个抽象操作。
- 具体子类(Concrete Subclass):实现抽象类中的抽象操作,从而完成算法中特定步骤的具体实现。
抽象类定义了一个模板方法,这个方法通常包含对具体方法的调用,抽象类还定义了一些抽象方法,这些方法会在模板方法中被调用,但具体的实现由子类来提供(抽象类,也就是父类中,将这些方法都设置为纯虚函数,子类要重写纯虚函数),子类通过继承抽象类并提供抽象方法的实现,从而可以自定义模板方法中的某些步骤,当模板方法被调用时,它会按照定义的顺序依次调用抽象类中的抽象方法和具体方法。
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define Convert(addrptr) ((struct sockaddr *)addrptr)
namespace Net_Work
{
const static int defaultsockfd = -1;
const int backlog = 5;
enum
{
SocketError = 1,
BindError,
ListenError
};
// 封装一个基类, 套接字对应的接口类
// 一旦一个类中有纯虚函数, 只有被继承并且实现此方法后, 才能创建对象
// ::close 使用系统的接口函数
// 设计模式:模板方法
class Socket
{
public:
virtual ~Socket() {}
virtual void CreateSocketOrDie() = 0; // 创建套接字
virtual void BindSocketOrDie(uint16_t port) = 0; // 绑定
virtual void ListenSocketOrDie(int backlog) = 0; // 监听
// 获取连接, 拿到tcp所需的新的文件描述符 newsockfd, 并返回并 create 一个 tcp 套接字, 并将客户端信息通过输出型参数返回
virtual Socket* AcceptConnection(std::string* peerip, uint16_t* peerport) = 0;
virtual bool ConnetServer(std::string& serverip, uint16_t serverport) = 0;
virtual int GetSockFd() = 0;
virtual void SetSockFd(int sockfd) = 0;
virtual void CloseSockfd() = 0;
public:
// 下面这些方法会被子类继承下去
void BulidListenSocketMethod(uint16_t port, int backlog) // 创建监听套接字, 给 Server 用的
{
CreateSocketOrDie();
BindSocketOrDie(port);
ListenSocketOrDie(backlog);
}
bool BulidConnectSocketMethod(std::string& serverip, uint16_t serverport) // 创建连接套接字, 给Client 用
{
CreateSocketOrDie();
return ConnetServer(serverip, serverport); // connet 有可能会失败
}
void BulidNormalSocketMethod(int sockfd)
{
SetSockFd(sockfd);
}
};
// 成员函数用 override 修饰后, 派生类必须重载基类的同名虚函数, 否则编译不能通过
class TcpSocket : public Socket
{
public:
TcpSocket(int sockfd = defaultsockfd) : _sockfd(sockfd)
{
}
~TcpSocket()
{
}
void CreateSocketOrDie() override
{
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
exit(SocketError);
}
void BindSocketOrDie(uint16_t port) override
{
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = INADDR_ANY;
local.sin_port = htons(port);
int n = ::bind(_sockfd, Convert(&local), sizeof(local));
if (n < 0)
exit(BindError);
}
void ListenSocketOrDie(int backlog) override
{
int n = ::listen(_sockfd, backlog);
if (n < 0)
exit(ListenError);
}
Socket* AcceptConnection(std::string* peerip, uint16_t* peerport) override
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int newsockfd = ::accept(_sockfd, Convert(&peer), &len);
if (newsockfd < 0)
return nullptr;
*peerport = ntohs(peer.sin_port);
*peerip = inet_ntoa(peer.sin_addr);
Socket* s = new TcpSocket(newsockfd);
return s;
}
bool ConnetServer(std::string& serverip, uint16_t serverport) override
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(serverip.c_str());
server.sin_port = htons(serverport);
int n = ::connect(_sockfd, Convert(&server), sizeof(server));
if (n == 0)
return true;
else
return false;
}
int GetSockFd() override
{
return _sockfd;
}
void SetSockFd(int sockfd) override
{
_sockfd = sockfd;
}
void CloseSockfd() override
{
if (_sockfd > defaultsockfd)
::close(_sockfd);
}
private:
int _sockfd;
};
}
使用封装后的套接字实现Tcp服务器和客户端
实现Tcp服务器
#pragma once
#include "Socket.hpp"
#include <iostream>
#include <pthread.h>
#include <functional>
// void 表示返回值为空, ()里面是参数
using func_t = std::function<void(Net_Work::Socket* sockp)>;
class TcpServer;
class ThreadData
{
public:
ThreadData(TcpServer* tcp_this, Net_Work::Socket* sockp) :_this(tcp_this), _sockp(sockp)
{}
public:
TcpServer* _this;
Net_Work::Socket* _sockp;
};
class TcpServer
{
public:
TcpServer(uint16_t port, func_t handler_request) :
_port(port), _listensocket(new Net_Work::TcpSocket()), _handler_request(handler_request)
{
_listensocket->BulidListenSocketMethod(_port, Net_Work::backlog);
}
static void* ThreadRun(void* args)
{
pthread_detach(pthread_self());
ThreadData* td = static_cast<ThreadData*>(args);
td->_this->_handler_request(td->_sockp); // 先回调,
td->_sockp->CloseSockfd(); // 再关闭文件描述符
delete td->_sockp;
delete td;
}
void Loop()
{
while (true)
{
std::string peerip;
uint16_t peerport;
Net_Work::Socket* newsock = _listensocket->AcceptConnection(&peerip, &peerport);
if (newsock == nullptr) continue;
std::cout << "获取一个新连接 sockfd: " << newsock->GetSockFd() << " client info: " << peerip << ":" << peerport << std::endl;
pthread_t tid;
ThreadData* td = new ThreadData(this, newsock);
pthread_create(&tid, nullptr, ThreadRun, td);
}
}
~TcpServer()
{
delete _listensocket;
}
private:
int _port;
Net_Work::Socket* _listensocket;
func_t _handler_request;
};
实现Tcp客户端
#include "Protocol.hpp"
#include "Socket.hpp"
#include <unistd.h>
#include <iostream>
#include <string>
#include <memory>
int main(int argc, char* argv[])
{
if (argc != 3)
{
std::cout << "Usage: " << argv[0] << " serverip serverport" << std::endl;
return 0;
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
Net_Work::Socket* s = new Net_Work::TcpSocket();
if (!s->BulidConnectSocketMethod(serverip, serverport))
{
std::cerr << "connect " << serverip << ":" << serverport << "failed" << std::endl;
}
std::cout << "connect " << serverip << ":" << serverport << " success" << std::endl;
std::unique_ptr<Factory> factory = std::make_unique<Factory>();
std::shared_ptr<Request> req = factory->BuildRequest(10, 20, '+');
while (true)
{
req->Inc();
send(s->GetSockFd(), &(*req), sizeof(*req), 0);
sleep(1);
}
s->CloseSockfd();
return 0;
}
工厂模式
工厂设计模式是一种创建型设计模式,它提供了一种灵活的方式来实例化和组织对象的创建。工厂设计模式是一种创建对象的软件设计模式,它通过一个公共接口或基类来创建对象,而无需暴露对象的具体实现。这种设计模式的主要作用是将对象的创建与使用分离,从而降低耦合度,使代码更易于理解和维护。
- 抽象产品(Abstract Product):定义了一个产品的接口或抽象类,它描述了产品的共同属性和方法。
- 具体产品(Concrete Product):实现了抽象产品接口或继承自抽象产品类的具体类,代表了具体的对象。
- 工厂类(Factory Class):负责创建具体产品的实例,它通常包含一个工厂方法,用于创建产品对象。
优点:
- 解耦:将对象的创建与使用分离,降低了客户端与具体产品之间的耦合度。
- 灵活性和可扩展性:通过引入抽象层,可以轻松地创建新的具体产品类,而无需修改客户端代码。
- 代码复用:通过封装对象的创建过程,可以在多个客户端之间复用相同的创建逻辑。
简易的工厂模式:一个请求类,一个回应类,一个工厂类。
#include <iostream>
#include <memory>
// 请求
class Request
{
public:
Request()
{}
Request(int x, int y, char op) :_data_x(x), _data_y(y), _oper(op)
{}
private:
// 约定好的 x _oper y
int _data_x;
int _data_y;
char _oper; // + - * / %
};
// 相应
class Response
{
public:
Response()
{}
Response(int result, int code) :_result(result), _code(code)
{}
private:
int _result; // 计算结果
int _code; // 运算状态
};
// 工厂模式
class Factory
{
public:
std::shared_ptr<Request> BuildRequest()
{
std::shared_ptr<Request> req = std::make_shared<Request>();
return req;
}
std::shared_ptr<Request> BuildRequest(int x, int y, char op)
{
std::shared_ptr<Request> req = std::make_shared<Request>(x, y, op);
return req;
}
std::shared_ptr<Response> BuildResponse()
{
std::shared_ptr<Response> resp = std::make_shared<Response>();
return resp;
}
std::shared_ptr<Response> BuildResponse(int result, int code)
{
std::shared_ptr<Response> resp = std::make_shared<Response>(result, code);
return resp;
}
};