首页 > 其他分享 >文件描述符(File Descriptor, FD)和 poll 函数简介

文件描述符(File Descriptor, FD)和 poll 函数简介

时间:2024-03-18 11:59:04浏览次数:26  
标签:文件 fd Descriptor 描述符 client FD File let poll

文件描述符(File Descriptor, FD)是Unix和类Unix操作系统中用于标识进程打开的文件、设备或其他I/O资源的一个抽象概念。它是一个非负整数,由内核在进程打开或创建一个文件时分配给该进程。

当应用程序通过系统调用如open()socket()等操作打开一个现有文件、创建新文件或者创建网络套接字时,内核返回一个文件描述符。这个描述符实际上是一个指向内核维护的文件表结构的索引,该结构记录了与该文件相关的状态信息,例如当前读写位置、权限模式以及底层的数据结构指针。

程序后续可以通过这个文件描述符来执行对相关资源的各种操作,如读取数据read(),写入数据write(),关闭文件close(),或是获取或改变其属性等。由于文件描述符是对实际资源的一种引用,因此它允许程序员以统一的方式处理不同类型的数据源,而不仅仅是普通文件。在多进程或多线程编程中,每个进程或线程都有独立的文件描述符表,使得它们可以各自访问不同的文件资源而不互相干扰。

poll是一个Unix系统调用,用于检测一组文件描述符上的事件。它允许程序在多个文件描述符上等待事件,而无需阻塞主线程。poll函数可以用于监视文件描述符上的读写事件,以及错误和挂起的连接等事件。 在使用poll函数时,需要提供一个包含要监视的文件描述符的列表和一个指定要监视的事件类型的掩码。当调用poll函数时,它会阻塞当前线程,直到至少有一个文件描述符上发生了指定的事件,或者超时时间到达。poll函数在多路复用I/O中非常有用,因为它可以监视多个文件描述符,而无需阻塞主线程。这使得程序能够更高效地处理多个连接或事件,而无需创建多个线程或进程。

总结一下:

在Unix-like系统中,文件描述符(FileDescriptor,简称FD)是用于标识和访问打开的文件、设备、套接字等资源的整数。在进行网络编程时,套接字也被视为一种文件,因此套接字也有对应的文件描述符。

poll函数是用于检测多个文件描述符上的事件是否发生的系统调用。它允许程序同时监控多个套接字,以避免阻塞在单个套接字上。poll函数可以用于检测多种类型的事件,例如读就绪、写就绪、错误和挂起的连接等。

下面是一个使用poll函数来监控客户端套接字的示例代码:

use libc::{c_int, pollfd, POLLIN, POLLOUT, POLLERR, POLLHUP};
use std::io::ErrorKind;
use std::net::SocketAddr;
use std::str::from_utf8;
use std::thread;

const TIMEOUT_MS: i64 = 1000;

struct Client {
    fd: c_int,
    addr: SocketAddr,
}

impl Client {
    fn new(fd: c_int, addr: SocketAddr) -> Self {
        Self { fd, addr }
    }
}

fn handle_client(client: Client) {
    let mut buffer = [0; 1024];
    let pollfds = [
        pollfd {
            fd: client.fd,
            events: POLLIN | POLLOUT | POLLERR | POLLHUP,
            revents: 0,
        },
    ];

    loop {
        let timeout = Some(TIMEOUT_MS);
        let ret = unsafe { poll(&mut pollfds[..], timeout) };
        if ret == -1 {
            println!("poll failed: {}", ErrorKind::Other);
            break;
        } else if ret == 0 {
            println!("poll timeout");
            break;
        }

        if pollfds[0].revents & POLLIN != 0 {
            let len = unsafe { recv(client.fd, &mut buffer, buffer.len(), 0) };
            if len == -1 {
                println!("recv failed: {}", ErrorKind::Other);
                break;
            } else if len == 0 {
                println!("client disconnected");
                break;
            } else {
                let message = from_utf8(&buffer).unwrap();
                println!("Received message from {}: {}", client.addr, message);
            }
        }

        if pollfds[0].revents & POLLOUT != 0 {
            // Send a response to the client
            let response = "Hello, client!";
            let len = unsafe { send(client.fd, response.as_bytes(), response.len(), 0) };
            if len == -1 {
                println!("send failed: {}", ErrorKind::Other);
                break;
            }
        }

        if pollfds[0].revents & (POLLERR | POLLHUP) != 0 {
            println!("poll error or hangup");
            break;
        }
    }

    // Close the client socket
    unsafe {
        close(client.fd);
    }
}

fn main() {
    // Bind a server socket to a local address
    let addr = SocketAddr::from(([0, 0, 0, 0], 8080));
    let listener = std::net::TcpListener::bind(addr).unwrap();
    let mut incoming = listener.incoming();

    while let Some(stream) = incoming.next().unwrap() {
        let stream = stream.unwrap();
        let addr = stream.local_addr().unwrap();
        let client = Client::new(stream.as_raw_fd(), addr);
        thread::spawn(|| {
            handle_client(client);
        });
    }
}

在上面的示例代码中,我们首先创建了一个Client结构体,用于表示一个客户端连接。Client结构体包含了一个文件描述符fd和一个套接字地址addr

