《TCP IP网络编程》第十三章

这篇具有很好参考价值的文章主要介绍了《TCP IP网络编程》第十三章。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

第 13 章 多种 I/O 函数

13.1 send & recv 函数

Linux 中的 send & recv:

         send 函数定义:

#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
/*
成功时返回发送的字节数,失败时返回 -1
sockfd: 表示与数据传输对象的连接的套接字和文件描述符
buf: 保存待传输数据的缓冲地址值
nbytes: 待传输字节数
flags: 传输数据时指定的可选项信息
*/

        recv 函数的定义:

#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
/*
成功时返回接收的字节数(收到 EOF 返回 0),失败时返回 -1
sockfd: 表示数据接受对象的连接的套接字文件描述符
buf: 保存接受数据的缓冲地址值
nbytes: 可接收的最大字节数
flags: 接收数据时指定的可选项参数
*/

        send 和 recv 函数的最后一个参数是收发数据的可选项,该选项可以用位或(bit OR)运算符(| 运算符)同时传递多个信息。send & recv 函数的可选项意义:

《TCP IP网络编程》第十三章,《TCPIP网络编程》,网络,tcp/ip,服务器,网络编程

MSG_OOB:发送紧急消息 :

        MSG_OOB 可选项用于创建特殊发送方法和通道以发送紧急消息。下面为 MSG_OOB 的示例代码:

recv:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>

#define BUF_SIZE 30
void error_handling(char *message);
void urg_handler(int signo);

int acpt_sock;
int recv_sock;

int main(int argc, char *argv[])
{
    struct sockaddr_in recv_adr, serv_adr;
    int str_len, state;
    socklen_t serv_adr_sz;
    struct sigaction act;
    char buf[BUF_SIZE];
    if (argc != 2)
    {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }
    act.sa_handler = urg_handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

    acpt_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&recv_adr, 0, sizeof(recv_adr));
    recv_adr.sin_family = AF_INET;
    recv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    recv_adr.sin_port = htons(atoi(argv[1]));

    if (bind(acpt_sock, (struct sockaddr *)&recv_adr, sizeof(recv_adr)) == -1)
        error_handling("bind() error");
    listen(acpt_sock, 5);

    serv_adr_sz = sizeof(serv_adr);
    recv_sock = accept(acpt_sock, (struct sockaddr *)&serv_adr, &serv_adr_sz);
    //文件描述符 recv_sock 指向的套接字引发的 SIGURG 信号处理进程变为 getpid 函数返回值用作 ID 进程.
    fcntl(recv_sock, F_SETOWN, getpid());
    state = sigaction(SIGURG, &act, 0); //SIGURG 是一个信号,当接收到 MSG_OOB 紧急消息时,系统产生SIGURG信号

    while ((str_len = recv(recv_sock, buf, sizeof(buf), 0)) != 0)
    {
        if (str_len == -1)
            continue;
        buf[str_len] = 0;
        puts(buf);
    }
    close(recv_sock);
    close(acpt_sock);
    return 0;
}
void urg_handler(int signo)
{
    int str_len;
    char buf[BUF_SIZE];
    str_len = recv(recv_sock, buf, sizeof(buf) - 1, MSG_OOB);
    buf[str_len] = 0;
    printf("Urgent message: %s \n", buf);
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

send:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
    int sock;
    struct sockaddr_in recv_adr;
    if (argc != 3)
    {
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }
    sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&recv_adr, 0, sizeof(recv_adr));
    recv_adr.sin_family = AF_INET;
    recv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    recv_adr.sin_port = htons(atoi(argv[2]));

    if (connect(sock, (struct sockaddr *)&recv_adr, sizeof(recv_adr)) == -1)
        error_handling("connect() error");

    write(sock, "123", strlen("123"));
    send(sock, "4", strlen("4"), MSG_OOB);
    write(sock, "567", strlen("567"));
    send(sock, "890", strlen("890"), MSG_OOB);
    close(sock);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

       《TCP IP网络编程》第十三章,《TCPIP网络编程》,网络,tcp/ip,服务器,网络编程

         输出结果,可能出乎意料:

