草庐IT

Socket发送缓冲区接收缓冲区快问快答

恐龙弟旺仔 2023-05-18 原文

1.Socket发送数据的全过程

首先,我们要明确一下,通过socket发送数据之后(调用发送方法),数据并不是会直接发送到接收方。

整个过程简略图如下:

 

最终数据发送是要通过网卡的,但是socket没法直接将数据发送到网卡,所以只能先将数据发送到操作系统数据发送缓冲区。

然后网卡从数据发送缓冲区中获取数据,再发送到接收方。

2.socket接收数据的全过程

socket接收数据,也不是直接从对端获取的。

整个过程简略图如下:

首先接收方机器网卡接收到发送方的数据后,先将数据保存到操作系统接收缓冲区。

client端感知到操作系统缓冲区的数据后,主动调用接收数据的方法来获取数据。

3.接收缓冲区、发送缓冲区的含义

首先,这两个区域是每一个Socket连接都有的。本质上而言,就是内核中的两块内存空间,socket创建完成后,这两块内存空间就开辟出来了。

至于其作用,如上图所示,主要是为了做数据缓冲区用的。

4.接收缓冲区、发送缓冲区需要设置嘛?

一般情况下,我们不需要主动设置(当然,也不建议主动设置这两个区域)。

貌似在我们的代码中,从来没有主动设置过这两个区域。

5.接收缓冲区、发送缓冲区默认大小是多少呢?

既然我们没有主动设置过,那么平时我们怎么知道,这两块区域的占用内存大小是多少呢。

就java技术栈而言,我们可以通过获取Socket的属性来获取

Socket socket = new Socket();
int sendBufferSize = socket.getSendBufferSize(); // 发送缓冲区
int receiveBufferSize = socket.getReceiveBufferSize(); // 接收缓冲区

笔者在Windows电脑上测试,默认输出都是65536,也就是64KB。

Linux系统下,我们可以通过查看参数来获取默认值

[root@cfs_netstorage core]# cat /proc/sys/net/ipv4/tcp_wmem
4096    16384   131072  //写缓冲区大小  第一个表示最小值,第二个表示默认值,第三个表示最大值。

[root@cfs_netstorage core]# cat /proc/sys/net/ipv4/tcp_rmem
4096    87380   174760  //读缓冲区大小  第一个表示最小值,第二个表示默认值,第三个表示最大值。

6.可以主动设置读写缓冲区大小嘛?

肯定是可以的。

在java栈中,我们可以通过如下方式设置

Socket socket = new Socket();
socket.setSendBufferSize(size); // 设置写缓冲区大小
socket.setReceiveBufferSize(size); // 设置读缓冲区大小

但是这个设置是不能超过系统设置参数的。

以下解释来自于: 高性能网络编程7--tcp连接的内存使用_陶辉的博客-CSDN博客   

SO_SNDBUF、SO_RCVBUF都是个体化的设置,即,只会影响到设置过的连接,而不会对其他连接生效。SO_SNDBUF表示这个连接上的内核写缓存上限。
实际上,进程设置的SO_SNDBUF也并不是真的上限,在内核中会把这个值翻一倍再作为写缓存上限使用。
我们不需要纠结这种细节,只需要知道,当设置了SO_SNDBUF时,就相当于划定了所操作的TCP连接上的写缓存能够使用的最大内存。
然而,这个值也不是可以由着进程随意设置的,它会受制于系统级的上下限,当它大于上面的系统配置wmem_max(net.core.wmem_max)时,
将会被wmem_max替代(同样翻一倍);而当它特别小时,例如在2.6.18内核中设计的写缓存最小值为2K字节,此时也会被直接替代为2K。

SO_RCVBUF表示连接上的读缓存上限,与SO_SNDBUF类似,它也受制于rmem_max配置项,实际在内核中也是2倍大小作为读缓存的使用上限。
SO_RCVBUF设置时也有下限,同样在2.6.18内核中若这个值小于256字节就会被256所替代。

7.设置了读写缓冲区,是否就是一定会使用这么多内存呢?

以下解释来自于: 高性能网络编程7--tcp连接的内存使用_陶辉的博客-CSDN博客   

