> 文档中心 > 【Linux】高并发服务器设计——socket封装

【Linux】高并发服务器设计——socket封装

目录

一、什么是高并发服务器

二、socket封装+epoll封装

1、为什么要进行封装?

2、需要封装哪些类?

地址类(CHostAddress)

1)CHostAddress.h

2)CHostAddress.cpp

socket基类(CBaseSocket)

1)CBaseSocket.h

2)  CBaseSocket.cpp

TCP派生类(CTcpServer)

1)CTcpServer.h

2)CTcpServer.cpp

epoll类(CEpollServer)

1)CEpollServer.h

2)CEpollServer.cpp

三、主函数测试

main.cpp

客户端示例代码

测试结果


一、什么是高并发服务器

📜高

        允许同时上线的客户端数量高,即服务器能够同时接收多个客户端的连接请求。需要用到epoll多路IO复用技术。

📜并发

        可以同时处理多个客户端的业务,客户端不需要“排队”等待服务器处理业务。需要用线程池来进行并发设计。

        比如遇到双十一的时候,一时间有大量的用户上线进行抢购,服务器会同时收到大量的客户端请求,需要同时处理大量的业务,这时服务器的高并发作用就体现出来了。如果服务器的高并发处理的不好,可能服务器就会“罢工”,用户体验极其差(账号登不上去,请求响应的时间长)。

        因此一个强大的服务器,必须要高并发

二、socket封装+epoll封装

  • 本文介绍高并发服务器中的epoll封装,下期介绍线程池的封装

1、为什么要进行封装?

        因为c++是面向对象的语言,需要体现封装性,将各个业务封装成类也便于后期维护和修改,总不能一个业务在main函数直接写完吧。

2、需要封装哪些类?

  • 服务器和客户端要通过网络连接起来,并且要同时能够接收多个客户端的连接请求,因此需要封装下面这几个类

🔶地址类(ip地址+端口号)

🔶socket基类

🔶TCP派生类(继承于socket基类)

🔶epoll多路复用类

💡

  • 下面开始对这些类进行封装


地址类(CHostAddress)

  • 1)CHostAddress.h

#pragma once#include   #include #include #include #include class CHostAddress{public:CHostAddress(char *ip, unsigned short port);~CHostAddress();char* getIp();void setIp(char* ip);unsigned short getPort();void setPort(unsigned short port);struct sockaddr_in getAddr_in();struct sockaddr* getAddr();int getLength();private:char ip[16]; //保存ip地址int length; //保存 sockaddr_in 结构体长度unsigned short port; //端口号struct sockaddr_in s_addr; };
  • 2)CHostAddress.cpp

#include "CHostAddress.h"CHostAddress::CHostAddress(char* ip, unsigned short port){    memset(this->ip, 0, sizeof(this->ip));    strcpy(this->ip, ip);    this->port = port;    this->s_addr.sin_family = AF_INET; //ipv4 协议族    this->s_addr.sin_port = htons(this->port); //将端口号转换为网络字节序    this->s_addr.sin_addr.s_addr = inet_addr(this->ip);    this->length = sizeof(this->s_addr);}CHostAddress::~CHostAddress(){}char* CHostAddress::getIp(){    return this->ip;}void CHostAddress::setIp(char* ip){    strcpy(this->ip, ip);}unsigned short CHostAddress::getPort(){    return this->port;}void CHostAddress::setPort(unsigned short port){    this->port = port;}sockaddr_in CHostAddress::getAddr_in(){    return this->s_addr;}sockaddr* CHostAddress::getAddr(){    // bind函数需要用到struct sockaddr *,因此return类型转换之后数据    return (struct sockaddr *)&(this->s_addr);}int CHostAddress::getLength(){    return this->length;}

socket基类(CBaseSocket)

  • 1)CBaseSocket.h

#pragma once#include   #include #include #include class CBaseSocket{public:CBaseSocket(char* ip, unsigned short port);~CBaseSocket();void Start();int getSocketFd();virtual void Run() = 0;//写成纯虚函数,子类来实现virtual void Stop() = 0;//写成纯虚函数,子类来实现protected:int socketFd;//写到受保护区,子类可以用到};
  • 2)  CBaseSocket.cpp

#include "CBaseSocket.h"CBaseSocket::CBaseSocket(char* ip, unsigned short port){this->socketFd = 0;}CBaseSocket::~CBaseSocket(){}void CBaseSocket::Start(){//打通网络通道this->socketFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (this->socketFd Run();//走子类实现的run函数}int CBaseSocket::getSocketFd(){return this->socketFd;}

TCP派生类(CTcpServer)

  • 1)CTcpServer.h

#pragma once#include "CBaseSocket.h"#include "CHostAddress.h"#include   #include #include #include #include #define LISTEN_MAX_NUM 10  class CTcpServer :    public CBaseSocket{public:    CTcpServer(char* ip, unsigned short port);    ~CTcpServer();    void Run();    void Stop();    CHostAddress* getAddress();    void setAddress(CHostAddress* address);private:    CHostAddress* address;//地址类};
  • 2)CTcpServer.cpp

