面试官:代码执行 send 后,数据就发出去了吗?
扫描二维码
随时随地手机看文章
Socket 缓冲区
什么是 socket 缓冲区
编程的时候,如果要跟某个IP建立连接,我们需要调用操作系统提供的socket API
。socket 在操作系统层面,可以理解为一个文件。我们可以对这个文件进行一些方法操作。用listen
方法,可以让程序作为服务器监听其他客户端的连接。用connect
,可以作为客户端连接服务器。用send
或write
可以发送数据,recv
或read
可以接收数据。在建立好连接之后,这个 socket 文件就像是远端机器的 "代理人" 一样。比如,如果我们想给远端服务发点什么东西,那就只需要对这个文件执行写操作就行了。那写到了这个文件之后,剩下的发送工作自然就是由操作系统内核来完成了。既然是写给操作系统,那操作系统就需要提供一个地方给用户写。同理,接收消息也是一样。这个地方就是 socket 缓冲区。用户发送消息的时候写给 send buffer(发送缓冲区)用户接收消息的时候写给 recv buffer(接收缓冲区)也就是说一个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 缓冲区。至 什么时候会发数据,发多少数据,全听操作系统安排。在用户进程中,程序通过操作 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 是阻塞的,那么程序会在那干等、死等,直到释放出新的缓存空间,就继续把数据拷进去,然后返回。
- 如果此时 socket 是非阻塞的,程序就会立刻返回一个
EAGAIN
错误信息,意思是Try again
, 现在缓冲区满了,你也别等了,待会再试一次。
tcp_sendmsg
发送方法中。int tcp_sendmsg()
{
if (skb_availroom(skb) > 0) {
// ..如果有足够缓冲区就执行balabla
} else {
// 如果发送缓冲区没空间了,那就等到有空间,至于等的方式,分阻塞和非阻塞
if ((err = sk_stream_wait_memory(sk,