内核态用户态读写流程

write调用的过程
- 用户态的用户进程对socket进行write调用
- 内核会搬运用户程序缓冲区的数据到内核写缓冲区(发送缓冲区), 搬运完毕write调用就会返回(即使缓冲区上还没有发送出去)
- 内核TCP协议栈会把数据从内核写缓冲区(发送缓冲区)到网卡
网卡在物理层把数据发送到目标网卡上,中间的网络的过程可以略过
read调用的过程
用户态的用户程序对socket进行read调用
- 内核TCP协议栈会搬运网卡上来源的数据到内核读缓冲区(接收缓冲区)
- 内核会搬运内核读缓冲区(接收缓冲区)的数据到用户程序缓冲区
- 用户程序就可以在用户程序缓冲区访问到这些数据了
shutdown与close
我们先来看看unix网络编程中是如何定义这两个函数的
int close(int sockfd)
close函数会对套接字引用计数(也就是引用了这个套接字描述符的进程数)减1.,一旦发现套接字引用计数到0, 就会对套接字进行彻底释放,并且在会关闭TCP两个方向的数据流并回收连接和相关资源,就是所谓的粗暴式关闭:
- 在read方向上,内核会将该套接字设置为不可读,对套接字对read都会返回异常
在write的方向上,内核尝试将发送缓冲区的数据发送给对方, 并最后向对端发送一个FIN报文。 接下来,如果再对套接字进行wiite会返回异常
int shutdown(int sockfd,int howto)
shuwdown函数可以单向或者双向地关闭连接,是所谓的优雅关闭,howto设置:
SHUT_RD(0): 关闭连接的read方向,对该套接字进行read直接返回EOF。从数据角度来看,套接字上接收缓冲区已有对数据将被丢弃。也就是说,对端还是会接收到ACK, 在这种情况下根本不知道数据已经被丢弃

SHUT_WR(1): 关闭连接对write方向,这就是常被称为半关闭连接。此时,不管套接字引用计数的值是多少,都是直接关闭连接的write方向,套接字上发送缓冲区已有的数据立即发送出去,并发送一个FIN报文给对端, 之后应用程序对该套接字进行write会报错

SHUT_RDWR(2): 相当于SHUT_RD和SHUT_WR各执行一次,关闭套接字的read和write两个方向

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