#include "CTcpServer.h"CTcpServer::CTcpServer(char* ip, unsigned short port)    :CBaseSocket(ip, port){    this->address = new CHostAddress(ip, port);}CTcpServer::~CTcpServer(){}void CTcpServer::Run(){    int opt_val = 1;    int res = 0;    //端口复用 解决出现 adress already use的问题    res = setsockopt(this->socketFd, SOL_SOCKET, SO_REUSEADDR, (const void*)&opt_val, sizeof(opt_val));    if (res == -1)    { perror("setsockopt error");    }    //绑定端口号和地址 协议族    res = bind(this->socketFd, this->address->getAddr(), this->address->getLength());    if (res == -1)    { perror("bind error");    }    //监听这个地址和端口有没有客户端来连接    res = listen(this->socketFd, LISTEN_MAX_NUM);    if (res == -1)    { perror("listen error");    }}void CTcpServer::Stop(){    if (this->socketFd != 0)    { close(this->socketFd); this->socketFd = 0;    }}CHostAddress* CTcpServer::getAddress(){    return this->address;}void CTcpServer::setAddress(CHostAddress* address){    this->address = address;}

epoll类(CEpollServer)

  • 1)CEpollServer.h

#pragma once#include #include #include "CTcpServer.h"#define EPOLL_SIZE 5using namespace std;class CEpollServer{public:CEpollServer(char *ip, unsigned short port);~CEpollServer();void Start();private:int epollfd;int epollwaitefd;int acceptFd;char buf[1024]; //存放客户端发来的消息struct epoll_event epollEvent;struct epoll_event epollEventArray[5];CTcpServer* tcp;//TCP类};
  • 2)CEpollServer.cpp

#include "CEpollServer.h"CEpollServer::CEpollServer(char* ip, unsigned short port){//初始化 TcpServer类this->tcp = new CTcpServer(ip, port);this->tcp->Start();cout << "socketFd = " <tcp->getSocketFd() <epollfd = 0;this->epollwaitefd = 0;this->acceptFd = 0;bzero(this->buf, sizeof(this, buf));//事件结构体初始化bzero(&(this->epollEvent), sizeof(this->epollEvent));//绑定当前准备好的sockedfd(可用网络对象)this->epollEvent.data.fd = this->tcp->getSocketFd();//绑定事件为客户端接入事件this->epollEvent.events = EPOLLIN;//创建epollthis->epollfd = epoll_create(EPOLL_SIZE);//将已经准备好的网络描述符添加到epoll事件队列中epoll_ctl(this->epollfd, EPOLL_CTL_ADD, this->tcp->getSocketFd(), &(this->epollEvent));}CEpollServer::~CEpollServer(){}void CEpollServer::Start(){while (1){cout << "epoll wait client…………" <epollwaitefd = epoll_wait(this->epollfd, epollEventArray, EPOLL_SIZE, -1);if (this->epollwaitefd < 0){perror("epoll wait error");}for (int i = 0; i epollwaitefd; i++){//判断是否有客户端上线if (epollEventArray[i].data.fd == this->tcp->getSocketFd()){cout << "网络开始工作,等待客户端上线" <acceptFd = accept(this->tcp->getSocketFd(), NULL, NULL);cout << "acceptfd = " <acceptFd <acceptFd;epollEvent.events = EPOLLIN; //EPOLLIN表示对应的文件描述符可以读epoll_ctl(this->epollfd, EPOLL_CTL_ADD, this->acceptFd, &epollEvent);}else if (epollEventArray[i].events & EPOLLIN){bzero(this->buf, sizeof(this->buf));int res = read(epollEventArray[i].data.fd, this->buf, sizeof(this->buf));if (res > 0){cout << "服务器收到 fd = " << epollEventArray[i].data.fd << " 发来的数据:buf = " <buf << endl;}else if (res <= 0){cout << "客户端 fd = "<< epollEventArray[i].data.fd <<" 掉线……" <epollfd, EPOLL_CTL_DEL, epollEventArray[i].data.fd, &epollEvent);}}}}}

三、主函数测试

  • main.cpp

#include #include "CEpollServer.h"using namespace std;int main(){CEpollServer* epoll = new CEpollServer("127.0.0.1", 12345);epoll->Start();return 0;}
  • 客户端示例代码

使用下列客户端连接刚才封装好的服务器,测试服务器能否收到客户端的信息

#include #include    #include #include #include #include #include #include  using namespace std; int main(){int socketfd = 0;int acceptfd = 0;int len = 0;int res = 0;char buf[255] = { 0 };//初始化网络socketfd = socket(AF_INET, SOCK_STREAM, 0);if (socketfd == -1){perror("socket error");}else{struct sockaddr_in s_addr;//确定使用哪个协议族  ipv4s_addr.sin_family = AF_INET; //填入服务器的ip地址  也可以是  127.0.0.1 (回环地址)s_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //端口一个计算机有65535个  10000以下是操作系统自己使用的,  自己定义的端口号为10000以后s_addr.sin_port = htons(12345);  //自定义端口号为12345 len = sizeof(s_addr); //绑定ip地址和端口号int res = connect(socketfd, (struct sockaddr*)&s_addr, len);if (res == -1){perror("connect error");}else{while (1){cout << "请输入:" <> buf;write(socketfd, buf, sizeof(buf));bzero(buf, sizeof(buf));}}}return 0;}
  • 测试结果

😎

客户端成功连接上服务器,服务器也能收到客户端发来的消息,封装成功! 

😘后面会出关于线程池封装的内容,今天就到这啦!

  • The end ……🔚

原创不易,转载请标明出处

对您有帮助的话可以一键三连,会持续更新的(嘻嘻)

【Linux】高并发服务器设计——socket封装 《新程序员》:云原生和全面数字化实践 【Linux】高并发服务器设计——socket封装 50位技术专家共同创作,文字、视频、音频交互阅读