IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 【Linux】select多路转接 -> 正文阅读

[系统运维]【Linux】select多路转接

目录

  • 前言
  • select的定位与接口分析
  • select的特点
  • select的缺点
  • 基于select的echo服务器代码实现
  • telnet测试

前言

对于服务器来讲,需要一刻不停地监听请求,提供服务
在这里插入图片描述

我们不希望服务器主程序卡住

服务器监听的本质是:while 1 { 等待数据的到来->数据到来后读取数据 }

由于recv是阻塞等待,所以我们想尽一切办法来处理这个阻塞问题

  1. 多线程,把每一个链接交给一个新线程处理,新线程recv阻塞等待对应套接字的时候不会干扰到主线程;这时,阻塞等待+数据读取由线程全权承担
  2. 我们让内核帮助我们检测指定的套接字的状态。一旦套接字有数据就绪,就会把某个数据结构的状态设置为就绪,主程序检测到就绪,就会读取数据。这时,主程序只要不停地去检测一批套接字的状态就行。这时,等待由内核帮我们承担,主程序只要承担数据读取的任务

select的定位与接口分析

select的定位:只负责等待,是就绪事件通知机制,它等待IO就绪;唯一和read,write,recv,send不同的是,select可以等待多个文件描述符

来看select接口:

int select(
	int nfds, 
	fd_set *readfds,
	fd_set *writefds, 
	fd_set *exceptfds,
	struct timeval *timeout
);
  1. timeval结构体:秒+微秒
    nullptr:永久等待,直到某个fd就绪才返回
    3,0:等待3秒,没有就绪就返回
    0,0:没有就绪立马返回
  2. fd_set:比特位的位置代表文件描述符的编号,比特位的内容代表是否关心
    作用:所有关心X事件的文件描述符,都应该添加在这个集合里
    输入:用户告诉内核,你要帮我检测一下在这个集合中的fd的X事件
    输出:内核告诉用户,你关心的fd,有哪些文件描述符已经就绪了不具有保存sock的功能,只具有通知的能力
  3. fd_set:位图
 /* fd_set for select and pselect.  */
 typedef struct         
   {
     /* XPG4.2 requires this member name.  Otherwise avoid the name
        from the global namespace.  */
 #ifdef __USE_XOPEN
     __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
 # define __FDS_BITS(set) ((set)->fds_bits)
 #else
     __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
 # define __FDS_BITS(set) ((set)->__fds_bits)
 #endif
   } fd_set;

fd_set的作用是:比特位的位置代表哪一个文件描述符,比特位0/1代表文件描述符是否就绪

  1. 返回
    读就绪代表该文件描述符的底层数据已经就绪
    写就绪代表文件描述符可以继续写了
    异常就绪代表哪个文件描述符异常了

so

轮询检测有没有关心的fd就绪,覆盖式地写入位图,返回。所以select需要我们花一点点第三方变量把fds保存起来,一般是数组

在我们通知了一轮就绪的sock之后,可能还需要select帮助我们进行监测其他的fd,所以要对fd_set重新设置,故要提前保存

其中,监听套接字只关心读事件

获取新的fd之后我们不读它,因为我们不知道这个新的fd里面有没有读写事件就绪,所以要等下一轮循环

select的特点

  • select只能等待确定数量的文件描述符,有上限,可监控的fd的数量取决于fd_set的大小
  • 需要第三方数组存储fd
  • 每次循环都需要更新nfds的值

select的缺点

  • 每次调用select, 都需要手动设置fd集合, 从接口使用角度来说也非常不便
  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
  • 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
  • select支持的文件描述符数量太小

基于select的echo服务器代码实现

  1. socket.hpp
#pragma once 
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>

namespace ns_sock
{
  static const int BACKLOG = 5;

  enum {
    SOCKET_ERROR = 2,
    BIND_ERROR,
    LISTEN_ERROR,
    ACCEPT_ERROR
  };

  class Sock {
  public:
    static int Socket() {
      int sock = socket(AF_INET, SOCK_STREAM, 0);
      if(sock == -1) {
        std::cerr << "create socket failed" << std::endl;
        exit(SOCKET_ERROR);
      }
      int opt = 1;
      setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
      return sock;
    }
    
    static void Bind(int sock, uint16_t port) {
      sockaddr_in local;
      bzero(&local, sizeof(local));
      local.sin_family = AF_INET;
      local.sin_port = htons(port);
      local.sin_addr.s_addr = INADDR_ANY;
      if(bind(sock, (struct sockaddr*)&local, sizeof(local)) == -1) {
        std::cerr << "bind error" << std::endl;
        exit(BIND_ERROR);
      }
    }

