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

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

第 16 章 关于 I/O 流分离的其他内容

 

16.1 分离 I/O 流

        「分离 I/O 流」是一种常用表达。有 I/O 工具可区分二者,无论采用哪种方法,都可以认为是分离了 I/O 流。

2次 I/O 流分离:

  • 第一种是第 10 章的「TCP I/O 过程」分离。通
    shutdown(sock,SHUT_WR);
    过调用 fork 函数复制出一个文件描述符,以区分输入和输出中使用的文件描述符。虽然文件描述符本身不会根据输入和输出进行区分,但我们分开了 2 个文件描述符的用途,因此,这也属于「流」的分离。
  • 第二种分离是在第 15 章。通过 2 次 fdopen 函数的调用,创建读模式 FILE 指针(FILE 结构体指针)和写模式 FILE 指针。换言之,我们分离了输入工具和输出工具,因此也可视为「流」的分离。下面是分离的理由。

分离「流」的好处:

        首先是第 10 章「流」的分离目的:

  • 通过分开输入过程(代码)和输出过程降低实现难度
  • 与输入无关的输出操作可以提高速度

        下面是第 15 章「流」分离的目的:

  • 为了将 FILE 指针按读模式和写模式加以区分
  • 可以通过区分读写模式降低实现难度
  • 通过区分 I/O 缓冲提高缓冲性能

「流」分离带来的 EOF 问题:

        第 7 章介绍过 EOF 的传递方法和半关闭的必要性。有一个语句:

shutdown(sock,SHUT_WR);

        当时说过调用 shutdown 函数的基于半关闭的 EOF 传递方法。第十章添加了半关闭的相关代码。但是还没有讲采用 fdopen 函数怎么半关闭。那么是否是通过 fclose 函数关闭流呢?我们先试试:

        服务端代码:

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

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    FILE *readfp;
    FILE *writefp;

    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;
    char buf[BUF_SIZE] = {
        0,
    };
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));
    bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr));
    listen(serv_sock, 5);
    clnt_adr_sz = sizeof(clnt_adr);
    clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_sz);

    readfp = fdopen(clnt_sock, "r");
    writefp = fdopen(clnt_sock, "w");

    fputs("FROM SERVER: Hi~ client? \n", writefp);
    fputs("I love all of the world \n", writefp);
    fputs("You are awesome! \n", writefp);
    fflush(writefp);

    fclose(writefp);
    fgets(buf, sizeof(buf), readfp);
    fputs(buf, stdout);
    fclose(readfp);
    return 0;
}

        客户端代码:

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

int main(int argc, char *argv[])
{
    int sock;
    char buf[BUF_SIZE];
    struct sockaddr_in serv_addr;

    FILE *readfp;
    FILE *writefp;

    sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    readfp = fdopen(sock, "r");
    writefp = fdopen(sock, "w");

    while (1)
    {
        if (fgets(buf, sizeof(buf), readfp) == NULL)
            break;
        fputs(buf, stdout);
        fflush(stdout);
    }

    fputs("FROM CLIENT: Thank you \n", writefp);
    fflush(writefp);
    fclose(writefp);
    fclose(readfp);

    return 0;
}

运行结果:

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

        从运行结果可以看出,服务端最终没有收到客户端发送的信息。

        原因是:服务端代码的 fclose(writefp); 这一句,完全关闭了套接字而不是半关闭。这才是这一章需要解决的问题。

16.2 文件描述符的的复制和半关闭

终止「流」时无法半关闭原因:

        下面的图描述的是服务端代码中的两个FILE 指针、文件描述符和套接字中的关系:

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

        从图中可以看到,两个指针都是基于同一文件描述符创建的。因此,针对于任何一个 FILE 指针调用 fclose 函数都会关闭文件描述符,如图所示:

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

        那如何进入可以进入但是无法输出的半关闭状态呢?如下图所示:

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

        只需要创建 FILE 指针前先复制文件描述符即可。复制后另外创建一个文件描述符,然后利用各自的文件描述符生成读模式的 FILE 指针和写模式的 FILE 指针。这就为半关闭创造好了环境,因为套接字和文件描述符具有如下关系: 

        销毁所有文件描述符候才能销毁套接字。

        也就是说,针对写模式 FILE 指针调用 fclose 函数时,只能销毁与该 FILE 指针相关的文件描述符,无法销毁套接字,如下图:

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

        那么调用 fclose 函数候还剩下 1 个文件描述符,因此没有销毁套接字。那此时的状态是否为半关闭状态?不是!只是准备好了进入半关闭状态,而不是已经进入了半关闭状态。仔细观察,还剩下一个文件描述符。而该文件描述符可以同时进行 I/O 。因此,不但没有发送 EOF ,而且仍然可以利用文件描述符进行输出。 

