首页 > 其他分享 >【技术学习】网络学习--使用select的IO多路复用的ftp服务器

【技术学习】网络学习--使用select的IO多路复用的ftp服务器

时间:2023-06-19 21:22:22浏览次数:44  
标签:ftp -- listenfd int 描述符 fd IO include select

上一篇文章复习了一下最基础的服务器代码,这次再将代码改为io多路复用的方式。

select函数是一种用于实现I/O多路复用的系统调用。它可以监视多个文件描述符,判断它们是否处于可读、可写或异常等事件状态,并在一个或多个文件描述符就绪时进行处理。

这种方式避免了使用多线程或多进程来同时处理多个文件描述符的大量系统开销,不必创建进程/线程,也不必维护这些进程/线程,提高了程序的效率。

当然这种方式也有缺点,当文件描述符过多的时候,select的遍历整个文件描述符集合操作就显得比较麻烦了。

#include <sys/select.h>
#include <sys/time.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

第一个参数需要用最大文件描述符再加一,确保监听返回,然后是读、写、异常文件描述符集合,超时时间(如果为NULL则为阻塞等待)。

在使用select之前,首先要创建一个文件描述符的集合,将其初始化,,还要把listenfd放入这个集合。创建一个最大文件描述符,直接指向监听描述符。

fd_set rfds;
FD_ZERO(&rfds); // 初始化集合
FD_SET(listenfd, &rfds); // 将监听套接字加入集合中
int max_fd = listenfd;

然后写while循环里的select

while (1)
{
    fd_set rset = rfds; //通过这个赋值操作,可以将所有在 allfds 中的文件描述符设置为在 readfds 中处于就绪状态。这样,在调用 select 函数时,只需要监视 readfds 集合,即可判断其中哪些文件描述符已经准备好进行读取操作。
    int fd_num = select(max_fd+1, &rset, NULL, NULL, NULL);
    if (fd_num == -1) 
    {
        std::cerr << "Error in select." << std::endl;
        return -1;
    }
}

接下来去判断listenfd是否就绪,如果就绪,就代表监听到连接请求了,我们就accept接收它,并且将新建的connfd放入rfds集合之中。并调整max_fd的位置

if (FD_ISSET(listenfd, &rset)){

       struct sockaddr_in client;
        socklen_t len = sizeof(client);
        // 接受连接请求
        connfd = accept(listenfd, (struct sockaddr *)&client, &len);
        if (connfd < 0) {
            std::cerr << "Error in accepting connection." << std::endl;
            return -1;
        }

        FD_SET(connfd, &rfds);

        if (connfd > max_fd){
            max_fd = connfd; //更新最大文件描述符
        }

        if (--fd_num == 0) { 
            continue; //如果当前计数器为最后一个,则表示所有文件描述符已经处理完毕,可以结束当前的循环,进入下一轮循环。
        } 
    }

然后就是读写监听操作,循环去检查所有读操作。

for (i = listenfd+1;i <= max_fd;i ++)  {
            if (FD_ISSET(i, &rset)) {
                // 读取客户端发来的数据
                bzero(buffer, sizeof(buffer));
                ret = recv(i, buffer, sizeof(buffer), 0);
                if (ret < 0) {
                    std::cerr << "Error in reading data." << std::endl;
                    FD_CLR(i, &rfds); //从集合中删除该文件描述符
                    close(i); //关闭连接套接字
                    continue;
                }
                else if(ret == 0){
                    FD_CLR(i, &rfds);
                    // 关闭连接套接字
                    close(i);
                }
                else{
                    // 输出客户端发送的消息
                    std::cout << "Client message: " << buffer << std::endl;

                    // 发送响应给客户端
                    const char *response = "Hello from server!";
                    if (send(i, response, strlen(response), 0) < 0) {
                        std::cerr << "Error in sending response." << std::endl;
                        FD_CLR(i, &rfds); //从集合中删除该文件描述符
                        close(i); //关闭连接套接字
                        continue;
                    }
                }
                
                if (--fd_num == 0) { 
                    break; //如果当前计数器为最后一个,则表示所有文件描述符已经处理完毕,可以结束for循环。
                } 
            }
        }

