• 【Linux学习】高并发服务器 socket+epoll封装 服务器/客户端举例


    目录

    前言

    一、高并发服务器

    💻什么是高并发?

    💻高并发的处理指标?

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

    二、搭建服务器/客户端

    💻服务器代码(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

    1. #pragma once
    2. #include <sys/types.h> /* See NOTES */
    3. #include <sys/socket.h>
    4. #include <netinet/in.h>
    5. #include <string.h>
    6. #include <arpa/inet.h>
    7. class CHostAddress
    8. {
    9. public:
    10. CHostAddress(char* ip, unsigned short port);
    11. ~CHostAddress();
    12. char* getIp();
    13. void setIp(char* ip);
    14. unsigned short getPort();
    15. void setPort(unsigned short port);
    16. struct sockaddr_in getAddr_in();
    17. struct sockaddr* getAddr();
    18. int getLength();
    19. private:
    20. char ip[16]; //保存ip地址
    21. int length; //保存 sockaddr_in 结构体长度
    22. unsigned short port; //端口号
    23. struct sockaddr_in s_addr;
    24. };

    📍CHostAddress.cpp

    1. #include "CHostAddress.h"
    2. CHostAddress::CHostAddress(char* ip, unsigned short port)
    3. {
    4. memset(this->ip, 0, sizeof(this->ip));
    5. strcpy(this->ip, ip);
    6. this->port = port;
    7. this->s_addr.sin_family = AF_INET;
    8. this->s_addr.sin_port = htons(this->port);
    9. this->s_addr.sin_addr.s_addr = inet_addr(this->ip);
    10. this->length = sizeof(this->s_addr);
    11. }
    12. CHostAddress::~CHostAddress()
    13. {
    14. }
    15. char* CHostAddress::getIp()
    16. {
    17. return this->ip;
    18. }
    19. void CHostAddress::setIp(char* ip)
    20. {
    21. strcpy(this->ip, ip);
    22. }
    23. unsigned short CHostAddress::getPort()
    24. {
    25. return this->port;
    26. }
    27. void CHostAddress::setPort(unsigned short port)
    28. {
    29. this->port = port;
    30. }
    31. sockaddr_in CHostAddress::getAddr_in()
    32. {
    33. return this->s_addr;
    34. }
    35. sockaddr* CHostAddress::getAddr()
    36. {
    37. // bind函数需要用到struct sockaddr *,因此return类型转换之后数据
    38. return (struct sockaddr*)&(this->s_addr);
    39. }
    40. int CHostAddress::getLength()
    41. {
    42. return this->length;
    43. }

    🌈socket类【CBaseSocket】

    📍CBaseSocket.h

    1. #pragma once
    2. #include <sys/types.h> //socket头文件
    3. #include <sys/socket.h>//socket头文件
    4. #include <netinet/in.h>
    5. #include <stdio.h>
    6. class CBaseSocket
    7. {
    8. public:
    9. CBaseSocket(char* ip, unsigned short port);
    10. ~CBaseSocket();
    11. void Start();
    12. int getSocketFd();
    13. virtual void Run() = 0;//写成纯虚函数,子类来实现
    14. virtual void Stop() = 0;//写成纯虚函数,子类来实现
    15. protected:
    16. int socketFd;//写到受保护区,子类可以用到
    17. };

    📍CBaseSocket.cpp 

    1. #include "CBaseSocket.h"
    2. CBaseSocket::CBaseSocket(char* ip, unsigned short port)
    3. {
    4. this->socketFd = 0;
    5. }
    6. CBaseSocket::~CBaseSocket()
    7. {
    8. }
    9. void CBaseSocket::Start()
    10. {
    11. //打通网络通道
    12. this->socketFd = socket(AF_INET, SOCK_STREAM, 0);//IPPROTO_TCP用0替换也行
    13. if (this->socketFd < 0)//大于0成功,小于0失败
    14. {
    15. perror("socket error");//socket创建失败
    16. }
    17. this->Run();//子类实现的run函数
    18. }
    19. int CBaseSocket::getSocketFd()
    20. {
    21. return this->socketFd;
    22. }

    🌈TCP类【CTcpServer】

    📍CTcpServer.h 

    1. #pragma once
    2. #include<iostream>
    3. #include "CBaseSocket.h"
    4. #include "CHostAddress.h"
    5. #include <netinet/in.h>
    6. #include <unistd.h>
    7. #include <stdio.h>
    8. #include <sys/types.h> /* See NOTES */
    9. #include <sys/socket.h>
    10. using namespace std;
    11. #define LISTEN_MAX_NUM 10
    12. class CTcpServer :
    13. public CBaseSocket
    14. {
    15. public:
    16. CTcpServer(char* ip, unsigned short port);
    17. ~CTcpServer();
    18. void Run();
    19. void Stop();
    20. CHostAddress* getAddress();
    21. void setAddress(CHostAddress* address);
    22. private:
    23. CHostAddress* address;//地址类
    24. };

    📍CTcpServer.cpp 

    1. #include "CTcpSever.h"
    2. CTcpServer::CTcpServer(char* ip, unsigned short port)
    3. :CBaseSocket(ip, port)
    4. {
    5. this->address = new CHostAddress(ip, port);
    6. }
    7. CTcpServer::~CTcpServer()
    8. {
    9. }
    10. void CTcpServer::Run()
    11. {
    12. int opt_val = 1;
    13. int res = 0;
    14. //端口复用 解决出现 adress already use的问题
    15. res = setsockopt(this->socketFd, SOL_SOCKET, SO_REUSEADDR, (const void*)&opt_val, sizeof(opt_val));
    16. if (res == -1)
    17. {
    18. perror("setsockopt error");
    19. }
    20. //绑定端口号和地址 协议族
    21. res = bind(this->socketFd, this->address->getAddr(), this->address->getLength());
    22. if (res == -1)
    23. {
    24. perror("bind error");
    25. }
    26. //监听这个地址和端口有没有客户端来连接
    27. res = listen(this->socketFd, LISTEN_MAX_NUM);
    28. if (res == -1)
    29. {
    30. perror("listen error");
    31. }
    32. cout << "Server start success socketFd = " << this->socketFd << endl;
    33. }
    34. void CTcpServer::Stop()
    35. {
    36. if (this->socketFd != 0)
    37. {
    38. close(this->socketFd);
    39. this->socketFd = 0;
    40. }
    41. }
    42. CHostAddress* CTcpServer::getAddress()
    43. {
    44. return this->address;
    45. }
    46. void CTcpServer::setAddress(CHostAddress* address)
    47. {
    48. this->address = address;
    49. }

    🌈epoll类 【CEpollServer】

     📍CEpollServer.h

    1. #pragma once
    2. #include <sys/epoll.h>
    3. #include <iostream>
    4. #include "CTcpSever.h"
    5. #define EPOLL_SIZE 5
    6. using namespace std;
    7. class CEpollServer
    8. {
    9. public:
    10. CEpollServer(char* ip, unsigned short port);
    11. ~CEpollServer();
    12. void Start();
    13. private:
    14. int epollfd;
    15. int epollwaitefd;
    16. int acceptFd;
    17. char buf[1024]; //存放客户端发来的消息
    18. struct epoll_event epollEvent;
    19. struct epoll_event epollEventArray[5];
    20. CTcpServer* tcp;//TCP类
    21. };

    📍 CEpollServer.cpp

    1. #include "CEpollServer.h"
    2. CEpollServer::CEpollServer(char* ip, unsigned short port)
    3. {
    4. //初始化 TcpServer类
    5. this->tcp = new CTcpServer(ip, port);
    6. this->tcp->Start();
    7. cout << "socketFd = " << this->tcp->getSocketFd() << endl;
    8. //初始化数据成员
    9. this->epollfd = 0;
    10. this->epollwaitefd = 0;
    11. this->acceptFd = 0;
    12. bzero(this->buf, sizeof(this, buf));
    13. //事件结构体初始化
    14. bzero(&(this->epollEvent), sizeof(this->epollEvent));
    15. //绑定当前准备好的sockedfd(可用网络对象)
    16. this->epollEvent.data.fd = this->tcp->getSocketFd();
    17. //绑定事件为客户端接入事件
    18. this->epollEvent.events = EPOLLIN;
    19. //创建epoll
    20. this->epollfd = epoll_create(EPOLL_SIZE);
    21. //将已经准备好的网络描述符添加到epoll事件队列中
    22. epoll_ctl(this->epollfd, EPOLL_CTL_ADD, this->tcp->getSocketFd(), &(this->epollEvent));
    23. }
    24. CEpollServer::~CEpollServer()
    25. {
    26. }
    27. void CEpollServer::Start()
    28. {
    29. while (1)
    30. {
    31. cout << "epoll wait client" << endl;
    32. this->epollwaitefd = epoll_wait(this->epollfd, epollEventArray, EPOLL_SIZE, -1);
    33. if (this->epollwaitefd < 0)
    34. {
    35. perror("epoll wait error");
    36. }
    37. for (int i = 0; i < this->epollwaitefd; i++)
    38. {
    39. //判断是否有客户端上线
    40. if (epollEventArray[i].data.fd == this->tcp->getSocketFd())
    41. {
    42. cout << "网络_开始工作_等待客户端_上线" << endl;
    43. this->acceptFd = accept(this->tcp->getSocketFd(), NULL, NULL);
    44. cout << "acceptfd = " << this->acceptFd << endl;
    45. //上线的客户端描述符是acceptfd 绑定事件添加到epoll
    46. epollEvent.data.fd = this->acceptFd;
    47. epollEvent.events = EPOLLIN; //EPOLLIN表示对应的文件描述符可以读
    48. epoll_ctl(this->epollfd, EPOLL_CTL_ADD, this->acceptFd, &epollEvent);
    49. }
    50. else if (epollEventArray[i].events & EPOLLIN)
    51. {
    52. bzero(this->buf, sizeof(this->buf));
    53. int res = read(epollEventArray[i].data.fd, this->buf, sizeof(this->buf));
    54. if (res > 0)
    55. {
    56. cout << "服务器_收到 fd = " << epollEventArray[i].data.fd << " 送达数据: buf = " << this->buf << endl;
    57. }
    58. else if (res <= 0)
    59. {
    60. cout << "客户端 fd = " << epollEventArray[i].data.fd << " _掉线_" << endl;
    61. close(epollEventArray[i].data.fd);
    62. //从epoll中删除客户端描述符
    63. epollEvent.data.fd = epollEvent.data.fd;
    64. epollEvent.events = EPOLLIN;
    65. epoll_ctl(this->epollfd, EPOLL_CTL_DEL, epollEventArray[i].data.fd, &epollEvent);
    66. }
    67. }
    68. }
    69. }
    70. }

    💻客户端代码

    1. #include <iostream>
    2. #include <sys/types.h>
    3. #include <sys/socket.h>
    4. #include <netinet/in.h>
    5. #include <arpa/inet.h>
    6. #include <stdio.h>
    7. #include <unistd.h>
    8. #include <string.h>
    9. using namespace std;
    10. int main()
    11. {
    12. int socketfd = 0;
    13. int acceptfd = 0;
    14. int len = 0;
    15. int res = 0;
    16. char buf[255] = { 0 };//初始化
    17. //初始化网络
    18. socketfd = socket(AF_INET, SOCK_STREAM, 0);
    19. if (socketfd == -1)
    20. {
    21. perror("socket error");
    22. }
    23. else
    24. {
    25. struct sockaddr_in s_addr;
    26. //确定使用哪个协议族 ipv4
    27. s_addr.sin_family = AF_INET;
    28. //填入服务器的ip地址 也可以是 127.0.0.1 (回环地址)
    29. s_addr.sin_addr.s_addr = inet_addr("192.168.48.129");
    30. //端口一个计算机有65535个 10000以下是操作系统自己使用的,自己定义的端口号为10000以后
    31. s_addr.sin_port = htons(12345); //自定义端口号为12345
    32. len = sizeof(s_addr);
    33. //绑定ip地址和端口号
    34. int res = connect(socketfd, (struct sockaddr*)&s_addr, len);
    35. if (res == -1)
    36. {
    37. perror("connect error");
    38. }
    39. else
    40. {
    41. while (1)
    42. {
    43. cout << "请输入内容:" << endl;
    44. cin >> buf;
    45. write(socketfd, buf, sizeof(buf));
    46. bzero(buf, sizeof(buf));
    47. }
    48. }
    49. }
    50. return 0;
    51. }

    💻案例测试

    📍main.cpp

    1. #include <iostream>
    2. #include "CEpollServer.h"
    3. using namespace std;
    4. int main()
    5. {
    6. CEpollServer* epoll = new CEpollServer("192.168.48.129", 12345);
    7. epoll->Start();
    8. return 0;
    9. }

    📍测试效果 

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

    参考:

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

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

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

  • 相关阅读:
    Pycharm 配置Django 框架
    组内教研活动简报
    iOS小技能:安全措施
    Go语言的Mutex
    C语言-入门-static(十五)
    [msyql]实战:关于回表的一次查询优化实战
    java 生成相亲信息海报
    Hadoop:模板虚拟机的创建
    gin-admin-react踩坑
    zabbix5 使用自动发现对端口进行监控
  • 原文地址:https://blog.csdn.net/m0_61745661/article/details/125527759