linux网络编程--跳水send和recv

时间:2023-11-11 16:37:32

要了解一个概念:所有的TCP socket在内核具有发送缓冲器和接收缓冲器。TCP除了全双工操作模式TCP滑模取决于这两个单独buffer和这个buffer填充状态。

接收缓冲器数据缓存入内核。应用进程一直没有调用read进行读取的话,此数据会一直缓存在对应 socket的接收缓冲区内。再啰嗦一点。无论进程是否读取socket,对端发来的数据都会经由内核接收而且缓存到socket的内核接收缓冲区之中。 read所做的工作,就是把内核缓冲区中的数据复制到应用层用户的buffer里面。仅此而已。进程调用send发送的数据的时候,最简单情况(也是普通情况),将数据拷贝进入socket的内核发送缓冲区之中,然后send便会在上层返回。

换句话说,send返回之时。数据不一定会发送到对端去(和
write写文件有点类似),send不过把应用层buffer的数据拷贝进socket的内核发送buffer中。

兴许我会专门用一篇文章介绍 read和send所关联的内核动作。每一个UDP socket都有一个接收缓冲区。没有发送缓冲区,从概念上来说就是只要有数据就发,无论对方能否够正确接收,所以不缓冲。不须要发送缓冲区。

  接收缓冲区被TCP和UDP用来缓存网络上来的数据,一直保存到应用进程读走为止。对于TCP。假设应用进程一直没有读取,buffer满了之后,发生的动作是:通知对端TCP协议中的窗体关闭。这个便是滑动窗体的实现。保证TCP套接口接收缓冲区不会溢出。从而保证了TCP是可靠传输。由于对方不同意发出超过所通告窗体大小的数据。

这就是TCP的流量控制。假设对方无视窗体大小而发出了超过窗体大小的数据。则接收方TCP将丢弃它。 UDP:当套接口接收缓冲区满时。新来的数据报无法进入接收缓冲区。此数据报就被丢弃。

UDP是没有流量控制的;快的发送者能够非常easy地就淹没慢的接收者,导致接收方的UDP丢弃数据报。

  以上便是TCP可靠,UDP不可靠的实现。

  TCP_CORK TCP_NODELAY

  这两个选项是相互排斥的,打开或者关闭TCP的nagle算法,以下用场景来解释

  典型的webserver向client的应答,应用层代码实现流程粗略来说,一般例如以下所看到的:

  if(条件1){

  向buffer_last_modified填充协议内容“Last-Modified: Sat, 04 May 2012 05:28:58 GMT”;

  send(buffer_last_modified);

  }

  if(条件2){

  向buffer_expires填充协议内容“Expires: Mon, 14 Aug 2023 05:17:29 GMT”;

  send(buffer_expires);

  }

  。

  if(条件N){

  向buffer_N填充协议内容“。。。

”;

  send(buffer_N);

  }

  对于这种实现。当前的http应答在运行这段代码时。如果有M(M<=N)个条件都满足。那么会有连续的M个send调用,那是不是下层会依次向client发出M个TCP包呢?答案是否定的。包的数目在应用层是无法控制的,而且应用层也是不须要控制的。

  我用下列四个如果场景来解释一下这个答案

  因为TCP是流式的,对于TCP而言,每一个TCP连接唯独syn開始和fin结尾,中间发送的数据是没有边界的。多个连续的send所干的事情不过:

  假如socket的文件描写叙述符被设置为堵塞方式,并且发送缓冲区还有足够空间容纳这个send所指示的应用层buffer的所有数据,那么把这些数据从应用层的buffer,复制到内核的发送缓冲区。然后返回。

  假如socket的文件描写叙述符被设置为堵塞方式,可是发送缓冲区没有足够空间容纳这个send所指示的应用层buffer的所有数据,那么能拷贝多少就拷贝多少,然后进程挂起,等到TCP对端的接收缓冲区有空余空间时,通过滑动窗体协议(ACK包的又一个作用----打开窗体)通知TCP本端:“亲,我已经做好准备,您如今能够继续向我发送X个字节的数据了”,然后本端的内核唤醒进程,继续向发送缓冲区拷贝剩余数据。而且内核向TCP对端发送TCP数据,假设send所指示的应用层buffer中的数据在本次仍然无法所有拷贝完。那么过程反复。

。。直到所有数据所有拷贝完。返回。

  请注意,对于send的行为,我用了“拷贝一次”,send和下层是否发送数据包。没有不论什么关系。

  假如socket的文件描写叙述符被设置为非堵塞方式,并且发送缓冲区还有足够空间容纳这个send所指示的应用层buffer的所有数据,那么把这些数据从应用层的buffer,复制到内核的发送缓冲区,然后返回。

  假如socket的文件描写叙述符被设置为非堵塞方式,可是发送缓冲区没有足够空间容纳这个send所指示的应用层buffer的所有数据,那么能拷贝多少就拷贝多少,然后返回拷贝的字节数。多涉及一点。返回之后有两种处理方式:

  1.死循环,一直调用send,持续測试。一直到结束(基本上不会这么搞)。

  2.非堵塞搭配epoll或者select。用这两种东西来測试socket是否达到可发送的活跃状态,然后调用send(高性能server必需的处理方式)。

  综上。以及请參考本文前述的SO_RCVBUF和SO_SNDBUF,你会发现,在实际场景中,你能发出多少TCP包以及每一个包承载多少数据,除了受到自身server配置和环境带宽影响,对端的接收状态也能影响你的发送状况。

  至于为什么说“应用层也是不须要控制发送行为的”。这个说法的原因是:

  软件系统分层处理、分模块处理各种软件行为,目的就是为了各司其职,分工。应用层仅仅关心业务实现,控制业务。传输数据由专门的层面去处理,这样应用层开发的规模和复杂程度会大为减少。开发和维护成本也会对应减少。

  再回到发送的话题上来:)之前说应用层无法精确控制和全然控制发送行为。那是不是就是不控制了?非也!尽管无法控制,但也要尽量控制!

  怎样尽量控制?如今引入本节主题----TCP_CORK和TCP_NODELAY。

  cork:塞子。塞住

  nodelay:不要延迟

  TCP_CORK:尽量向发送缓冲区中攒数据,攒到多了再发送,这样网络的有效负载会升高。

简单粗暴地解释一下这个有效负载的问题。

假如每一个包中仅仅有一个字节的数据,为了发送这一个字节的数据。再给这一个字节外面包装一层厚厚的TCP包头,那网络上跑的差点儿全是包头了,有效的数据仅仅占当中非常小的部分,非常多訪问量大的server,带宽能够非常轻松的被这么耗尽。那么,为了让有效负载升高,我们能够通过这个选项指示TCP层。在发送的时候尽量多攒一些数据,把他们填充到一个TCP包中再发送出去。这个和提升发送效率是相互矛盾的。空间和时间总是一堆冤家!!

  TCP_NODELAY:尽量不要等待,仅仅要发送缓冲区中有数据,而且发送窗体是打开的,就尽量把数据发送到网络上去。

  非常明显。两个选项是相互排斥的。实际场景中该怎么选择这两个选项呢?

再次举例说明

  webserver,,下载server(ftp的发送文件server)。须在更大的带宽,server,使用TCP_CORK。

  它涉及的互动server。实例ftp接收命令server。您必须使用TCP_NODELAY。缺省值是TCP_CORK。试想一下,。用户命令的每几个字节的敲,下保存数据。我想等到更多的数据发送前,这样的用户会等到发疯。

在最坏的情况下有一个特殊的词汇来描述-----棍(nian拼音第二声)包裹。