handle_client函数用于处理来自客户端的消息。在该函数中,我们首先定义了一个pollfd结构体数组,用于监控客户端套接字上的事件。pollfd结构体包含了一个文件描述符fd、一个监控的事件集合events和一个返回的事件集合revents。我们监控的事件集合包含了读就绪POLLIN、写就绪POLLOUT、错误POLLERR和挂起的连接POLLHUP等事件。

然后,我们调用poll函数来检测事件的发生。如果poll函数返回错误,我们打印错误信息并退出循环。如果poll函数超时,我们也退出循环。如果poll函数返回有事件发生,我们检查返回的事件集合中是否包含读就绪事件POLLIN,如果是,则调用recv函数来读取客户端发送的消息,并打印该消息。如果返回的事件集合中包含写就绪事件POLLOUT,则发送一条欢迎消息给客户端。如果返回的事件集合中包含错误事件POLLERR或挂起的连接事件POLLHUP,我们也退出循环。

最后,我们关闭客户端套接字。

main函数中,我们创建了一个TCP监听器,并将其绑定到本地地址。然后,我们使用incoming方法获取一个可迭代的客户端连接流,并在每个连接上创建一个新的线程来处理该连接。

标签:文件,fd,Descriptor,描述符,client,FD,File,let,poll
From: https://blog.csdn.net/weixin_40482577/article/details/136765364

相关文章

  • 编译器有关的Makefile语法
    在Makefile中,与编译器相关的语法通常用于定义编译规则和链接规则,以及设置编译器选项等。编译器相关的Makefile语法定义变量CFLAGS=-Wall-O2编译规则%.o:%.c$(CC)$(CFLAGS)-c$<-o$@链接规则program:file1.ofile2.o$(CC)$^-o$@......
  • Makefile学习(二)
    参看文档:《跟我一起写Makefile》Makefile中常用的自动化变量:$@:表示规则中的目标文件名。$<:表示规则中的第一个依赖文件名。$^:表示规则中的所有依赖文件列表,以空格分隔并去重。$?:表示规则中所有比目标文件更新的依赖文件列表。$*:表示不包括扩展名的目标文件名。$(@D):表示......
  • TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for xx.ts
    TypeError[ERR_UNKNOWN_FILE_EXTENSION]:Unknownfileextension".ts"假如你在编写一个Typescript库函数,你希望将其编译为ESModule,那么你可以通过在package.json中声明"type":"module"来告诉使用者你的库函数使用的模块规范是ESModule。但如果你使用ts-node来运......
  • Makefile学习(一)
    参看文档:《跟我一起写Makefile》Makefile的基本规则:target...:prerequisites...command......target也就是一个目标文件,可以是ObjectFile,也可以是执行文件。还可以是一个标签(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。prerequisites......
  • vim,gcc,gdb与Makefile的使用
    一、Linux编辑器-vim使用1.vim的基本概念vim的三种模式(其实有好多模式,目前掌握这3种即可),分别是命令模式(commandmode)、插入模式(Insertmode)和底行模式(lastlinemode),各模式的功能区分如下(1)正常/普通/命令模式(Normalmode)控制屏幕光标的移动,字符、字或行的删除,移动复制......
  • 安装"DESeq2", "edgeR", "limma", "clusterProfiler"几个R包
    安装"DESeq2","edgeR","limma","clusterProfiler"bioPackages=c("DESeq2","edgeR","limma","clusterProfiler")#设置镜像local({r<-getOption("repos");r["......
  • Linux开发:通过sendfile高效的拷贝文件数据
    如果想要将一个文件的内容拷贝到另一个文件中,常规的做法是读取源文件,然后再把内容写入到目的文件中:#include<fstream>#include<iostream>#include<string>#include<vector>usingnamespacestd;vector<string>readFile(conststring&filename){vector<stri......
  • FireDAC中FDQuery1中SQL语句中的参数使用
    假设数据库已正常连接双击FDQuery1,SQL语句中以冒号开头就是参数,后面就是参数名 然后第二Parameters页,左边列表就有就该参数名,然后给参数的DataType,Value值,再点Execute,就可看到查询结果。 其后将上面的界面,变成代码实现即可procedureTForm13.Button1Click(Sende......
  • Makefile
    一、什么是Makefile?描述了整个工程的编译、链接规则。当源码文件比较多的时候就不适合通过输入gcc命令来编译,Makefile文件描述了编译哪些源码文件、如何编译,每次需要编译工程时只需要使用这个文件就行了。注意:Makefile中空出来的部分用Tab键,不能用空格;注释用“#”,不能用......
  • 滴水逆向笔记系列-PE总结2-25.FileBuffer-ImageBuffer-26.代码节空白区添加代码-27.新
    第二十五课FileBuffer-ImageBuffer1.PE文件执行的总过程第二十三课已经说过了,文件先复制一份读入虚拟内存中(FileBuffer),接着要运行时将FileBuffer中的文件数据拉伸,重载到4GB的虚拟内存中(ImageBuffer)但ImageBuffer还不是文件运行时在内存的真正状态,ImageBuffer还没表示文件已......