编程语言
首页 > 编程语言> > 《Unix 网络编程》14:高级 I/O 函数

《Unix 网络编程》14:高级 I/O 函数

作者:互联网

高级 I/O 函数

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★

系列文章导航:《Unix 网络编程》笔记

套接字超时

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★

SIGALRM

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★

connect

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
static void connect_alarm(int);

int connect_timeo(int sockfd, const SA* saptr, socklen_t salen, int nsec) {
    Sigfunc* sigfunc;
    int n;

    // Signal 是我们自己包装的函数,用来设置处理函数,返回原来的 old 处理函数
    sigfunc = Signal(SIGALRM, connect_alarm);

    // alarm 的返回值:
  	// 0 如果成功设置
  	// 如果已经设置过,返回剩余的时间
    if (alarm(nsec) != 0)
        err_msg("connect_timeo: alarm was already set");

  	// 如果被中断,所做的处理
    if ((n = connect(sockfd, saptr, salen)) < 0) {
        close(sockfd);
        if (errno == EINTR)
            errno = ETIMEDOUT;
    }

    // 关闭
    alarm(0);                 /* turn off the alarm */
    // 恢复原来的处理函数
    Signal(SIGALRM, sigfunc); /* restore previous signal handler */

    return (n);
}

static void connect_alarm(int signo) {
    return; /* just interrupt the connect() */
}

为什么可以打断 connect

可以参考前文的:可中断的系统调用

这一点有点像 Java 中的 InterruptedExecption 那一部分

这个函数的缺陷

connect 的超时通常为 75s ,我们只能指定一个更小的值,如果指定了一个更大的值,那么 connect 仍将在 75s 后发生超时

尽管本例子相当简单,但是在多线程中正确使用信号却非常困难,因此只建议在未线程化或单线程化的程序中使用本技术

recvfrom

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
static void sig_alrm(int);

void dg_cli(FILE* fp, int sockfd, const SA* pservaddr, socklen_t servlen) {
    int n;
    char sendline[MAXLINE], recvline[MAXLINE + 1];

    Signal(SIGALRM, sig_alrm);

    while (Fgets(sendline, MAXLINE, fp) != NULL) {
        Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

        alarm(5);
        if ((n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL)) < 0) {
            if (errno == EINTR)
                fprintf(stderr, "socket timeout\n");
            else
                err_sys("recvfrom error");
        } else {
            alarm(0);
            recvline[n] = 0; /* null terminate */
            Fputs(recvline, stdout);
        }
    }
}

static void sig_alrm(int signo) {
    return; /* just interrupt the recvfrom() */
}

这个代码还是比较清晰的,在 recvfrom 前设置 alarm

select

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
int readable_timeo(int fd, int sec) {
    fd_set rset;
    struct timeval tv;

    FD_ZERO(&rset);
    FD_SET(fd, &rset);

  	// 把 select 的时间设为用户传入的时间
    tv.tv_sec = sec;
    tv.tv_usec = 0;
		// 出错返回 -1
  	// 超时返回 0
  	// 否则返回已就绪的描述符的数目
    return (select(fd + 1, &rset, NULL, NULL, &tv));
    /* 4> 0 if descriptor is readable */
}

这个的源代码也比较清晰,具体在注释中就体现了

下面是一个使用的案例:

void dg_cli(FILE* fp, int sockfd, const SA* pservaddr, socklen_t servlen) {
    int n;
    char sendline[MAXLINE], recvline[MAXLINE + 1];

    while (Fgets(sendline, MAXLINE, fp) != NULL) {
        Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

        if (Readable_timeo(sockfd, 5) == 0) {
            fprintf(stderr, "socket timeout\n");
        } else {
            n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
            recvline[n] = 0; /* null terminate */
            Fputs(recvline, stdout);
        }
    }
}

套接字选项

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★

套接字选项一旦设置到某个描述符,其超时设置将应用于该描述符的所有读操作

