内核态用户态读写流程

  1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2332713/1615132110084-01bac358-0567-4b57-a938-f534dbed51aa.png#height=265&id=kPtES&margin=%5Bobject%20Object%5D&name=image.png&originHeight=424&originWidth=800&originalType=binary&ratio=1&size=39545&status=done&style=none&width=500)

write调用的过程

  • 用户态的用户进程对socket进行write调用
  • 内核会搬运用户程序缓冲区的数据到内核写缓冲区(发送缓冲区), 搬运完毕write调用就会返回(即使缓冲区上还没有发送出去)
  • 内核TCP协议栈会把数据从内核写缓冲区(发送缓冲区)到网卡
  • 网卡在物理层把数据发送到目标网卡上,中间的网络的过程可以略过

    read调用的过程

  • 用户态的用户程序对socket进行read调用

  • 内核TCP协议栈会搬运网卡上来源的数据到内核读缓冲区(接收缓冲区)
  • 内核会搬运内核读缓冲区(接收缓冲区)的数据到用户程序缓冲区
  • 用户程序就可以在用户程序缓冲区访问到这些数据了

shutdown与close

我们先来看看unix网络编程中是如何定义这两个函数的

  1. int close(int sockfd)

close函数会对套接字引用计数(也就是引用了这个套接字描述符的进程数)减1.,一旦发现套接字引用计数到0, 就会对套接字进行彻底释放,并且在会关闭TCP两个方向的数据流并回收连接和相关资源,就是所谓的粗暴式关闭:

  • 在read方向上,内核会将该套接字设置为不可读,对套接字对read都会返回异常
  • 在write的方向上,内核尝试将发送缓冲区的数据发送给对方, 并最后向对端发送一个FIN报文。 接下来,如果再对套接字进行wiite会返回异常

    1. int shutdown(int sockfdint howto)

    shuwdown函数可以单向或者双向地关闭连接,是所谓的优雅关闭,howto设置:

  • SHUT_RD(0): 关闭连接的read方向,对该套接字进行read直接返回EOF。从数据角度来看,套接字上接收缓冲区已有对数据将被丢弃。也就是说,对端还是会接收到ACK, 在这种情况下根本不知道数据已经被丢弃

    1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2332713/1615213178981-b35f23cc-f038-40a6-b080-99069d4af802.png#height=322&id=pjpwy&margin=%5Bobject%20Object%5D&name=image.png&originHeight=644&originWidth=776&originalType=binary&ratio=1&size=82438&status=done&style=none&width=388)
  • SHUT_WR(1): 关闭连接对write方向,这就是常被称为半关闭连接。此时,不管套接字引用计数的值是多少,都是直接关闭连接的write方向,套接字上发送缓冲区已有的数据立即发送出去,并发送一个FIN报文给对端, 之后应用程序对该套接字进行write会报错

    1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2332713/1615213676540-6d197266-1fdb-48ba-8e15-2ad418753adb.png#height=323&id=CUBaa&margin=%5Bobject%20Object%5D&name=image.png&originHeight=646&originWidth=780&originalType=binary&ratio=1&size=80991&status=done&style=none&width=390)
  • SHUT_RDWR(2): 相当于SHUT_RD和SHUT_WR各执行一次,关闭套接字的read和write两个方向

    1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2332713/1615302143375-fdc2b83d-e82b-4c80-828c-13b771340439.png#height=324&id=skHiG&margin=%5Bobject%20Object%5D&name=image.png&originHeight=648&originWidth=774&originalType=binary&ratio=1&size=85163&status=done&style=none&width=387)

注意关闭的是socket而不是连接

之前分析问题的时候一直有一个以为,既然client处于半关闭,也就是只能读不能写了,那为什么还是可以发送ack给server呢?其实这里就是没有彻底理解关闭的意义,半关闭就是说socket这个套接字描述符半关闭了,不是连接本身关闭了,连接在内核态还输存在的,还可以通过内核态TCP协议栈正常通信,但是用户态的程序对socket的write调用不行了。

参考

close和shutdown关闭TCP连接