Socket封装---模板方法类
目录
一、模板方法类
二、Socket的框架
三、TCPSocket对父类的虚函数重写
在平时写网络代码的时候,经常会涉及到socket套接字的部分,这一部分用的十分频繁,但是流程都是固定的,我实在是饱受其苦,但是由于C++不像java一样有已经封装好的socket和serverSock类。所以我在想能不能自己把套接字封装一下,这样以后在使用的时候就会十分便捷。
一、模板方法类
模板方法模式(Template Method Pattern)是一种行为设计模式,它在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变算法的结构即可重定义算法的某些特定步骤。
模板方法类通常适用于固定的步骤,但是不同人使用这个相同的步骤可能会有细小的差别。
二、Socket的框架
唯一要注意的一点就是这里使用了智能指针,将Socket包裹成了shared_ptr,但是使用SockPtr的时候,仍然可以传他的子类TCPSocket进去,以后还是能用SockPtr来调用成员函数的。从这里可以体现出继承和多态的特性。
class Socket;
//因为这里要用到Socket这个类,但是此时他还没有被定义出来,所以我们在他前面声明一下,告诉编译器他的定义在后面,让我们在这一行可以使用他
using SockPtr = std::shared_ptr<Socket>;
// 基本套接字:只负责提供接口,他的子类通过让接口做组合,完成固定的逻辑
class Socket
{
public:
public:
virtual void CreateSocketOrDie() = 0;
virtual void CreateBindOrDie(uint16_t port) = 0;
virtual void CreateListenOrDie(int backlog = gbacklog) = 0;
virtual SockPtr Accepter(InetAddr *clientAddr) = 0;
virtual bool Connector(const std::string& peerip,uint16_t peerport) = 0;
virtual int sockfd()=0;
virtual void Close()=0;
virtual int Recv(std::string* out)=0;
virtual int Send(const std::string& in)=0;
public:
//父类中只是对虚函数进行组合,以固定的逻辑顺序执行函数组合。
//但是实际上调用的时候用父类的指针或者引用调用BuildListenSocket这种函数,会走子类重写的函数,这是多态的特性
void BuildListenSocket(uint16_t port)
{
// 1.创建套接字
CreateSocketOrDie();
// 2.绑定
CreateBindOrDie(port);
// 3.设置监听状态
CreateListenOrDie();
}
bool BuildClientSocket(const std::string& peerip,uint16_t peerport)
{
//1.创建套接字
CreateSocketOrDie();
//2.连接
return Connector(peerip,peerport);
}
};
三、TCPSocket对父类的虚函数重写
class TcpSocket : public Socket
{
public:
TcpSocket() {}
TcpSocket(int sockfd) : _sockfd(sockfd)
{}
~TcpSocket()
{}
void CreateSocketOrDie() override
{
// 1. 创建socket
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
{
LOG(FATAL, "socket create error\n");
exit(SOCKET_ERROR);
}
LOG(INFO, "socket create success, sockfd: %d\n", _sockfd); // 3
}
void CreateBindOrDie(uint16_t port) override
{
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;
// 2. bind sockfd 和 Socket addr
if (::bind(_sockfd, (struct sockaddr *)&local, sizeof(local)) < 0)
{
LOG(FATAL, "bind error\n");
exit(BIND_ERROR);
}
LOG(INFO, "bind success, sockfd: %d\n", _sockfd);
}
void CreateListenOrDie(int backlog) override
{
// 3. 因为tcp是面向连接的,tcp需要未来不断地能够做到获取连接
if (::listen(_sockfd, backlog) < 0)
{
LOG(FATAL, "listen error\n");
exit(LISTEN_ERR);
}
LOG(INFO, "listen success\n");
}
SockPtr Accepter(InetAddr *clientAddr) override
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
// 4. 获取新连接
int sockfd = ::accept(_sockfd, (struct sockaddr *)&client, &len);
if (sockfd < 0)
{
LOG(WARNING, "accept error\n");
return nullptr;
}
*clientAddr = InetAddr(client);
LOG(INFO, "get a new link, client info : %s, sockfd is : %d\n", clientAddr->AddrStr().c_str(), sockfd);
return std::make_shared<TcpSocket>(sockfd);
}
bool Connector(const std::string& peerip,uint16_t peerport) override
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(peerport);
::inet_pton(AF_INET, peerip.c_str(), &server.sin_addr);
int n = ::connect(_sockfd, (struct sockaddr *)&server, sizeof(server));
if (n < 0)
{
return false;
}
return true;
}
int sockfd() override
{
return _sockfd;
}
void Close() override
{
if(_sockfd>0)
{
::close(_sockfd);
}
}
int Recv(std::string* out)override
{
char inbuffer[1024]; // 当做字符串
int n = ::recv(_sockfd, inbuffer, sizeof(inbuffer) - 1,0);
if (n > 0)
{
inbuffer[n] = 0;
//覆盖式不可取,要使用+=
*out+=inbuffer;
}
return n;
}
int Send(const std::string& in)override
{
return ::send(_sockfd,in.c_str(),in.size(),0);
}
private:
int _sockfd; // 可以是listen也可是普通套接字
};
// //使用的时候:
// Socket* sock=new TcpSocket();
// sock->BuildListenSocket();
}
这上面除了封装代码,还使用到了LOG日志,这个在我之前的笔记中有写到。当然除了TCP可以对他进行重写,你还可以定义一个UDPSocket类,与上述类似。