void dg_cli(FILE* fp, int sockfd, const SA* pservaddr, socklen_t servlen) {
    int n;
    char sendline[MAXLINE], recvline[MAXLINE + 1];
    struct timeval tv;

    tv.tv_sec = 5;
    tv.tv_usec = 0;
    Setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

    while (Fgets(sendline, MAXLINE, fp) != NULL) {
        Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

        n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
        if (n < 0) {
            // 如果超时,发生 E.WOULD.BLOCK 错误
            if (errno == EWOULDBLOCK) {
                fprintf(stderr, "socket timeout\n");
                continue;
            } else
                err_sys("recvfrom error");
        }

        recvline[n] = 0; /* null terminate */
        Fputs(recvline, stdout);
    }
}

消息处理的函数变体

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★

recv 和 send

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★

类似标准的 recv 和 send 函数,不过需要一个额外的 flags 参数:

#include <sys/socket.h>

ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);
ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);

flag 的具体说明:

flags 的问题

readv 和 writev

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★

read 和 write 的缺点:

  • 使用read()将数据读到不连续的内存、使用write()将不连续的内存发送出去,要经过多次的调用read、write

  • 如果要从文件中读一片连续的数据至进程的不同区域,有两种方案:

    • 使用read()一次将它们读至一个较大的缓冲区中,然后将它们分成若干部分复制到不同的区域

    • 调用read()若干次分批将它们读至不同区域

    • 同样,如果想将程序中不同区域的数据块连续地写至文件,也必须进行类似的处理

怎么解决多次系统调用+拷贝带来的开销呢

UNIX提供了另外两个函数—readv()和writev(),它们只需一次系统调用就可以实现在文件和进程的多个缓冲区之间传送数据,免除了多次系统调用或复制数据的开销[1]

这两个函数类似 readwrite ,但是允许单个系统调用读取或写出到一个或多个缓冲区,即:分散读集中写

#include <sys/uio.h>

ssize_t readv(int filedes, // 读到哪里
              const struct iovec *iov, // 数组指针,从哪里读
              int iovcnt); // 数量
ssize_t writev(int filedes, // 从哪里读
               const struct iovec *iov, // 写到哪里
               int iovcnt);

// 其中 struct iovec:
struct iovec {
  void *iov_base; // 开始的地址
  size_t iov_len; // 长度
}

recvmsg 和 sendmsg

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★

这两个函数是最通用的 IO 函数,实际上我们可以把所有 read、readv、recv、recvfrom 调用替换成 recvmsgsendmsg 也类似。

#include <sys/socket.h>

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags);

这两个函数把大部分参数封装到了 msghdr 结构中:

struct msghdr {
  // 如下两个参数用于套接字未连接的场景
  void *msg_name;		// 指向一个套接字地址结构,无需指明则置空
  socklen_t msg_namelen;  // 存放长度(对sendmsg来说是一个值参数,对recvmsg来说是值-结果参数)
  
  // 如下两个参数指定输入或输出缓冲区数组
  struct iovec *msg_iov;
  int msg_iovlen;
  
  // 如下两个参数指定可选的辅助数据的位置和大小
  void *msg_control; 
  socklen_t msg_controllen; // (对recvmsg来说是值-结果参数)
  
  // 只有 recvmsg 使用这个成员,sendmsg 只使用外面的那个 flags 参数
  // recvmsg 调用时,flags 参数被复制到 msg_flags 中,并由内核更新其值
  int msg_flags; 
}

flags 的不同位置的不同设置

这里再记录一下后面 7 个的作用:

消息处理函数的对比

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★

辅助数据

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★

辅助数据(ancillary data)可通过调用 sendmsg 和 recvmsg 这两个函数,使用 msghdr 结构中的 msg_control 和 msg_controllen 这两个成员发送和接收。辅助数据的另一个称谓是控制信息(control information)。

关于辅助数据的基本使用,如结构、填充、传递、处理宏可以参考如下文章:

关于辅助数据的具体使用案例在之后的文章中会提到