TCP连接所用内存主要由读写缓存决定,而读写缓存的大小只与实际使用场景有关,在实际使用未达到上限时,SO_SNDBUF、SO_RCVBUF是不起任何作用的。
对读缓存来说,接收到一个来自连接对端的TCP报文时,会导致读缓存增加,当然,如果加上报文大小后读缓存已经超过了读缓存上限,那么这个报文会被丢弃从而读缓存大小维持不变。
什么时候读缓存使用的内存会减少呢?当进程调用read、recv这样的方法读取TCP流时,读缓存就会减少。
因此,读缓存是一个动态变化的、实际用到多少才分配多少的缓冲内存,当这个连接非常空闲时,且用户进程已经把连接上接收到的数据都消费了,那么读缓存使用内存就是0。

写缓存也是同样道理。当用户进程调用send或者write这样的方法发送TCP流时,就会造成写缓存增大。当然,如果写缓存已经到达上限,那么写缓存维持不变,向用户进程返回失败。
而每当接收到TCP连接对端发来的ACK确认了报文的成功发送时,写缓存就会减少,这是因为TCP的可靠性决定的,发出去报文后由于担心报文丢失而不会销毁它,
可能会由重发定时器来重发报文。因此,写缓存也是动态变化的,空闲的正常连接上,写缓存所用内存通常也为0。

因此,只有当接收网络报文的速度大于应用程序读取报文的速度时,可能使读缓存达到了上限,这时这个缓存使用上限才会起作用。

所起作用为:丢弃掉新收到的报文,防止这个TCP连接消耗太多的服务器资源。

同样,当应用程序发送报文的速度大于接收对方确认ACK报文的速度时,写缓存可能达到上限,从而使send这样的方法失败,内核不为其分配内存。

8.在实际开发中,我们是否推荐设置读写缓冲区大小呢?

答案是:不推荐。

我们考虑以下实际场景:

当连接到服务端的连接比较少时,我们期望设置该连接的读写缓冲区大小为系统缓存上限,这样便可以充分利用网络资源;

当连接到服务端的连接比较多时,这时系统内存为稀缺资源了,为了支持更多的连接,便需要减小每个连接的读写缓冲区大小;

为了支持这种场景,Linux操作系统实现了自动调整分配内存的功能。由以下参数来决定

net.ipv4.tcp_moderate_rcvbuf = 1 # 默认tcp_moderate_rcvbuf配置为1,表示打开了TCP内存自动调整功能。若配置为0,这个功能将不会生效

但是,如果我们主动配置了socket读写缓冲区大小,那么这个自动分配内存的功能就不再生效。

9.发送缓冲区、接收缓冲区与发送窗口、接收创建之间的关系?

需要先明确下:

发送缓冲区>=发送窗口

如下图所示:(图片来自: TCP缓存区与窗口的关系_ccc_yxc的博客-CSDN博客_发送窗口和发送缓存  )

发送窗口只是发送缓冲区的一部分,已经被确认的数据则会从发送缓冲区中删除。

接收缓冲区>=接收窗口

如下图所示:(图片来自: TCP缓存区与窗口的关系_ccc_yxc的博客-CSDN博客_发送窗口和发送缓存 )

 

接收缓冲区中存放的数据,一旦被应用程序读取到之后,则会从缓冲区中删除。

参考:

socket tcp缓冲区大小的默认值、最大值_zzd2018的博客-CSDN博客_socket接收缓冲区最大值

高性能网络编程7--tcp连接的内存使用_陶辉的博客-CSDN博客

TCP缓存区与窗口的关系_ccc_yxc的博客-CSDN博客_发送窗口和发送缓存

 