通过 MSG_OOB 可选项传递数据时只返回 1 个字节,而且也不快

        的确,通过 MSG_OOB 并不会加快传输速度,而通过信号处理函数 urg_handler 也只能读取一个字节。剩余数据只能通过未设置 MSG_OOB 可选项的普通输入函数读取。因为 TCP 不存在真正意义上的「外带数据」。实际上,MSG_OOB 中的 OOB 指的是 Out-of-band ,而「外带数据」的含义是: 

        通过完全不同的通信路径传输的数据。

        真正意义上的 Out-of-band 需要通过单独的通信路径高速传输数据,但是 TCP 不另外提供,只利用 TCP 的紧急模式(Urgent mode)进行传输。

紧急模式工作原理:

        MSG_OOB 的真正意义在于督促数据接收对象尽快处理数据。这是紧急模式的全部内容,而 TCP 「保持传输顺序」的传输特性依然成立。下面是 MSG_OOB 可选项状态下的数据传输过程,如图:

《TCP IP网络编程》第十三章,《TCPIP网络编程》,网络,tcp/ip,服务器,网络编程

        上面是: send(sock, "890", strlen("890"), MSG_OOB);

        图上是调用这个函数的缓冲状态。如果缓冲最左端的位置视作偏移量 0 。字符 0 保存于偏移量 2 的位置。另外,字符 0 右侧偏移量为 3 的位置存有紧急指针(Urgent Pointer)。紧急指针指向紧急消息的下一个位置(偏移量加一),同时向对方主机传递以下信息:

        紧急指针指向的偏移量为 3 之前的部分就是紧急消息。

        也就是说,实际上只用了一个字节表示紧急消息。这一点可以通过图中用于传输数据的 TCP 数据包(段)的结构看得更清楚,如图:

《TCP IP网络编程》第十三章,《TCPIP网络编程》,网络,tcp/ip,服务器,网络编程

        TCP 数据包实际包含更多信息。TCP 头部包含如下两种信息:

  • URG=1:载有紧急消息的数据包
  • URG指针:紧急指针位于偏移量为 3 的位置。

        指定 MSG_OOB 选项的数据包本身就是紧急数据包,并通过紧急指针表示紧急消息所在的位置。紧急消息的意义在于督促消息处理,而非紧急传输形式受限的信息。

检查输入缓冲:

        同时设置 MSG_PEEK 选项和 MSG_DONTWAIT 选项,以验证输入缓冲是否存在接收的数据。设置 MSG_PEEK 选项并调用 recv 函数时,即使读取了输入缓冲的数据也不会删除。因此,该选项通常与 MSG_DONTWAIT 合作,用于以非阻塞方式验证待读数据存在与否。下面的示例是二者的含义:

peek_recv:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
    int acpt_sock, recv_sock;
    struct sockaddr_in acpt_adr, recv_adr;
    int str_len, state;
    socklen_t recv_adr_sz;
    char buf[BUF_SIZE];
    if (argc != 2)
    {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }
    acpt_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&acpt_adr, 0, sizeof(acpt_adr));
    acpt_adr.sin_family = AF_INET;
    acpt_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    acpt_adr.sin_port = htons(atoi(argv[1]));

    if (bind(acpt_sock, (struct sockaddr *)&acpt_adr, sizeof(acpt_adr)) == -1)
        error_handling("bind() error");
    listen(acpt_sock, 5);

    recv_adr_sz = sizeof(recv_adr);
    recv_sock = accept(acpt_sock, (struct sockaddr *)&recv_adr, &recv_adr_sz);

    while (1)
    {
        //保证就算不存在待读取数据也不会阻塞
        str_len = recv(recv_sock, buf, sizeof(buf) - 1, MSG_PEEK | MSG_DONTWAIT);
        if (str_len > 0)
            break;
    }

    buf[str_len] = 0;
    printf("Buffering %d bytes : %s \n", str_len, buf);
    //再次调用 recv 函数,这一次没有设置任何可选项,所以可以直接从缓冲区读出
    str_len = recv(recv_sock, buf, sizeof(buf) - 1, 0);
    buf[str_len] = 0;
    printf("Read again: %s \n", buf);
    close(acpt_sock);
    close(recv_sock);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

 peek_send:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