排队的数据量

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★

即在不真正读取数据的前提下直到一个套接字上已经有多少数据排队等着读取

有三种方法:

  1. 如果获悉已排队数据量的目的在于避免读操作阻塞在内核中,即没有数据可读时能做其他事情。这种情况值仅需要知道是否有数据而关心具体排队数量时,可以使用非阻塞IO

  2. 既想查看数据,又想数据留在接收队列以供进程中其他业务部分稍后读取,可以使用MSG_PEEK标志

    • 注意,设定 flags 不能肯定是否真有数据可读,可以结合非阻塞套接字使用,也可以组合使用MSG_DONTWAIT标志
    • 另外,对于流式套接字TCP和数据报套接字UDP,先后两次调用recv的结果有一定差异:
      • TCP 可能在两次调用之间又接收到了新的数据,所以可能不一致
      • UDP 则会完全相同
  3. 目前有些实现支持 ioctl 的 FIONREAD 命令,第三个参数是一个指向某整数的指针,用于接收内核返回当前套接字接收队列中可读的数据

原书这部分并没有给出代码,但是可以参考 网络编程(13)高级IO函数 (2)排队的数据量 作进一步了解

套接字和标准 IO

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★

介绍

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★

到目前为止的例子,我们一直使用 Unix I/O 的函数执行 IO 操作;这些函数围绕描述符工作,通常作为 Unix 内核中的系统调用实现。

执行 IO 的另一个方法是使用标准 I/O 函数库,这个函数库由 ANSI C 标准规范,意在便于移植到支持 ANSI C 的非 Unix 系统上。

标准 IO 处理我们直接使用 Unix I/O 函数时必须考虑的一些问题,比如自动缓冲输入流和输出流,不幸的是,这导致我们可能需要解决一些其他的问题。

问题

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★

由于标准 IO 函数库中缓冲的存在,数据可能不会被立刻打印出来;

标准 IO 函数库的三种缓冲和输出的条件:

  1. 完全缓冲:缓冲区满、进程调用 flush、进程 exit 自己
  2. 行缓冲:碰到换行符、进程调用 fflush、进程 exit 自己
  3. 不缓冲:每次调用都发生 I/O

标准 IO 函数库的大多数 Unix 实现使用如下原则:

在网络编程中的实践:

由于套接字不是终端设备,因此是完全缓冲

如果想实现 echo 函数的效果,可以:

  1. 调用 setvbuf 迫使输出流变为行缓冲
  2. 每次调用 fputs 后调用 fflush 强制输出

但是这两个办法都容易犯错,并且与 Nagle 算法的交互也是问题

所以:

高级轮询技术

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★

他们都具备 select 和 poll 两个函数的特性

另外,这里提到的机制和代码应被认为是不可移植的

/dev/poll 接口

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★

Solaris 上名为 /dev/poll 的特殊文件提供了一个可扩展的轮询大量描述符的方法

优势:

使用步骤:

  1. 打开 /dev/poll

  2. 初始化 pollfd 结构数组

  3. 调用 write 往 /dev/poll 设备上写这个结构数组以把它传递给内核

  4. 执行 ioctlDP_POLL 命令阻塞自身以等待事件的发生

    传递给 ioctl 的 dvpoll 结构如下:

    struct dvpoll {
      struct pollfd* dp_fds; // 指向一个缓冲区,
      int dp_nfds; // 缓冲区的大小
      int dp_timeout; // 超时事件,或 -1 表示无
    }
    

代码:

