小言_互联网的博客

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

589人阅读  评论(0)

目录

前言

一、高并发服务器

💻什么是高并发?

💻高并发的处理指标?

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

二、搭建服务器/客户端

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

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


转载:https://blog.csdn.net/m0_61745661/article/details/125527759
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场