void error_handling(char *message);

int main(int argc, char *argv[])
{
    int sock;
    struct sockaddr_in send_adr;
    if (argc != 3)
    {
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }
    sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&send_adr, 0, sizeof(send_adr));
    send_adr.sin_family = AF_INET;
    send_adr.sin_addr.s_addr = inet_addr(argv[1]);
    send_adr.sin_port = htons(atoi(argv[2]));

    if (connect(sock, (struct sockaddr *)&send_adr, sizeof(send_adr)) == -1)
        error_handling("connect() error");

    write(sock, "123", strlen("123"));
    close(sock);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

运行结果:

《TCP IP网络编程》第十三章,《TCPIP网络编程》,网络,tcp/ip,服务器,网络编程

        当使用MSG_PEEK标志时,接收函数会将数据复制到指定的缓冲区中,但是这些数据仍然保留在套接字的接收缓冲区中。这意味着,即使再次调用接收函数,仍然可以读取相同的数据。这对于某些特定的应用场景可能是有用的。        

        可以通过结果验证,仅发送了一次的数据被读取了 2 次,因为第一次调用 recv 函数时设置了 MSG_PEEK 可选项。 

13.2 readv & writev 函数

 使用 readv & writev 函数:

        readv & writev 函数的功能可概括如下:

        对数据进行整合传输及发送的函数。

        也就是说,通过 writev 函数可以将分散保存在多个缓冲中的数据一并发送,通过 readv 函数可以由多个缓冲分别接收。因此,适用这 2 个函数可以减少 I/O 函数的调用次数。下面先介绍 writev 函数:

#include <sys/uio.h>
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
/*
成功时返回发送的字节数,失败时返回 -1
filedes: 表示数据传输对象的套接字文件描述符。但该函数并不仅限于套接字,因此,可以像 read 一样向向其传递文件或标准输出描述符.
iov: iovec 结构体数组的地址值,结构体 iovec 中包含待发送数据的位置和大小信息
iovcnt: 向第二个参数传递数组长度
*/

        上述第二个参数中出现的数组 iovec 结构体的声明如下:

struct iovec
{
    void *iov_base; //缓冲地址
    size_t iov_len; //缓冲大小
};

        下图是该函数的使用方法:

《TCP IP网络编程》第十三章,《TCPIP网络编程》,网络,tcp/ip,服务器,网络编程

 

        writev 的第一个参数,是文件描述符,因此向控制台输出数据,ptr 是存有待发送数据信息的 iovec 数组指针。第三个参数为 2,因此,从 ptr 指向的地址开始,共浏览 2 个 iovec 结构体变量,发送这些指针指向的缓冲数据。

        下面是 writev 函数的使用示例:

#include <stdio.h>
#include <sys/uio.h>
int main(int argc, char *argv[])
{
    struct iovec vec[2];
    char buf1[] = "ABCDEFG";
    char buf2[] = "1234567";
    int str_len;

    vec[0].iov_base = buf1;
    vec[0].iov_len = 3;
    vec[1].iov_base = buf2;
    vec[1].iov_len = 4;

    str_len = writev(1, vec, 2);
    puts("");
    printf("Write bytes: %d \n", str_len);
    return 0;
}

 运行结果:

《TCP IP网络编程》第十三章,《TCPIP网络编程》,网络,tcp/ip,服务器,网络编程

        下面介绍 readv 函数,功能和 writev 函数正好相反.函数为: 

