目录
一、什么是高并发服务器
二、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复用技术。
并发
可以同时处理多个客户端的业务,客户端不需要“排队”等待服务器处理业务。需要用线程池来进行并发设计。
比如遇到双十一的时候,一时间有大量的用户上线进行抢购,服务器会同时收到大量的客户端请求,需要同时处理大量的业务,这时服务器的高并发作用就体现出来了。如果服务器的高并发处理的不好,可能服务器就会“罢工”,用户体验极其差(账号登不上去,请求响应的时间长)。
因此一个强大的服务器,必须要高并发。
因为c++是面向对象的语言,需要体现封装性,将各个业务封装成类也便于后期维护和修改,总不能一个业务在main函数直接写完吧。
地址类(ip地址+端口号)
socket基类
TCP派生类(继承于socket基类)
epoll多路复用类
#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;
};
#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;
}
#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;//写到受保护区,子类可以用到
};
#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 < 0)
{
perror("socket error");
}
this->Run();//走子类实现的run函数
}
int CBaseSocket::getSocketFd()
{
return this->socketFd;
}
#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;//地址类
};
#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;
}
#pragma once
#include
#include
#include "CTcpServer.h"
#define EPOLL_SIZE 5
using 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类
};
#include "CEpollServer.h"
CEpollServer::CEpollServer(char* ip, unsigned short port)
{
//初始化 TcpServer类
this->tcp = new CTcpServer(ip, port);
this->tcp->Start();
cout << "socketFd = " << this->tcp->getSocketFd() << endl;
//初始化数据成员
this->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;
//创建epoll
this->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…………" << endl;
this->epollwaitefd = epoll_wait(this->epollfd, epollEventArray, EPOLL_SIZE, -1);
if (this->epollwaitefd < 0)
{
perror("epoll wait error");
}
for (int i = 0; i < this->epollwaitefd; i++)
{
//判断是否有客户端上线
if (epollEventArray[i].data.fd == this->tcp->getSocketFd())
{
cout << "网络开始工作,等待客户端上线" << endl;
this->acceptFd = accept(this->tcp->getSocketFd(), NULL, NULL);
cout << "acceptfd = " << this->acceptFd << endl;
//上线的客户端描述符是acceptfd 绑定事件添加到epoll
epollEvent.data.fd = this->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 = " << this->buf << endl;
}
else if (res <= 0)
{
cout << "客户端 fd = "<< epollEventArray[i].data.fd <<" 掉线……" << endl;
close(epollEventArray[i].data.fd);
//从epoll中删除客户端描述符
epollEvent.data.fd = epollEvent.data.fd;
epollEvent.events = EPOLLIN;
epoll_ctl(this->epollfd, EPOLL_CTL_DEL, epollEventArray[i].data.fd, &epollEvent);
}
}
}
}
}
#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;
//确定使用哪个协议族 ipv4
s_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 << "请输入:" << endl;
cin >> buf;
write(socketfd, buf, sizeof(buf));
bzero(buf, sizeof(buf));
}
}
}
return 0;
}
客户端成功连接上服务器,服务器也能收到客户端发来的消息,封装成功!
后面会出关于线程池封装的内容,今天就到这啦!
原创不易,转载请标明出处
对您有帮助的话可以一键三连,会持续更新的(嘻嘻)