zoukankan      html  css  js  c++  java
  • socket套接字及缓冲区详解

    socket套接字及缓冲区详解


    文章目录
    一、域(domain)
    二、类型(type)
    三、协议(protocol)
    四、socket缓冲区以及阻塞模式
    1、缓冲区简介
    2、使用write()/send()发送数据
    3、使用read()/recv()读取数据
    4、系统调用read()的返回错误场景
    五、面试题--->TCP服务端一直sleep,客户端发送数据问题
    1、TCP发送数据的过程
    2、阻塞方式的情况
    3、非阻塞方式的情况
      套接字是一种通信机制(通信的两方的一种约定),凭借这种机制,不同主机之间的进程可以进行通信。我们可以用套接字中的相关函数来完成通信过程。

      套接字的特性有三个属性确定,它们是:域(domain),类型(type),和协议(protocol)。

    #include<sys/types.h>
    #include<sys/socket.h>
    int socket(int domain, int type, int protocol);

    一、域(domain)

      域指定套接字通信中使用的网络介质。最常见的套接字域是 AF_INET(IPv4)或者AF_INET6(IPV6),它是指 Internet 网络,许多 Linux 局域网使用的都是该网络,当然,因特网自身用的也是它。

    二、类型(type)
    流套接字(SOCK_STREAM):
      流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission Control Protocol)协议。

    数据报套接字(SOCK_DGRAM):
      数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。

    原始套接字(SOCK_RAW):
      原始套接字与标准套接字(标准套接字指的是前面介绍的流套接字和数据报套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送数据必须使用原始套接字。

    三、协议(protocol)
    0:
       使用默认协议;

    IPPROTO_TCP;
       使用TCP协议;

    IPPROTO_UDP;
       使用UDP协议;

    四、socket缓冲区以及阻塞模式
    1、缓冲区简介
      每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。

      write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。
      read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。


    I/O缓冲区在每个TCP套接字中单独存在;
    I/O缓冲区在创建套接字时自动生成;
    即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
    关闭套接字将丢失输入缓冲区中的数据。

    2、使用write()/send()发送数据
    【阻塞模式下】:

    首先会检查缓冲区,如果缓冲区的可用空间长度小于要发送的数据,那么 write()/send() 会被阻塞(暂停执行),直到缓冲区中的数据被发送到目标机器,腾出足够的空间,才唤醒 write()/send() 函数继续写入数据;

    如果TCP协议正在向网络发送数据,那么输出缓冲区会被锁定,不允许写入,write()/send() 也会被阻塞,直到数据发送完毕缓冲区解锁,write()/send() 才会被唤醒。如果TCP协议正在向网络发送数据,那么输出缓冲区会被锁定,不允许写入,write()/send() 也会被阻塞,直到数据发送完毕缓冲区解锁,write()/send() 才会被唤醒;

    如果要写入的数据大于缓冲区的最大长度,那么将分批写入。如果要写入的数据大于缓冲区的最大长度,那么将分批写入;直到所有数据被写入缓冲区 write()/send() 才能返回。直到所有数据被写入缓冲区 write()/send() 才能返回。

    send()函数默认情况下会使用Nagle算法。Nagle算法通过将未确认的数据存入缓冲区直到积攒到一定数量一起发送的方法,来降低主机发送零碎小数据包的数目。所以假设send()函数发送数据过快的话,该算法会将一些数据打包后统一发出去。通过setsockopt()的TCP_NODELAY选项来禁用Nagle算法。

    【非阻塞模式下】:

    send()函数的过程仅仅是将数据拷贝到协议栈的缓冲区而已,如果缓冲区可用空间不够,则尽可能拷贝,返回成功拷贝的大小;如果缓存区可用空间为0,则返回-1,同时设置errno为EAGAIN。
    3、使用read()/recv()读取数据
    【阻塞模式下】:

    首先会检查缓冲区,如果缓冲区中有数据,那么就读取,否则函数会被阻塞,直到网络上有数据到来;

    如果要读取的数据长度小于缓冲区中的数据长度,那么就不能一次性将缓冲区中的所有数据读出,剩余数据将不断积压,直到读取到数据后 read()/recv() 函数才会返回,否则就一直被阻塞。

    【非阻塞模式下】:

    接收数据时perror时常遇到“Resource temporarilyunavailable”的提示,errno代码为11(EAGAIN)。这表明你在非阻塞模式下调用了阻塞操作,在该操作没有完成就返回这个错误,这个错误不会破坏socket的同步,继续循环接着recv就可以。
    4、系统调用read()的返回错误场景
    EINTR:在读取到数据以前调用被信号所中断。

    EAGAIN:使用 O_NONBLOCK 标志指定了非阻塞式输入输出,但当前没有数据可读或者使用了阻塞操作。

    EIO:输入输出错误.可能是正处于后台进程组进程试图读取其控制终端,但读操作无效,或者被信号SIGTTIN所阻塞,或者其进程组是孤儿进程组.也可能执行的是读磁盘或者磁带机这样的底层输入输出错误。

    EISDIR:fd 指向一个目录。

    EBADF:fd 不是一个合法的文件描述符,或者不是为读操作而打开。

    EINVAL:fd 所连接的对象不可读。

    EFAULT:buf 超出用户可访问的地址空间。

    EWOULDBLOCK:用于非阻塞模式,表示不需要重新读或者写。

    五、面试题—>TCP服务端一直sleep,客户端发送数据问题
    1、TCP发送数据的过程
      TCP发送数据的大体过程:首先,TCP是有链接的可靠传输协议,所谓可靠也就是说保证客户端发送的数据服务端都能够收到,并且是按序收到。那么对于上面的问题就不可能存在数据的丢弃。那么客户端一直发送数据越来越多怎么办?下面我们分析一下TCP的传输过程。


    1. 数据首先由应用程序缓冲区复制到发送端的输出缓冲区(位于内核),注意这个过程是用类似write功能的函数完成的。有的人通常看到write成功就以为数据发送到了对端主机,其实这是错误的,write成功仅仅表示数据成功的由应用进程缓冲区复制到了输出缓冲区。

    2. 然后内核协议栈将输出缓冲区中的数据发送到对端主机,注意这个过程不受应用程序控制,而是发送端内核协议栈完成,其中包括使用滑动窗口、拥塞控制等功能。

    3. 数据到达接收端主机的输入缓冲区,注意这个接收过程也不受应用程序控制,而是由接收端内核协议栈完成,其中包括发送ack确认等。

    4. 数据由套接字接收缓冲区复制到接收端应用程序缓冲区,注意这个过程是由类似read等函数来完成。

    2、阻塞方式的情况
      阻塞方式下,如果服务端一直sleep不接收数据,而客户端一直write,也就是只能执行上述过程中的前三步,这样最终结果肯定是接收端的输入缓冲区和发送端的输出缓冲区都被填满,这样write就无法继续将数据从应用程序复制到发送端的输出缓冲区了,从而使进程进入睡眠。


    3、非阻塞方式的情况
      非阻塞情况下,服务端一直sleep,客户端一直write数据的结果:开始客户端write成功,随着客户端write,接收端的输入缓冲区和发送端的输出缓冲区会被填满。当发送端的输出缓冲区的可用空间小于write请求写的字节数时,write立即返回-1,并将errno置为EWOULDBLOCK。

    UDP的接收缓冲区:
    每个UDP Socket都有一个接收缓冲区,没有发送缓冲区。有数据就直接发送,不管对方是否能够正确接收,也不管对端接收缓冲区是否已经满了。
    UDP是没有流量控制的;快的发送者可以很容易地就淹没慢的接收者,导致接收方的UDP丢弃数据报
    参考:
    https://www.cnblogs.com/kex1n/p/6501977.html
    https://blog.csdn.net/lianghe_work/article/details/45170359
    http://blog.chinaunix.net/uid-28541347-id-4730278.html
    https://www.cnblogs.com/suanec/p/4248207.html


  • 相关阅读:
    WIN10安装python及numpy等第三方库以及卸载
    学习Python一年,基础忘记了,看看面试题回忆回议,Python面试题No3
    包含了 java环境,mysql,nginx,redis docker 镜像
    Docker的镜像制作与整套项目一键打包部署
    RedHat Enterprise Linux 5.8 升级openssl
    RedHat Enterprise Linux 5.8 升级openssl
    RedHat Enterprise Linux 5.8 升级openssl
    log4net进阶手札(二):基本用法
    log4net进阶手札(二):基本用法
    log4net进阶手札(二):基本用法
  • 原文地址:https://www.cnblogs.com/xiangshihua/p/13324747.html
Copyright © 2011-2022 走看看