#include <sys/uio.h>
ssize_t readv(int filedes, const struct iovc *iov, int iovcnt);
/*
成功时返回接收的字节数,失败时返回 -1
filedes: 表示数据传输对象的套接字文件描述符。但该函数并不仅限于套接字,因此,可以像 write 一样向向其传递文件或标准输出描述符.
iov: iovec 结构体数组的地址值,结构体 iovec 中包含待数据保存的位置和大小信息
iovcnt: 第二个参数中数组的长度
*/

        下面是示例代码:

#include <stdio.h>
#include <sys/uio.h>
#define BUF_SIZE 100

int main(int argc, char *argv[])
{
    struct iovec vec[2];
    char buf1[BUF_SIZE] = {
        0,
    };
    char buf2[BUF_SIZE] = {
        0,
    };
    int str_len;

    vec[0].iov_base = buf1;
    vec[0].iov_len = 5;
    vec[1].iov_base = buf2;
    vec[1].iov_len = BUF_SIZE;

    str_len = readv(0, vec, 2);
    printf("Read bytes: %d \n", str_len);
    printf("First message: %s \n", buf1);
    printf("Second message: %s \n", buf2);
    return 0;
}

运行结果:

《TCP IP网络编程》第十三章,《TCPIP网络编程》,网络,tcp/ip,服务器,网络编程 

        从图上可以看出,首先截取了长度为 5 的数据输出,然后再输出剩下的。 

合理使用 readv & writev 函数:

        实际上,能使用该函数的所有情况都适用。例如,需要传输的数据分别位于不同缓冲(数组)时,需要多次调用 write 函数。此时可通过 1 次 writev 函数调用替代操作,当然会提高效率。同样,需要将输入缓冲中的数据读入不同位置时,可以不必多次调用 read 函数,而是利用 1 次 readv 函数就能大大提高效率。

        其意义在于减少数据包个数。假设为了提高效率在服务器端明确禁用了 Nagle 算法。其实 writev 函数在不采用 Nagle 算法时更有价值,如图:

《TCP IP网络编程》第十三章,《TCPIP网络编程》,网络,tcp/ip,服务器,网络编程

         上述示例中待发送的数据分别存在3个不同的地方,此时如果使用write函数则需要3次函数调用。.但若为提高速度而关闭了Nagle算法,则极有可能通过3个数据包传递数据。反之,若使用writev函数将所有数据一次性写入输出缓冲,则很有可能仅通过1个数据包传输数据。所以writev函数和readv函数非常有用。


习题:

1、利用 readv & writev 函数收发数据有何优点?分别从函数调用次数和 I/O 缓冲的角度给出说明

        需要传输的数据分别位于不同缓冲(数组)时,需要多次调用 write 函数。此时可通过 1 次 writev 函数调用替代操作,当然会提高效率。同样,需要将输入缓冲中的数据读入不同位置时,可以不必多次调用 read 函数,而是利用 1 次 readv 函数就能大大提高效率。        

2、通过 recv 函数验证输入缓冲中是否存在数据时(确认后立即返回时),如何设置 recv 函数最后一个参数中的可选项?分别说明各可选项的含义

        使用 MSG_PEEK 来验证输入缓冲中是否存在待接收的数据。文章来源地址https://www.toymoban.com/news/detail-615994.html