这样就解决了一次请求就开一次线程的系统消耗,所有的套接字都放在一起管理,我们可以把select理解成酒店大堂经理,合理的管理着所有顾客需求

整体代码如下,

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>

#define MAXLNE  4096

int main() {
    int listenfd, connfd;
    struct sockaddr_in servaddr{};
    char buffer[MAXLNE]{};
    int ret = 0;

    // 创建套接字
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1) {
        std::cerr << "Failed to create socket." << std::endl;
        return -1;
    }

    // 设置服务器地址
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(8080); // 指定端口号(这里使用8080)
    servaddr.sin_addr.s_addr = INADDR_ANY;

    // 绑定套接字到指定地址和端口
    if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        std::cerr << "Failed to bind socket." << std::endl;
        return -1;
    }

    // 监听连接请求
    if (listen(listenfd, 5) < 0) { // 允许同时处理最多5个连接请求
        std::cerr << "Error in listening." << std::endl;
        return -1;
    }

    std::cout << "Server started. Listening for incoming connections..." << std::endl;

    fd_set rfds;
    FD_ZERO(&rfds); // 初始化集合
    FD_SET(listenfd, &rfds); // 将监听套接字加入集合中
    int max_fd = listenfd;

    while (1){
        fd_set rset = rfds; //通过这个赋值操作,可以将所有在 allfds 中的文件描述符设置为在 readfds 中处于就绪状态。这样,在调用 select 函数时,只需要监视 readfds 集合,即可判断其中哪些文件描述符已经准备好进行读取操作。
        int fd_num = select(max_fd+1, &rset, NULL, NULL, NULL);
        if (fd_num == -1){
            std::cerr << "Error in select." << std::endl;
            return -1;
        }
    
        if (FD_ISSET(listenfd, &rset)){
            
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            // 接受连接请求
            connfd = accept(listenfd, (struct sockaddr *)&client, &len);
            if (connfd < 0) {
                std::cerr << "Error in accepting connection." << std::endl;
                return -1;
            }

            FD_SET(connfd, &rfds);

            if (connfd > max_fd){
                max_fd = connfd; //更新最大文件描述符
            }

            if (--fd_num == 0) { 
                continue; //如果当前计数器为最后一个,则表示所有文件描述符已经处理完毕,可以结束当前的循环,进入下一轮循环。
            } 
        }
    
        int i = 0;
        for (i = listenfd+1;i <= max_fd;i ++)  {
            if (FD_ISSET(i, &rset)) {
                // 读取客户端发来的数据
                bzero(buffer, sizeof(buffer));
                ret = recv(i, buffer, sizeof(buffer), 0);
                if (ret < 0) {
                    std::cerr << "Error in reading data." << std::endl;
                    FD_CLR(i, &rfds); //从集合中删除该文件描述符
                    close(i); //关闭连接套接字
                    continue;
                }
                else if(ret == 0){
                    FD_CLR(i, &rfds);
                    // 关闭连接套接字
                    close(i);
                }
                else{
                    // 输出客户端发送的消息
                    std::cout << "Client message: " << buffer << std::endl;

                    // 发送响应给客户端
                    const char *response = "Hello from server!";
                    if (send(i, response, strlen(response), 0) < 0) {
                        std::cerr << "Error in sending response." << std::endl;
                        FD_CLR(i, &rfds); //从集合中删除该文件描述符
                        close(i); //关闭连接套接字
                        continue;
                    }
                }
                
                if (--fd_num == 0) { 
                    break; //如果当前计数器为最后一个,则表示所有文件描述符已经处理完毕,可以结束for循环。
                } 
            }
        }
    }

    // 关闭服务器套接字
    close(listenfd);

    return 0;
}

 

