socket缓冲区以及阻塞模式详解

socket缓冲区以及阻塞模式详解

每个socket被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。

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

TCP协议独立于send()/recv()函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。

send()/recv()函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。

​ TCP套接字的I/O缓冲区示意图

这些I/O缓冲区特性可整理如下:

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

默认的套节字缓冲区大小可能不够用。因此我们可以想办法去修改默认缓存区大小,改成一个合适的值。

我们使用的方法是利用套节字对象的setsocketopt( )的方法修改默认的套节字缓冲区大小。

首先我们定义两个常量:SEND_BUF_SIZE和RECV_BUF_SIZE。然后在一个函数中调用套节字的实例setsocketopt( )方法。以下是具体代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
SEND_BUF_SIZE = 4096
RECV_BUF_SIZE = 4096

def modify_buff_size():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

bufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)

print "Buffer size [Before] :%d" %bufsize

sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)

sock.setsockopt(
socket.SOL_SOCKET,
socket.SO_SNDBUF,
SEND_BUF_SIZE
)
sock.setsockopt(
socket.SOL_SOCKET,
socket.SO_RCVBUF,
RECV_BUF_SIZE
)

bufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
print "Buffer size [After] :%d" %bufsize

if __name__ == '__main__':
modify_buff_size()

在套节字对象可调用方法getsocketopt( )和setsocketopt( )分别获取和修改套节字对象属性。
setsocketopt( )方法接收三个参数:level、optname和value。其中optname是选项名、value是该选项的值、level代表选项所在的协议层,以下列出了level常用的符号常量(so_*等)的意义。

在套节字对象可调用方法getsocketopt( )和setsocketopt( )分别获取和修改套节字对象属性。
setsocketopt( )方法接收三个参数:level、optname和value。其中optname是选项名、value是该选项的值、level代表选项所在的协议层,以下列出了level常用的符号常量(so_*等)的意义。

选项 意义
SO_BROADCAST 允许套接口传送广播信息
SO_DEBUG 记录调试信息
SO_DONTLINER 不要因为数据未发送就阻塞关闭操作
SO_DONTROUTE 禁止选径;直接传送
SO_KEEPALⅣE 发送“保持活动”包
SO_LINGER struct linger FAR* 如关闭时有未发送数据,则逗留
SO_OOBINLINE 在常规数据流中接收带外数据
SO_RCVBUF 为接收确定缓冲区大小
SO_REUSEADDR 允许套接口和一个已在使用中的地址捆绑(参见bind())
SO_SNDBUF 指定发送缓冲区大小
TCP_NODELAY 禁止发送合并的Nagle算法

阻塞模式

对于TCP套接字默认情况下,当使用send()发送数据时。

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

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

3) 如果要写入的数据大于缓冲区的最大长度,那么将分批写入。

4) 直到所有数据被写入缓冲区send() 才能返回。

当使用recv() 读取数据时:
1) 首先会检查缓冲区,如果缓冲区中有数据,那么就读取,否则函数会被阻塞,直到网络上有数据到来。

2) 如果要读取的数据长度小于缓冲区中的数据长度,那么就不能一次性将缓冲区中的所有数据读出,剩余数据将不断积压,直到有 recv() 函数再次读取。

3) 直到读取到数据后recv() 函数才会返回,否则就一直被阻塞。

这就是TCP套接字的阻塞模式。所谓阻塞,就是上一步动作没有完成,下一步动作将暂停,直到上一步动作完成后才能继续,以保持同步性。