到了这里,关于《TCP IP网络编程》第十三章的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 《TCP IP网络编程》第十七章

            select 复用方法由来已久,因此,利用该技术后,无论如何优化程序性能也无法同时介入上百个客户端。这种 select 方式并不适合以 web 服务器端开发为主流的现代开发环境 ,所以需要学习 Linux 环境下的 epoll。 基于 select 的 I/O 复用技术速度慢的原因:        

    2024年02月12日
    浏览(38)
  • TCP/IP网络编程 第十七章:优于select的epoll

    select复用方法其实由来已久,因此,利用该技术后,无论如何优化程序性能也无法同时接入上百个客户端(当然,硬件性能不同,差别也很大)。这种select方式并不适合以Web服务器端开发为主流的现代开发环境,所以要学习Linux平台下的epoll。 基于select的I/O复用技术速度慢的原

    2024年02月16日
    浏览(40)
  • TCP/IP网络编程 第十九章:Windows平台下线程的使用

    要想掌握Windows平台下的线程,应首先理解“内核对象”(Kernel Objects)的概念。如果仅介绍Windows平台下的线程使用技巧,则可以省略相对陌生的内核对象相关内容。但这并不能使各位深入理解Windows平台下的线程。 内核对象的定义 操作系统创建的资源有很多种,如进程、线程

    2024年02月16日
    浏览(51)
  • TCP/IP网络编程 第十六章:关于IO流分离的其他内容

    两次I/O流分离 我们之前通过2种方法分离过IO流,第一种是第十章的“TCPI/O过程(Routine)分离”。这种方法通过调用fork函数复制出1个文件描述符,以区分输入和输出中使用的文件描述符。虽然文件描述符本身不会根据输入和输出进行区分,但我们分开了2个文件描述符的用途

    2024年02月16日
    浏览(38)
  • TCP/IP网络编程 第十五章:套接字和标准I/O

    标准I/O函数的两个优点 将标准I/O函数用于数据通信并非难事。但仅掌握函数使用方法并没有太大意义,至少应该 了解这些函数具有的优点。下面列出的是标准I/O函数的两大优点: □标准I/O函数具有良好的移植性(Portability) □标准I/O函数可以利用缓冲提高性能。 关于移植性无需

    2024年02月16日
    浏览(51)
  • 突破编程_C++_网络编程(TCPIP 四层模型(概述))

    TCP/IP 协议,全称为 Transmission Control Protocol/Internet Protocol,中文名为传输控制协议/因特网互联协议,又名网络通讯协议。这是 Internet 最基本的协议,也是 Internet 国际互联网络的基础。它主要由网络层的 IP 协议和传输层的 TCP 协议组成,定义了电子设备如何连入因特网,以及数

    2024年04月08日
    浏览(46)
  • 突破编程_C++_网络编程(TCPIP 四层模型(传输层))

    在 TCP/IP 四层模型中,传输层位于网络层之上和应用层之下,负责在源主机和目标主机之间提供端到端的可靠数据传输服务。传输层的主要功能与作用体现在以下几个方面: 分段与重组:由于网络层的数据包大小有限制(如 IP 数据包的最大长度为 65535 字节),而应用层的数

    2024年04月09日
    浏览(39)
  • 《TCP IP网络编程》

            2023.6.28 正式开始学习网络编程。 每一章每一节的笔记都会记录在博客中以便复习。         网络编程又叫套接字编程。所谓网络编程,就是编写程序使两台连网的计算机相互交换数据。 为什么叫套接字编程? 我们平常将插头插入插座上就能从电网中获取电力,同

    2024年02月11日
    浏览(44)
  • TCP/IP网络编程(一)

    1.1.1 构建打电话套接字 以电话机打电话的方式来理解套接字。 **调用 socket 函数(安装电话机)时进行的对话:**有了电话机才能安装电话,于是就要准备一个电话机,下面函数相当于电话机的套接字。 **调用 bind 函数(分配电话号码)时进行的对话:**套接字同样如此。就想

    2024年02月03日
    浏览(50)
  • TCP/IP网络编程(二)

    本章将讨论如何优雅地断开相互连接的套接字。之前用的方法不够优雅是因为,我们是调用 close 或 closesocket 函数单方面断开连接的。 TCP中的断开连接过程比建立连接过程更重要,因为连接过程中一般不会出现大的变数,但断开过程有可能发生预想不到的情况,因此应准确掌

    2024年02月03日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包