【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 ……🔚
原创不易,转载请标明出处
对您有帮助的话可以一键三连,会持续更新的(嘻嘻)
《新程序员》:云原生和全面数字化实践 50位技术专家共同创作,文字、视频、音频交互阅读