Linux网络编程(阻塞和非阻塞)

文章目录


一、阻塞和非阻塞的概念以及函数实现

阻塞与非阻塞的概念

**阻塞(Blocking)非阻塞(Non-Blocking)**是计算机编程中描述输入/输出(I/O)操作行为的两种方式。这两种方式主要影响程序如何处理I/O操作(如网络连接、文件读写等),特别是在涉及等待某些资源或数据的情况下。

阻塞模式(Blocking Mode)
  • 定义: 在阻塞模式下,当程序调用某个I/O操作时,程序会暂停执行,直到该操作完成。这意味着如果一个操作需要等待,比如网络连接建立、数据读取或写入、输入等待等,程序会一直等待,直到该操作完成后才继续执行后续代码。

  • 优点:

    • 简单易于理解和实现,因为程序逻辑是线性的。
    • 适合处理少量的I/O操作或处理周期较短的操作。
  • 缺点:

    • 如果I/O操作需要很长时间(例如等待网络响应),程序将被挂起,不能执行其他任务,可能导致程序响应不及时。
非阻塞模式(Non-Blocking Mode)
  • 定义: 在非阻塞模式下,I/O操作在无法立即完成时不会让程序等待,而是立即返回一个状态值,指示操作未完成。程序可以继续执行其他任务,并在适当的时候再次尝试完成该I/O操作。

  • 优点:

    • 提高了程序的并发性,允许程序在等待I/O完成时执行其他任务,适合处理高并发、大量I/O操作的场景。
    • 提高了程序的响应性,因为它不必等待单个I/O操作完成。
  • 缺点:

    • 实现起来相对复杂,因为需要管理和检查I/O操作的状态。
    • 需要额外的逻辑来处理未完成的I/O操作。

函数实现

为了实现阻塞和非阻塞模式,我们通常使用套接字编程中的 fcntl 函数来控制文件描述符(如套接字)的阻塞行为。

阻塞模式示例

在默认情况下,套接字是阻塞的。以下是一个典型的阻塞套接字的连接示例:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>

int main() {
    int sockfd;
    struct sockaddr_in server_addr;

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    // 阻塞模式下连接服务器
    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("connect");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    printf("Connected to the server in blocking mode.\n");

    // 发送和接收数据...

    close(sockfd);
    return 0;
}

在这个代码中,connect 是阻塞的,它会等待直到连接成功或失败后才返回。

非阻塞模式实现

要将套接字设置为非阻塞模式,可以使用 fcntl 函数:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/select.h>

int main() {
    int sockfd;
    struct sockaddr_in server_addr;

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 设置套接字为非阻塞模式
    int flags = fcntl(sockfd, F_GETFL, 0);
    if (flags < 0) {
        perror("fcntl(F_GETFL)");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) < 0) {
        perror("fcntl(F_SETFL)");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    // 非阻塞模式下尝试连接服务器
    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        if (errno != EINPROGRESS) {
            perror("connect");
            close(sockfd);
            exit(EXIT_FAILURE);
        }
        // EINPROGRESS 表示连接正在进行中
    }

    // 使用 select 等待连接完成
    fd_set write_fds;
    struct timeval tv;
    FD_ZERO(&write_fds);
    FD_SET(sockfd, &write_fds);
    tv.tv_sec = 5;  // 设置超时时间为5秒
    tv.tv_usec = 0;

    int ret = select(sockfd + 1, NULL, &write_fds, NULL, &tv);
    if (ret < 0) {
        perror("select");
        close(sockfd);
        exit(EXIT_FAILURE);
    } else if (ret == 0) {
        fprintf(stderr, "connect timeout\n");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 检查连接是否成功
    int err;
    socklen_t len = sizeof(err);
    if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
        perror("getsockopt");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    if (err != 0) {
        fprintf(stderr, "connect failed: %s\n", strerror(err));
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    printf("Connected to the server in non-blocking mode.\n");

    // 发送和接收数据...

    close(sockfd);
    return 0;
}

代码说明:

  1. 设置非阻塞模式

    • 使用 fcntl 函数设置套接字为非阻塞模式。
    • fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) 将套接字设置为非阻塞模式。
  2. 处理 connectEINPROGRESS 错误

    • 在非阻塞模式下,connect 可能会返回 -1,并将 errno 设置为 EINPROGRESS,表示连接正在进行中。这是预期的行为,程序不会因此中断。
  3. 使用 select 处理连接

    • 通过 select 函数等待套接字的写事件(连接成功)或超时。
    • 如果 select 指示套接字可写,则表示连接可能已成功。
  4. 检查连接状态

    • 使用 getsockopt 检查连接的实际状态,确保连接确实成功或失败。