void str_cli(FILE* fp, int sockfd) {
    int stdineof;
    char buf[MAXLINE];
    int n;
    int wfd;
    struct pollfd pollfd[2];
    struct dvpoll dopoll;
    int i;
    int result;

    // 打开 /dev/poll
    wfd = Open("/dev/poll", O_RDWR, 0);

  	// 初始化数组结构
    pollfd[0].fd = fileno(fp);
    pollfd[0].events = POLLIN;
    pollfd[0].revents = 0;

    pollfd[1].fd = sockfd;
    pollfd[1].events = POLLIN;
    pollfd[1].revents = 0;

  	// 调用 write 往 `/dev/poll` 设备上写这个结构数组以把它传递给内核
    Write(wfd, pollfd, sizeof(struct pollfd) * 2);

    stdineof = 0;
    for (;;) {
        // 执行 `ioctl` 的 `DP_POLL` 命令阻塞自身以等待事件的发生
        dopoll.dp_timeout = -1;
        dopoll.dp_nfds = 2;
        dopoll.dp_fds = pollfd;
        result = Ioctl(wfd, DP_POLL, &dopoll);

        /* loop through ready file descriptors */
        for (i = 0; i < result; i++) {
            if (dopoll.dp_fds[i].fd == sockfd) {
                /* socket is readable */
                if ((n = Read(sockfd, buf, MAXLINE)) == 0) {
                    if (stdineof == 1)
                        return; /* normal termination */
                    else
                        err_quit("str_cli: server terminated prematurely");
                }

                Write(fileno(stdout), buf, n);
            } else {
                /* input is readable */
                if ((n = Read(fileno(fp), buf, MAXLINE)) == 0) {
                    stdineof = 1;
                    Shutdown(sockfd, SHUT_WR); /* send FIN */
                    continue;
                }

                Writen(sockfd, buf, n);
            }
        }
    }
}

kqueue 接口

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★

简介:

kueue 是在 UNIX 上比较高效的 IO 复用技术

常见的 IO 复用技术有 select, poll, epoll 以及 kqueue 等等

其中 epoll 为 Linux 独占,而 kqueue 则在许多 UNIX 系统上存在,包括 macOS

使用步骤:

  1. 调用 kqueue() ,返回一个 kqueue 描述符
  2. struct kevent changes[FD_NUM] 中保存要监视的事件列表
  3. 调用 EV_SET 将上一步的事件进行注册
  4. 进行 kevent() 调用,如果 changes 中有任何发生了变化,就保存在 struct kevents events[FD_NUM]
  5. 循环遍历并处理

可以参考这篇文章 kqueue用法简介 - luMinO - 博客园

T/TCP:事务目的 TCP

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》14:高级 I/O 函数《Unix 网络编程》14:高级 I/O 函数
原文地址https://www.cnblogs.com/lymtics/p/16350621.htmlhttps://www.cnblogs.com/lymtics/p/16350621.html
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★

T/TCP 是对 TCP 进行过略微修改的一个版本,所谓“事务”是客户的请求与服务器的应答,常见的事务如 DNS 请求与服务器的应答、HTTP 请求与服务器的应答等。

如果客户和服务器最近通过三次握手建立过连接,且都没有崩溃重启过,各自告诉缓存的一些信息都没有过时则他们能够避免彼此通信过的主机之间的三次握手。它能把 SYN、FIN 和数据组合到单个分节中,前提是数据的大小小于 MSS。

如下是一个案例:

可以看到不仅在网络中传输的分节有所减少(T/TCP 需要 3 个,TCP 需要 10 个,UDP 需要 2 个),而且客户从初始化到发送一个请求再到读取相应应答所花费的时间也减少了一个 RTT。

T/TCP 的优势:

T/TCP 的优势在于 TCP 的所有可靠性(序列号、超时、重传)以及慢启动和拥塞避免得以保留,而不像 UDP 那样把可靠性推给应用程序去实现。

T/TCP 的问题:

这只是一种试验性的协议。因为存在安全性问题,并没有成为标准,也没有被应用。[2]

在原书的第三版中已经删除,但是译者把这一部分又加上了


  1. https://blog.csdn.net/weixin_36750623/article/details/84579243 ↩︎

  2. https://baike.baidu.com/item/T%2FTCP/1647857 ↩︎

标签:https,14,top,编程,Unix,lymtics,原文
来源: https://www.cnblogs.com/lymtics/p/16350621.html