返回顶部
首页 > 资讯 > 操作系统 >【Linux网络编程】高并发服务器 socket+epoll封装 服务器/客户端举例
  • 880
分享到

【Linux网络编程】高并发服务器 socket+epoll封装 服务器/客户端举例

linux服务器学习c++ubuntu 2023-09-03 19:09:06 880人浏览 八月长安
摘要

目录 前言 一、高并发服务器 💻什么是高并发? 💻高并发的处理指标? 💻高并发和多线程的关系和区别? 二、搭建服务器/客户端 💻服务器代码(4种类的封装) 🌈地

目录

前言

一、高并发服务器

💻什么是高并发?

💻高并发的处理指标?

💻高并发和多线程的关系和区别?

二、搭建服务器/客户端

💻服务器代码(4种类的封装)

🌈地址类【CHostAddress】

🌈socket类【CBaseSocket】

🌈TCP类【CTcpServer】

🌈epoll类 【CEpollServer】

💻客户端代码

💻案例测试


前言

本文主要学习Linux内核编程,结合Visual Studio 2019进行跨平台编程,内容包括高并发服务器的介绍、服务器代码封装(Socket+epoll)、服务器/客户端测试

一、高并发服务器

💻什么是高并发?

📘  高并发 是一种系统运行过程中遇到的一种  “短时间内遇到大量操作请求”  的情况

      【主要发生在WEB系统集中大量访问收到大量请求】

🌰举个例子:12306的抢票情况;天猫双十一活动【突然下单一万张票,上万人下单购物】

该情况发生会导致系统在这段时间内执行大量操作,例如,对资源的请求,数据库的操作等

💻高并发的处理指标?

高并发相关常用的一些指标有:


1️⃣响应时间(Response Time)

📗含义:系统对请求做出响应的时间

🌰举个例子:系统处理一个Http请求需要200ms,这个200ms就是系统的响应时间


2️⃣吞吐量(Throughput)

📗含义:单位时间内处理的请求数量


3️⃣每秒查询率QPS(Query Per Second)

📗含义 :每秒响应请求数

📖在互联网领域,这个指标和吞吐量区分的没有这么明显


4️⃣并发用户数

📗含义:同时承载正常使用系统功能的用户数量

🌰举个例子:例如一个即时通讯系统,同时在线量一定程度上代表了系统的并发用户数

💻高并发和多线程的关系和区别?

“高并发和多线程”   总是被一起提起,给人感觉两者好像相等,实则    【高并发 ≠ 多线程


1️⃣多线程

  • 线程是java的特性,因为现在CPU都是多核多线程的,可以同时执行几个任务,为了提高JVM的执行效率,java提供了这种多线程的机制,以增强数据处理效率
  • 多线程对应的是CPU,高并发对应的是访问请求,可以用单线程处理所有访问请求,也可以用多线程同时处理访问请求
  • 在过去单CPU时代,单任务在一个时间点只能执行单一程序。之后发展到多任务阶段,计算机能在同一时间点并行执行多任务或多进程
  • 虽然并不是真正意义上的 “同一时间点”,而是多个任务或进程共享一个CPU,并交由操作系统来完成多任务间对CPU的运行切换,以使得每个任务都有机会获得一定的时间片运行
  • 再后来发展到多线程技术,使得在一个程序内部能拥有多个线程并行执行。一个线程的执行可以被认为是一个CPU在执行该程序。当一个程序运行在多线程下,就好像有多个CPU在同时执行该程序
  • 📗总结:多线程是处理高并发的一种编程方法,即并发需要用多线程实现

2️⃣高并发

  • 高并发不是JAVA的专有的东西,是语言无关的广义的,为提供更好互联网服务而提出的概念
  • 典型的场景:例如,12306抢火车票,天猫双十一秒杀活动等。该情况的发生会导致系统在这段时间内执行大量操作,例如对资源的请求,数据库的操作等。
  • 如果高并发处理不好,不仅仅降低了用户的体验度(请求响应时间过长),同时可能导致系统宕机,严重的甚至导致OOM异常,系统停止工作等
  • 如果,要想系统能够适应高并发状态,则需要从各个方面进行系统优化,包括,硬件、网络、系统架构开发语言的选取、数据结构的运用、算法优化、数据库优化等,而多线程只是其中解决方法之一

二、搭建服务器/客户端

💻服务器代码(4种类的封装)

  • 🍔地址类
  • 🍟socket基类
  • 🍕TCP派生类
  • 🍿epoll类

🌈地址类【CHostAddress】

📍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;};

📍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;     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】

📍CBaseSocket.h

#pragma once#include  //socket头文件        #include //socket头文件  #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;//写到受保护区,子类可以用到};

📍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, 0);//IPPROTO_TCP用0替换也行if (this->socketFd < 0)//大于0成功,小于0失败{perror("socket error");//socket创建失败}this->Run();//子类实现的run函数}int CBaseSocket::getSocketFd(){return this->socketFd;}

🌈tcp类【CTcpServer】

📍CTcpServer.h 

#pragma once#include#include "CBaseSocket.h"#include "CHostAddress.h"#include #include #include #include           #include using namespace std;#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;//地址类};

📍CTcpServer.cpp 

#include "CTcpSever.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");    }    cout << "Server start success socketFd = " << this->socketFd << endl;}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】

 📍CEpollServer.h

#pragma once#include #include #include "CTcpSever.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类};

📍 CEpollServer.cpp

#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;//创建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" << 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 绑定事件添加到epollepollEvent.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           #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("192.168.48.129");//端口一个计算机有65535个  10000以下是操作系统自己使用的,自己定义的端口号为10000以后s_addr.sin_port = htons(12345);  //自定义端口号为12345len = 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;}

💻案例测试

📍main.cpp

#include #include "CEpollServer.h"using namespace std;int main(){CEpollServer* epoll = new CEpollServer("192.168.48.129", 12345);epoll->Start();return 0;}

📍测试效果 

通过linux连接VS进行跨平台编程,上为本文设计的服务器,下为两个与之相连的客户端,在客户端1和客户端2中输入内容,服务器上能接收到相应的信息,即表示测试成功!如下动图所示: 

参考:

https://www.csdn.net/tags/MtjaUgxsMzAzMjgtYmxvZwO0O0OO0O0O.html  

【Linux】高并发服务器设计——socket封装_似末的博客-CSDN博客_linux socket 高并发

以上就是本文的全部内容啦!如果对您有帮助,麻烦点赞啦!收藏啦!欢迎各位评论区留言!!!

来源地址:https://blog.csdn.net/m0_61745661/article/details/125527759

--结束END--

本文标题: 【Linux网络编程】高并发服务器 socket+epoll封装 服务器/客户端举例

本文链接: https://lsjlt.com/news/392734.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作