当前位置:首页 > 单片机 > 小林coding
[导读]今天又是被倾盆的需求淹没的一天。有没有人知道,那种“我用3句话,就让产品为我砍了18个需求”的鸡汤课在哪报名,想报。"听懂掌声"的那种课就算了,太费手了。扯远了,回到我们今天的正题,我们了解下这篇文的目录。目录代码执行send成功后,数据就发出去了吗?回答这个问题之前,需要了解什...

今天又是被倾盆的需求淹没的一天。

有没有人知道,那种“我用3句话,就让产品为我砍了18个需求”的鸡汤课在哪报名,想报。

"听懂掌声"的那种课就算了,太费手了。

扯远了,回到我们今天的正题,我们了解下这篇文的目录。

目录
代码执行send成功后,数据就发出去了吗?

回答这个问题之前,需要了解什么是Socket 缓冲区


Socket 缓冲区

什么是 socket 缓冲区

编程的时候,如果要跟某个IP建立连接,我们需要调用操作系统提供的 socket API

socket 在操作系统层面,可以理解为一个文件

我们可以对这个文件进行一些方法操作

listen方法,可以让程序作为服务器监听其他客户端的连接。

connect,可以作为客户端连接服务器。

sendwrite可以发送数据,recvread可以接收数据。

在建立好连接之后,这个 socket 文件就像是远端机器的 "代理人" 一样。比如,如果我们想给远端服务发点什么东西,那就只需要对这个文件执行写操作就行了。

socket_api
那写到了这个文件之后,剩下的发送工作自然就是由操作系统内核来完成了。

既然是写给操作系统,那操作系统就需要提供一个地方给用户写。同理,接收消息也是一样。

这个地方就是 socket 缓冲区

用户发送消息的时候写给 send buffer(发送缓冲区)

用户接收消息的时候写给 recv buffer(接收缓冲区)

也就是说一个socket ,会带有两个缓冲区,一个用于发送,一个用于接收。因为这是个先进先出的结构,有时候也叫它们发送、接收队列

一个socket有两个缓冲区

怎么观察 socket 缓冲区

如果想要查看 socket 缓冲区,可以在linux环境下执行 netstat -nt 命令。

# netstat -nt
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0     60 172.22.66.69:22         122.14.220.252:59889    ESTABLISHED
这上面表明了,这里有一个协议(Proto)类型为 TCP 的连接,同时还有本地(Local Address)和远端(Foreign Address)的IP信息,状态(State)是已连接。

还有Send-Q 是发送缓冲区,下面的数字60是指,当前还有60 Byte在发送缓冲区中未发送。而 Recv-Q 代表接收缓冲区, 此时是空的,数据都被应用进程接收干净了。


TCP部分

【动图缓冲区的收发流程,TCP执行发收的流程】

我们在使用TCP建立连接之后,一般会使用 send 发送数据。

int main(int argc, char *argv[])
{
    // 创建socket
    sockfd=socket(AF_INET,SOCK_STREAM, 0))

    // 建立连接  
    connect(sockfd, 服务器ip信息, sizeof(server))  

    // 执行 send 发送消息
    send(sockfd,str,sizeof(str),0))  

    // 关闭 socket
    close(sockfd);

    return 0;
}
上面是一段伪代码,仅用于展示大概逻辑,我们在建立好连接后,一般会在代码中执行 send 方法。那么此时,消息就会被立刻发到对端机器吗?


执行 send 发送的字节,会立马发送吗?

答案是不确定!执行 send 之后,数据只是拷贝到了socket 缓冲区。至 什么时候会发数据,发多少数据,全听操作系统安排。

tcp_sendmsg 逻辑
在用户进程中,程序通过操作 socket 会从用户态进入内核态,而 send方法会将数据一路传到传输层。在识别到是 TCP协议后,会调用 tcp_sendmsg 方法。

// net/ipv4/tcp.c
// 以下省略了大量逻辑
int tcp_sendmsg()
{  
  // 如果还有可以放数据的空间
  if (skb_availroom(skb) > 0) {
    // 尝试拷贝待发送数据到发送缓冲区
    err = skb_add_data_nocache(sk, skb, from, copy);
  }  
  // 下面是尝试发送的逻辑代码,先省略     
}
在 tcp_sendmsg 中, 核心工作就是将待发送的数据组织按照先后顺序放入到发送缓冲区中, 然后根据实际情况(比如拥塞窗口等)判断是否要发数据。如果不发送数据,那么此时直接返回。


如果缓冲区满了会怎么办

前面提到的情况里是,发送缓冲区有足够的空间,可以用于拷贝待发送数据。

如果发送缓冲区空间不足,或者满了,执行发送,会怎么样?

这里分两种情况。

首先,socket在创建的时候,是可以设置是阻塞的还是非阻塞的。

int s = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
比如通过上面的代码,就可以将 socket 设置为非阻塞SOCK_NONBLOCK)。

当发送缓冲区满了,如果还向socket执行send

  • 如果此时 socket 是阻塞的,那么程序会在那干等、死等,直到释放出新的缓存空间,就继续把数据拷进去,然后返回

send阻塞
  • 如果此时 socket 是非阻塞的,程序就会立刻返回一个 EAGAIN 错误信息,意思是  Try again , 现在缓冲区满了,你也别等了,待会再试一次。

send非阻塞
我们可以简单看下源码是怎么实现的。还是回到刚才的 tcp_sendmsg 发送方法中。

int tcp_sendmsg()
{  
  if (skb_availroom(skb) > 0) {
    // ..如果有足够缓冲区就执行balabla
  } else {
    // 如果发送缓冲区没空间了,那就等到有空间,至于等的方式,分阻塞和非阻塞
    if ((err = sk_stream_wait_memory(sk, 
本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
换一批
延伸阅读
关闭