标签:ftp,--,listenfd,int,描述符,fd,IO,include,select
From: https://www.cnblogs.com/templeD/p/17492217.html

相关文章

  • Lua 文件
    Lua文件I/Olua常用的就是内存操作,和redis,mysql,kafka中间件打通。LuaI/O库用于读取和处理文件。分为简单模式(和C一样)、完全模式。简单模式(simplemodel)拥有一个当前输入文件和一个当前输出文件,并且提供针对这些文件相关的操作。完全模式(completemodel)使用外部的文件句......
  • 基于XC7Z100+OV5640(DSP接口)YOLO人脸识别模块编写思路(部分2)
    实现分批卷积计算的累加模块分批卷积计算:指的是将卷积层的输入通道或输出通道分成若干个批次,每次只计算一部分通道的卷积,然后将所有批次的结果累加起来,得到最终的卷积输出。这样做的目的是为了减少计算资源的消耗,提高运算效率。累加模块:指的是用于缓存和累加分批卷积计算的中间......
  • 复习高中数学 极坐标
    1.定义2.极坐标与直角坐标的关系3.几种特殊情况的极坐标方程其他一般情况代入公式进行转换即可。......
  • Lua 元表
    Lua元表(Metatable)在Luatable中我们可以访问对应的key来得到value值,但是却无法对两个table进行操作(比如相加)。因此Lua提供了元表(Metatable),允许我们改变table的行为,每个行为关联了对应的元方法。例如,使用元表我们可以定义Lua如何计算两个table的相加操作......
  • Lua 协同程序
    Lua协同程序(coroutine)目前来说基本用不到,暂时记录什么是协同(coroutine)?Lua协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。协同是非常强大的功能,但是用起来也很复杂。线程和协同程......
  • 网络协议与编程
    基本概念题:套接字:套接字是网络传输传输用的软件设备。协议:为了完成数据交换而定好的规则。Linux的文件描述符:是为了区分指定文件而赋予文件的整数值。面向连接的套接字传输特性有3点传输过程中数据不会丢失,按序传输数据,传输的数据不存在数据边界(Boundary)面向消息的套接字的特性......
  • BOLG-3
    (除题目和代码外,本次blog共计3350字)一、前言此次blog是关于pta作业6-8的总结,将主要分析课程成绩统计程序1-3。课程成绩统计程序1是这几次作业的基础,主要实现了功能读入课程信息和成绩信息,将其按照不同的类型存储(考试、考察、实验)。遍历成绩信息,计算每个学生的总成绩和每门课程的......
  • VirtualBox启动报错 E_FAIL (0x80004005) SessionMachine
    问题描述:每次卸载重装virtualbox后的第一次启动是成功的,之后就又报错。0x80004005报错解决方案:1、可以尝试使用“管理员身份”运行virtualbox,再打开虚拟机。2、可以尝试先“导出虚拟机”,然后再“导入虚拟机”。问题原因分析:可能是安装镜像系统时的virtualbox版本和后来新装......
  • python基础 | python中为什么没有自增运算符?
    学过Java或者C语言的同学在使用python时发现之前很方便的自增运算在python中无法使用,要想弄清楚这个问题,首先需要明白什么是自增运算符?自增运算符:自增运算符的作用是在运算结束前将变量的值加1。自增运算符一般存在于C/C++/C#/Java等高级语言中。自增运算是在该数字原来的内存地......
  • BUUCTF:[极客大挑战 2019]BabySQL
    题目地址:https://buuoj.cn/challenges#[%E6%9E%81%E5%AE%A2%E5%A4%A7%E6%8C%91%E6%88%98%202019]BabySQL简单测试之后发现有些字符被过滤,初步判断这里的过滤是指特殊字符被替换为空,如下图所示使用Burp进行SQL过滤字符的Fuzz这些长度为726响应内容是Inputyourusernameandpass......