socket网络编程 要疯了,到底什么是网络编程?
来源|蛇叔编程头脑作者|蛇叔介绍
不知道你有没有这样的经历。在网上搜索技术类文章时,总会看到网络编程这个词,各大互联网公司都渴求掌握网络编程的人才。其实网络编程无处不在,我们平时使用的互联网产品都与网络编程技术息息相关。只有掌握网络编程,才能在纷繁复杂的网络世界中看透问题的本质,轻松解决网络相关的技术问题。
目录
#客户端
你好-回声
#服务器端
你好-回声
此时,我们通过nc将字符串发送到服务器,服务器将按原样将字符串发送回我们。nc和echoServer的整个交互过程是怎样的?下图显示了详细的过程。
网络交互图
互动过程的详细说明
众所周知,TCP有三次握手,四次挥手,我们的echo服务器就是基于TCP协议的。当然,三次握手,四次挥手,缺一不可。
服务器|建立套接字内核数据结构
首先,我们需要创建 socket 内核数据结构,通过 socket 系统调用,我们可以告诉内核我们要建立基于 ipv4 的 tcp socket套接字 ,内核会维护一个 sock 数据结构并和一个文件相绑定,同时给我们返回一个 socketfd ,供后续函数使用。之后,我们需要通过 bind ,告知内核将哪个地址绑定到该socket内核数据结构。然后,使用 listen 系统调用,将 socket 转换为 已监听套接字 。此时,服务器就可以进行被动连接了。客户端|启动活动连接;服务器|被动连接
如果此时客户端通过 connect 系统调用发起主动连接,客户端内核协议栈,向服务器发送三次握手的第一步 SYN 。当服务器收到这个 SYN ,会把该套接字放入半连接队列,并向客户端发送 ACK、SYN 。当客户端接受到这个 ACK、SYN ,并向服务器发送 ACK 。此时客户端 connect 系统调用返回,客户端认为三次握手完成。当服务器收到客户端传来的 ACK ,则将内核中的套接字放入全连接队列,等待服务器调用 accept ,并返回给服务器。服务器|等待连接的套接字
当服务器调用 accept , 如果此时全连接队列中没有已完成三次握手的socket,则默认会阻塞,直到全连接队列中拥有已经完成三次握手的socket。accept 会为之前的监听套接字 sock 内核数据结构,构建一个新的文件,并分配新的 fd ,这个文件称为 已连接套接字 。此时,服务器和客户端就可以收发数据了。服务器|客户端|发送和接收数据
为什么收发数据需要调用读写?
在内核中,看到的 acceptfd ,本质和文件一样, acceptfd 就是文件描述符,所以我们可以直接使用 read 、 write 这种操作文件的系统调用。linux 内核为每个已连接套接字,分配一个 接受缓冲区 和一个 发送缓冲区 。对于 read ,是本机读取 acceptfd 或者 socketfd 相关的 socket接收缓冲区 ,如果 socket接收缓冲区 中有数据, read 返回,否则 read 会阻塞,直到 socket接收缓冲区 中拥有了对端数据。这个接受数据的过程是由内核 TCP/IP 协议栈实现的。对于 write ,写入的是 socket发送缓冲区 ,如果此时 发送缓冲区 是满的, write 则会被阻塞。写入 socket发送缓冲区 的数据,由内核 TCP/IP 协议栈真实的发往对端。这里有几点:
read 返回 大于0 ,表示读取成功。read 返回 等于0 ,表示对端关闭连接,此时应该调用 Close 关闭连接。read 返回 小于0 ,表示读操作产生错误。write 返回 小于0 ,表示写操作产生错误。客户端|服务器|关闭连接
在 nc 所在的终端上键入 Ctrl+c ,结束掉 nc 进程。此时内核协议栈,会给服务器发送 FIN Tcp节,告知本端已经关闭。服务端收到客户端的FIN,内核协议栈会给客户端回复 ACK ,同时 read 调用会返回 0 ,这样服务器就知道客户端关闭连接了。当客户端收到该FIN后,内核协议栈回复ACK,自身并进入 TIME_WAIT 状态, Linux 下等待 2MSL , 也就是 60秒 。至此,echoServer与nc之间的整个交互过程已经说明。整个过程是一个正常的网络交互过程,很多不正常的边界没有讨论。
网络问答
假如一方断网了,不能进行4次挥手,服务器会怎么处理?默认未断开一端,会保持 established 状态。未断开一端 开启 了 Keepalive ,则会定期往对端发送 TCP 保活 Segment ,对端协议栈回复 RST ,未断开端就会知道已经出现异常,关闭本端连接。如果未断开一端 没有开启 Kepalive ,则一直不知道对端已经关闭,直到往对端写数据会得到 Connection reset by peer 错误,进而知道对端已经关闭。这种情况下,如果未断开端再次往断开对端写数据,则会产生EPIPE错误。所以通常需要服务器在 应用层 做保活心跳,对这种情况的连接做 定时踢掉 处理。 客户端发送FIN之后,会发生什么?TCP是全双工通信,也就是双通道通信4次握手中,主动关闭端发送FIN,是告诉被动关闭端我不会再给你发送数据了,你不要再在acceptFd上读取数据了,此时服务端再读取数据,会返回 0 ,也就是 EOF 。如果服务端不需要发送数据给客户端,通常需要调用close,服务端向客户端发送FIN,也就是4次握手的第3步。如果服务端有数据要发送给客户端,此时依旧可以通过 socketfd 发送数据给客户端,这也是通常说的TCP半关闭状态。后记通过前面的解释,我们其实可以发现,TCP/IP协议和socketApi是紧密相关的。然而,它们有许多语义上的差异。很多时候,想当然并不是你想的那样。例如,close系统调用是否会将FIN发送到对端,取决于所连接套接字的引用计数是否达到0。如果多个进程共享此文件,其中一个进程将关闭,并且不会将FIN Tcp部分发送到另一端。像这样的细节很多,可见学好网络编程不是一门简单的知识。
参考
《TCP/IP详解 卷1》《Unix网络编程 卷1》《计算机网络》