有关Socket发送缓冲区接收缓冲区快问快答的更多相关文章

  1. ruby-openid:执行发现时未设置@socket - 2

    我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass

  2. ruby-on-rails - RSpec:避免使用允许接收的任何实例 - 2

    我正在处理旧代码的一部分。beforedoallow_any_instance_of(SportRateManager).toreceive(:create).and_return(true)endRubocop错误如下:Avoidstubbingusing'allow_any_instance_of'我读到了RuboCop::RSpec:AnyInstance我试着像下面那样改变它。由此beforedoallow_any_instance_of(SportRateManager).toreceive(:create).and_return(true)end对此:let(:sport_

  3. jquery - 我的 jquery AJAX POST 请求无需发送 Authenticity Token (Rails) - 2

    rails中是否有任何规定允许站点的所有AJAXPOST请求在没有authenticity_token的情况下通过?我有一个调用Controller方法的JqueryPOSTajax调用,但我没有在其中放置任何真实性代码,但调用成功。我的ApplicationController确实有'request_forgery_protection'并且我已经改变了config.action_controller.consider_all_requests_local在我的environments/development.rb中为false我还搜索了我的代码以确保我没有重载ajaxSend来发送

  4. ruby-on-rails - 如何使用 Rack 接收 JSON 对象 - 2

    我有一个非常简单的RubyRack服务器,例如:app=Proc.newdo|env|req=Rack::Request.new(env).paramspreq.inspect[200,{'Content-Type'=>'text/plain'},['Somebody']]endRack::Handler::Thin.run(app,:Port=>4001,:threaded=>true)每当我使用JSON对象向服务器发送POSTHTTP请求时:{"session":{"accountId":String,"callId":String,"from":Object,"headers":

  5. ruby - 使用 Ruby 通过 Outlook 发送消息的最简单方法是什么? - 2

    我的工作要求我为某些测试自动生成电子邮件。我一直在四处寻找,但未能找到可以快速实现的合理解决方案。它需要在outlook而不是其他邮件服务器中,因为我们有一些奇怪的身份验证规则,我们需要保存草稿而不是仅仅发送邮件的选项。显然win32ole可以做到这一点,但我找不到任何相当简单的例子。 最佳答案 假设存储了Outlook凭据并且您设置为自动登录到Outlook,WIN32OLE可以很好地完成此操作:require'win32ole'outlook=WIN32OLE.new('Outlook.Application')message=

  6. SPI接收数据异常问题总结 - 2

    SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手

  7. ruby - 是否可以在不实际发送或读取数据的情况下查明 ruby​​ 套接字是否处于 ESTABLISHED 或 CLOSE_WAIT 状态? - 2

    s=Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0)s.connect(Socket.pack_sockaddr_in('port','hostname'))ssl=OpenSSL::SSL::SSLSocket.new(s,sslcert)ssl.connect从这里开始,如果ssl连接和底层套接字仍然是ESTABLISHED,或者它是否在默认值7200之后进入CLOSE_WAIT,我想检查一个线程几秒钟甚至更糟的是在实际上不需要.write()或.read()的情况下关闭。是用select()、IO.select()还是其他方法完成

  8. ruby - 如何理解 Ruby 中的发送者和接收者? - 2

    我很难理解Ruby中sender和receiver的实际含义。它们一般是什么意思?到目前为止,我只是将它们理解为方法调用和获取其返回值的调用。但是,我知道我的理解还远远不够。谁能给我一个Ruby中发送者和接收者的具体解释? 最佳答案 面向对象中的一个核心概念是消息传递和早期概念化,这在很大程度上借鉴了计算的Actor模型。艾伦·凯(AlanKay)创造了面向对象一词并发明了最早的OO语言之一SmallTalk,他拥有voicedregretatusingatermwhichputthefocusonobjectsinsteadofo

  9. ruby - 动态扩展现有方法或覆盖 ruby​​ 中的发送方法 - 2

    假设我们有A、B、C类。Adefself.inherited(sub)#metaprogramminggoeshere#takeclassthathasjustinheritedclassA#andforfooclassesinjectprepare_foo()as#firstlineofmethodthenrunrestofthecodeenddefprepare_foo#=>prepare_foo()neededhere#somecodeendendBprepare_foo()neededhere#somecodeendend如您所见,我正在尝试将foo_prepare()调用注入

  10. ruby-on-rails - 如何通过 POST 发送多个相同的键/参数? - 2

    如果我必须在一个HTTP请求中发送一堆post参数,所有这些参数都具有相同的名称,我该如何构建要发布的data对象?想象一个带有一些复选框的表单,它们都具有相同的name属性但具有不同的值(如果它们被选中):我想用ruby​​构建它(但它需要根据在表单上选择的内容动态创建):data={"color"=>"red","color"=>"green","color"=>"blue"}然后将数据发送到某个URL:Net::HTTP.post_form(url,data)我无法控制接收端,所以我必须发送它期望接收的参数。怎么办? 最佳答案

随机推荐