干货都在图片里,请放大仔细阅读。
首先用户进程发个程序给服务器的时候,这个网络数据包会在TCP这层,加上TCP头和TCP尾。随后在IP层加上IP头和IP尾。
随后到网卡这里变成0101传输到网络上。
随后在以太网里传输,一直传到服务器的网卡。网卡一旦发现有数据了,它会发送一个中断信号。随后在硬件链路层,会去掉MAC头MAC尾。往上传输给IP层,去掉IP头和IP尾,随后上传至TCP层,去掉TCP头和TCP尾。到达服务器的应用程序。在同样的方式把RESPONSE传回去给客户端。
这里面有个高效的思想是层与层之间的传输不是通过拷贝。而是通过C语言的指针。会在数据的前后端都预留一定的空间,用来加头尾。然后这些操作都是通过指针移动来实现的。一直到底层的时候,会变成一个完整的MAC协议。
服务器虎仔ACCEPT的时候阻塞。一直到有一个客户端使用了CONNECT。 这个是ACCEPT()函数会收到一个FD,文件描述符。这个FD是用来进行数据收发的。
客户端发送完请求会使用close()函数来关闭连接。当服务器知道了这个CLOSE(),它也会调用CLOSE()
1、TCP建立连接的三次握手过程
(1)第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
(2)第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
(3)第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
2.TCP关闭连接的三次握手过程
由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭,上图描述的即是如此。
(1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
1.为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?
这是因为服务端的LISTEN状态下的SOCKET当收到客户端的SYN报文的建立连接请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
2.为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?
这是因为虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到 ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于 LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的 ACK报文。
我们先看下同步IO的流程:
然后再看下异步IO:
linux 中是没有真正的异步IO,真正的异步IO是说在内核态去实现的AIO。
而LINUX现在是在用户态用软件模拟出AIO的效果。
综上我们可以有3种不同的方案。
第一种就是一个线程一个客户端,这个就是BIO的应用。这种在网络并发量小的时候,性能是可以的,而且编程比较简单。
但是再网络并发量大的时候,会因为有很多线程间调度,造成性能不好,吞吐量上不去。
所以我们需要下面2种方式。
第一种就是用NIO+IO多路复用技术。也就是SELECT,POLL,EPOLL。和JAVA NIO的包为什么做的。
他可以用一个线程去监听所有的请求。
最后异步IO也是一个线程可以处理多个客户端请求。