    static void Listen(int sock) {
      if(listen(sock, BACKLOG) == -1) {
        std::cerr << "listen failed" << std::endl;
        exit(LISTEN_ERROR);
      }
    }

    static void Accept(){}

  };
}//ns_sock
  1. select.hpp
#pragma once 
#include "sock.hpp"
#include <sys/select.h>
namespace ns_select_server 
{
  static const uint16_t g_default_port = 8081;
  static const int SELECT_ERROR = 6;
  static const int BUFFER_SIZE = 4096;

  class SelectServer {
    public:
      SelectServer(int port = g_default_port):_listen_sock(-1), _port(port) {
        _listen_sock = ns_sock::Sock::Socket();
        ns_sock::Sock::Bind(_listen_sock, _port);
        ns_sock::Sock::Listen(_listen_sock);
        // 初始化第三方数组
        for(int i = 0; i < FD_SETSIZE; i++) {
          _fd_array[i] = -1;
        }
        // 添加listen套接字为第三方数组的第一个元素
        _fd_array[0] = _listen_sock;
      }

      void Loop() {
        while(true) {
          // 设置读事件的位图
          fd_set rfds;
          FD_ZERO(&rfds); // 初始化
          int max_fd = -1; // 初始化select的第一个参数
          // 告诉内核关心的读事件是什么
          for(int i = 0; i < FD_SETSIZE; i++) {
            if(_fd_array[i] != -1) {
              FD_SET(_fd_array[i], &rfds);
              if(max_fd < _fd_array[i]) {
                max_fd = _fd_array[i];
              }
            }
          }
          // select去进行等待
          int n = select(max_fd + 1, &rfds, nullptr, nullptr, nullptr); // 直到等到某个事件就绪才返回
          // 1. 异常
          if(n < 0) {
            std::cerr << "select error" << std::endl;
            exit(SELECT_ERROR);
          }
          // 2. 没有就绪的读事件
          else if(n == 0) {
            std::cout << "timeout" << std::endl;
          }
          // 3. 有读事件就绪
          else {
            for(int i = 0; i < FD_SETSIZE; i++) {
              if(_fd_array[i] == -1) {
                continue;
              }
              else {
                // 该文件描述符未就绪
                if(!FD_ISSET(_fd_array[i], &rfds)) {
                  continue;
                }
                // 该文件描述符就绪
                else {
                  // listen套接字的读事件就绪,有新的链接到来
                  if(_fd_array[i] == _listen_sock) {
                    sockaddr_in peer;
                    bzero(&peer, sizeof(peer));
                    socklen_t len = sizeof(peer);
                    int sock = accept(_listen_sock, (struct sockaddr*)&peer, &len);
                    // 不能读数据,因为不确定有没有数据
                    // 添加到第三方数组中等待下一轮循环
                    int index = 0;
                    for(; index < FD_SETSIZE; index++) {
                      if(_fd_array[index] == -1) {
                        _fd_array[index] = sock;
                        break;
                      }
                      else {
                        continue;
                      }
                    }
                    if(index == FD_SETSIZE) {
                      // 第三方数组用完了
                      std::cerr << "select管理的文件描述符超出限制" << std::endl;
                    }
                    else {
                      // 监听套接字的一个读事件处理完
                      std::cout << "sock " << sock << " 建立链接" << std::endl;
                    }
                  }
                  // 普通套接字的读事件就绪,有数据到来
                  else {
                    Handler(_fd_array[i], i);
                  }
                }
              }
            }
          }
        }
      }

    private:
      void Handler(int sock, int i) {
        char buffer[BUFFER_SIZE] = {0};
        ssize_t size = recv(sock, buffer, sizeof(buffer) - 1, 0);
        // 1. recv失败
        if(size < 0) {
          std::cerr << "recv data error" << std::endl;
        }
        // 2. 链接关闭
        else if(size == 0) {
          std::cout << "sock: " << sock << " closed link" << std::endl;
          // 读事件处理完,fd数组中置为-1
          _fd_array[i] = -1;
          // 关闭文件描述符
          close(sock); 
        }
        // 3. 正常读取数据
        else {
          buffer[size] = '\0';
          std::cout << "sock " << sock << "# " << buffer;;
        }
      }

    private:
      int _listen_sock;
      uint16_t _port;
      int _fd_array[FD_SETSIZE];
  };
}
  1. 主程序
#include "select_server.hpp"
int main()
{
  ns_select_server::SelectServer *srv = new ns_select_server::SelectServer;
  srv->Loop();
  return 0;
}

telnet测试

我们使用3台telnet客户端连接select服务器
在这里插入图片描述

github仓库地址

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-06-01 15:29:48  更:2022-06-01 15:30:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/18 17:03:36-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码