复制文件描述符:

        与调用 fork 函数不同,调用 fork 函数将复制整个进程,此处讨论的是同一进程内完成对描述符的复制。如图:

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

        复制完成后,两个文件描述符都可以访问文件,但是编号不同。 

dup 和 dup2:

        下面给出文件描述符的复制方法:

#include <unistd.h>
int dup(int fildes);
int dup2(int fildes, int fildes2);
/*
成功时返回复制的文件描述符,失败时返回 -1
fildes : 需要复制的文件描述符
fildes2 : 明确指定的文件描述符的整数值。
*/

        dup2 函数明确指定复制的文件描述符的整数值。向其传递大于 0 且小于进程能生成的最大文件描述符值时,该值将成为复制出的文件描述符值。下面是dup的代码示例:

#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int cfd1, cfd2;
    char str1[] = "Hi~ \n";
    char str2[] = "It's nice day~ \n";

    cfd1 = dup(1);        //复制文件描述符 1
    cfd2 = dup2(cfd1, 7); //再次复制文件描述符,定为数值 7

    printf("fd1=%d , fd2=%d \n", cfd1, cfd2);
    write(cfd1, str1, sizeof(str1));
    write(cfd2, str2, sizeof(str2));

    close(cfd1);
    close(cfd2); //终止复制的文件描述符,但是仍有一个文件描述符
    write(1, str1, sizeof(str1));
    close(1);
    write(1, str2, sizeof(str2)); //无法完成输出
    return 0;
}

         运行结果:

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

        复制文件描述符后「流」的分离 :

        下面更改sep_clnt.c和sep_serv.c   可以使得让它正常工作,正常工作是指通过服务器的半关闭状态接收客户端最后发送的字符串。

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

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    FILE *readfp;
    FILE *writefp;

    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;
    char buf[BUF_SIZE] = {
        0,
    };
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));
    bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr));
    listen(serv_sock, 5);
    clnt_adr_sz = sizeof(clnt_adr);
    clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_sz);

    readfp = fdopen(clnt_sock, "r");
    writefp = fdopen(dup(clnt_sock), "w"); //复制文件描述符

    fputs("FROM SERVER: Hi~ client? \n", writefp);
    fputs("I love all of the world \n", writefp);
    fputs("You are awesome! \n", writefp);
    fflush(writefp);

    shutdown(fileno(writefp), SHUT_WR); //对 fileno 产生的文件描述符使用 shutdown 进入半关闭状态
    fclose(writefp);

    fgets(buf, sizeof(buf), readfp);
    fputs(buf, stdout);
    fclose(readfp);
    return 0;
}

        运行结果:

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

         运行结果证明了 服务器端在半关闭状态下向客户端发送了EOF。文章来源地址https://www.toymoban.com/news/detail-647247.html

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

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

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

相关文章

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

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

    线程背景:         第 10 章介绍了多进程服务端的实现方法。多进程模型与 select 和 epoll 相比的确有自身的优点,但同时也有问题。如前所述, 创建(复制)进程的工作本身会给操作系统带来相当沉重的负担。而且,每个进程都具有独立的内存空间,所以进程间通信的实

    2024年02月12日
    浏览(11)
  • 《TCP IP网络编程》第十三章

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

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

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

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

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

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

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

    TCP/IP网络编程 第十五章:套接字和标准I/O

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

    2024年02月16日
    浏览(36)
  • 嵌入式学习第二十六天!(网络传输:TCP编程)

    嵌入式学习第二十六天!(网络传输:TCP编程)

            socket   -  connect  -  send  -  recv   -  close         socket   -  bind   -  listen   -  accept   - recv   -  send   -  close         1. connect:               功能: 发送链接请求               参数:                   sockfd: 套接字文件描述符                  

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

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

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

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

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

    《TCP IP网络编程》

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

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

    TCP/IP网络编程(三)

    多播(Multicast)方式的数据传输是 基于 UDP 完成的 。因此 ,与 UDP 服务器端/客户端的实现方式非常接近。区别在于,UDP 数据传输以单一目标进行,而多播数据 同时传递到加入(注册)特定组的大量主机 。换言之, 采用多播方式时,可以同时向多个主机传递数据 。 14.1.1 多

    2024年02月03日
    浏览(12)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包