阻塞与非阻塞的选择

  • 阻塞模式适合简单的程序和不需要处理大量并发连接的场景。
  • 非阻塞模式 适合需要同时处理多个 I/O 操作(如多个客户端连接)且希望程序能响应迅速的场景。结合 I/O 多路复用技术(如 selectpollepoll)可以显著提高程序的并发能力和响应速度。

二、#include <errno.h>头文件

<errno.h> 头文件是C标准库中的一个文件,用于定义与错误处理相关的宏、变量和函数。它提供了一种标准化的方式来报告和处理程序在运行时发生的各种错误。

<errno.h> 的主要内容

  1. errno 变量:

    • errno 是一个全局变量,用于存储最近一次系统调用或库函数调用出错时的错误码。当某个系统调用或库函数返回一个错误(通常是 -1NULL),errno 会被设置为一个表示特定错误类型的值。
    • 由于 errno 是全局变量,所以它可能在不同的系统调用之间被修改,因此在调用返回错误后,应该立即检查 errno 的值。
  2. 错误码宏:

    • <errno.h> 定义了一些标准错误码的宏,这些宏表示各种常见的错误类型。这些错误码通常是整数值,例如 EINTRENOMEMEINVAL 等。以下是一些常见的错误码:

      • EINTR (Interrupted function call): 函数调用在执行过程中被信号中断。
      • ENOMEM (Out of memory): 内存不足,无法分配请求的内存。
      • EINVAL (Invalid argument): 无效的参数传递给函数。
      • EAGAIN (Resource temporarily unavailable): 资源暂时不可用,通常需要稍后重试。
      • EBADF (Bad file descriptor): 无效的文件描述符。
      • EACCES (Permission denied): 权限被拒绝,通常是尝试访问被禁止的资源。
      • EFAULT (Bad address): 无效的内存地址传递给函数。
      • EIO (Input/output error): 输入/输出操作发生错误。
  3. perror 函数:

    • <errno.h> 还提供了 perror 函数,用于打印一条描述性错误消息,该消息与 errno 的当前值对应。perror 函数的格式是:

      c 复制代码
      void perror(const char *s);
    • perror 会在标准错误输出上打印 s 参数和一个描述性错误消息,消息与当前的 errno 值对应。这在调试程序时特别有用。

      例如:

      c 复制代码
      FILE *fp = fopen("nonexistent.txt", "r");
      if (fp == NULL) {
          perror("Error opening file");
      }

      如果文件打开失败,perror 会打印如下消息:

      复制代码
      Error opening file: No such file or directory
  4. strerror 函数:

    • <errno.h> 也提供了 strerror 函数,该函数返回一个指向描述错误的字符串的指针。strerror 的原型是:

      c 复制代码
      char *strerror(int errnum);
    • 例如:

      c 复制代码
      printf("Error: %s\n", strerror(errno));

      这个函数会返回一个字符串,描述与 errno 对应的错误。

使用 <errno.h> 的示例

以下是一个简单的使用 <errno.h> 的示例:

c 复制代码
#include <stdio.h>
#include <errno.h>
#include <string.h>

int main() {
    FILE *fp = fopen("nonexistent.txt", "r");

    if (fp == NULL) {
        // 打印错误码和对应的错误描述
        printf("Error code: %d\n", errno);
        printf("Error message: %s\n", strerror(errno));
        perror("Error occurred");
    }

    return 0;
}

输出:

复制代码
Error code: 2
Error message: No such file or directory
Error occurred: No such file or directory

在这个示例中,尝试打开一个不存在的文件会导致 fopen 失败,并且 errno 会被设置为相应的错误码(在这种情况下是 2,对应的错误消息是 "No such file or directory")。程序打印了错误码和对应的错误消息。

总结

<errno.h> 是一个非常有用的头文件,它提供了一种标准化的方式来处理程序运行时的错误。通过 errno 变量、错误码宏、以及 perrorstrerror 函数,程序员可以轻松地检测和报告错误,从而更好地调试和维护代码。

相关推荐
chlk1237 小时前
Linux文件权限完全图解:读懂 ls -l 和 chmod 755 背后的秘密
linux·操作系统
舒一笑7 小时前
Ubuntu系统安装CodeX出现问题
linux·后端
改一下配置文件8 小时前
Ubuntu24.04安装NVIDIA驱动完整指南(含Secure Boot解决方案)
linux
BingoGo10 小时前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack10 小时前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
深紫色的三北六号17 小时前
Linux 服务器磁盘扩容与目录迁移:rsync + bind mount 实现服务无感迁移(无需修改配置)
linux·扩容·服务迁移
SudosuBash21 小时前
[CS:APP 3e] 关于对 第 12 章 读/写者的一点思考和题解 (作业 12.19,12.20,12.21)
linux·并发·操作系统(os)
哈基咪怎么可能是AI1 天前
为什么我就想要「线性历史 + Signed Commits」GitHub 却把我当猴耍 🤬🎙️
linux·github
BingoGo1 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack1 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端