应用层:应用程序通过系统调用API(如socket)创建一个TCP套接字(socket),并设置好相关的选项。
传输层:当应用程序调用connect函数建立TCP连接时,TCP协议开始进行三次握手协议:
a. 客户端向服务端发送SYN数据包(SYN_SENT状态);
b. 服务端收到SYN数据包后,向客户端发送SYN+ACK数据包(SYN_RCVD状态);
c. 客户端收到SYN+ACK数据包后,向服务端发送ACK数据包(ESTABLISHED状态)。
网络层:TCP数据包被封装在IP数据包中,经过网络层进行路由选择和传输。
数据链路层:TCP/IP数据包被封装在以太网帧中,通过数据链路层进行物理传输。
传输层:当数据到达目的地后,TCP协议会进行四次挥手协议:
a. 客户端向服务端发送FIN数据包(FIN_WAIT_1状态);
b. 服务端收到FIN数据包后,向客户端发送ACK数据包(CLOSE_WAIT状态);
c. 当服务端完成数据发送后,向客户端发送FIN数据包(LAST_ACK状态);
d. 客户端收到FIN数据包后,向服务端发送ACK数据包,此时连接关闭(CLOSED状态)。
- TCP在建立连接的时候,采用的是三次握手,在断开连接的时候,采用的是四次挥手 。
这里的重点是三次握手和四次挥手,接下来我们重点讲解一下。
Linux三次握手是指TCP连接建立的过程中,客户端和服务端之间通过交换三个数据包来确认彼此的身份和建立连接的过程。
第一次握手:客户端发送SYN(同步)数据包给服务端,请求建立连接。(SYN数据包包含客户端的初始序列号(seq number),用于同步客户端和服务端的数据传输。)
第二次握手:服务端收到SYN数据包后,发送一个SYN-ACK(同步-确认)数据包给客户端。(SYN-ACK数据包包含服务端的初始序列号和对客户端SYN数据包的确认。)这个阶段,服务端已经准备好了接收来自客户端的数据。
第三次握手:客户端收到SYN-ACK数据包后,发送一个ACK(确认)数据包给服务端。(ACK数据包包含客户端对服务端SYN-ACK数据包的确认,以及客户端的初始序列号。)此时,客户端和服务端都已经准备好了通信,可以开始传输数据。此时,客户端和服务端之间的TCP连接已经建立完成。
就比如你跟你同学说周末到他家里玩(SYN)希望他能够同意。你同学知道了你要到他家玩(收到SYN)表示你那天啥时候来都行(SYN-ACK)让你知道他同意了。
当你知道了同学同意你去他家后,你就要为周末去他家做准备(发送ACK)。
当服务器完成套接字的创建、绑定和监听后,可以调用accept函数阻塞等待客户端发起连接请求。
服务端初始化的过程:
创建套接字:使用socket()函数创建一个套接字,该套接字可以用于接受客户端连接请求。
绑定端口:使用bind()函数将套接字绑定到一个具体的端口上,以便客户端可以通过这个端口来连接服务器。
监听连接:使用listen()函数将套接字设置为监听状态,以便接受客户端的连接请求。
接受连接:使用accept()函数等待客户端的连接请求,当客户端发起连接请求时,accept()函数会返回一个新的套接字描述符,用于和客户端进行通信。
处理请求:使用新的套接字描述符和客户端进行通信,处理客户端发送过来的请求,并发送响应给客户端。
关闭连接:当客户端请求处理完成后,关闭套接字描述符,释放相应的资源。
- 需要注意的是,在服务端处理请求的过程中,可能会有多个客户端同时发起连接请求,因此需要使用多线程或多进程的方式来处理这些请求。同时,服务端也需要进行异常处理,防止因为网络问题或其他原因导致服务器异常退出。
接下来就是客户端与服务器的交流了。
客户端与服务器建立连接的过程如下:
客户端创建一个套接字:使用socket()函数创建一个套接字,用于与服务器进行通信。
设置连接参数:使用connect()函数将套接字连接到服务器的地址和端口上。
发起连接请求:使用connect()函数发起连接请求,向服务器发送一个SYN数据包,表示客户端请求连接。
等待服务器响应:客户端会等待服务器的响应,如果服务器接受了连接请求,就会发送一个SYN+ACK数据包回来,表示服务器愿意建立连接。
确认服务器响应:客户端接收到服务器的SYN+ACK数据包后,会发送一个ACK数据包,表示客户端确认连接请求已经成功建立。
建立连接:当服务器接收到客户端发送的ACK数据包后,连接就正式建立了,客户端和服务器可以开始进行通信。
这个建立连接的过程,通常称为三次握手。
- 需要注意的是,连接并不是立马建立成功的,由于TCP属于传输层协议,因此在建立连接时双方的操作系统会自主进行三次协商,最后连接才会建立成功。
- 建立连接后,TCP协议提供全双工的通信服务,所谓全双工的意思是,在同一条连接中,同一时刻,通信双方可以同时写数据,相对的概念叫做半双工,同一条连接在同一时刻,只能由一方来写数据。
- 服务器从accept返回后立刻调用read,读socket就像读管道一样,如果没有数据到达就阻塞等待。
- 这时客户端调用write发送请求给服务器,服务器收到后从read返回,对客户端的请求进行处理,在此期间客户端调用read阻塞等待服务器端应答。
- 服务器调用write将处理的结果发回给客户端,再次调用read阻塞等待下一条请求。
- 客户端收到后从read返回,发送下一条请求,如此循环下去。
示例:
客户端使用send()函数将数据发送给服务器。发送的数据可以是任何格式的数据,例如字符串、二进制数据等。
服务器使用recv()函数接收客户端发送的数据。在接收数据之前,服务器需要先使用accept()函数接受客户端的连接请求,获取到一个新的套接字描述符。服务器从这个新的套接字描述符中读取客户端发送的数据。
服务器使用send()函数将响应数据发送给客户端。响应数据可以是任何格式的数据,例如字符串、二进制数据等。
客户端使用recv()函数接收服务器发送的响应数据。
客户端和服务器之间进行多次数据的传输,直到完成数据的传输任务。
四次挥手的过程就是两台主机断开通信连接的过程。
TCP连接的四次挥手过程如下:
客户端发送一个FIN报文,表示客户端已经没有数据要发送给服务器了,并要求服务器关闭连接。客户端进入FIN_WAIT_1状态。
服务器接收到客户端发送的FIN报文后,向客户端发送一个ACK报文作为响应。此时,服务器进入CLOSE_WAIT状态,等待应用程序关闭连接。
如果服务器有尚未发送的数据,会在数据发送完毕后再发送一个FIN报文给客户端,表示服务器已经没有数据要发送了。服务器进入LAST_ACK状态。
客户端接收到服务器发送的FIN报文后,向服务器发送一个ACK报文作为响应。此时,客户端进入TIME_WAIT状态,等待一段时间后才关闭连接。服务器接收到客户端发送的ACK报文后,进入CLOSED状态,连接关闭。
端口连接
- 当双方通信结束之后,需要通过四次挥手的方案使双方断开连接,当客户端调用close关闭连接后,服务器最终也会关闭对应的连接。而其中一次close就对应两次挥手,因此一对close最终对应的就是四次挥手。
- 其中最后一次挥手是为了确保数据传输的完整性和可靠性,避免数据重复和丢失等问题。在断开连接之前,需要确保所有的数据都已经被传输完毕,并且各个状态和缓存已经被正确处理和释放,避免可能的资源泄露和数据损失。
需要注意的是,客户端进入TIME_WAIT状态的目的是为了确保服务器接收到客户端发送的ACK报文,避免可能的数据重传和延迟。在客户端等待的这段时间内,客户端不会再向服务器发送任何数据,同时等待的时间也是由TCP协议栈决定的,通常为2倍的最大段生存时间
- TCP四次挥手的主要作用是安全地关闭连接并释放资源。在TCP连接中,一旦完成了数据的传输,就需要关闭连接以释放占用的资源,避免资源浪费和不必要的等待。
- 四次挥手的过程可以保证数据传输的完整性和可靠性,避免出现数据丢失和错误。同时,四次挥手的过程也可以保护连接的安全性,避免数据被未经授权的用户访问和篡改。
- 另外,四次挥手过程中,服务器可以先关闭连接并等待客户端的确认,避免出现客户端长时间占用连接资源的情况。这样可以有效地避免连接的滥用和资源的浪费。
- 总之,TCP四次挥手过程是保证连接可靠性、数据完整性和安全性的重要步骤,是网络通信中必不可少的一部分。