TCP Socket连接原理

Posted by CHuiL on April 6, 2019

connect()

在调用connect()函数的时候,会激发TCP三次握手,直到成功或者失败了才会返回,而且返回的失败中有 1、超时 2、收到RST(复位)响应,表示服务主机没有对应的端口进程在等待连接,硬错误; 3、若收到ICMP的主机不可达错误,是会想保存该错误信息然后当作超时处理在重新发送SYN分组,因为该错误时可能被修复的;

非阻塞connetc()

由于conncet()调用后需要等待一个RTT时间才能返回,这期间可以用来处理其他工作; 主要是配合select()来使用;connect()之后,将文件描述符设置进rwset中,因为连接成功后返回可读可写的socket;select即监听connect()是否完成,也监听已建立连接的socket是否可读可写,可通过设置对应的状态值来进行判断;在connect()返回后,还要判断连接是否成功;

bind()

bind()函数为socet绑定一个IP地址,端口地址或两者,如果不指定将有内核分配,ip地址为本机所在ip,端口随机分配临时端口;

listen()

listen()函数,让socket变为被动(成为服务器)接受连接,他有两个队列,分别是 1)未完成队列:表示接受到客户端的SYN分组,然后返回给SYN分组后建立的项(SYN_RCVD状态);只到第三个报文到达或者超时为止(超时了应该就是不保留这个项,重新发送的时候在重写建立新的项) 2)完成队列:表示完成三次握手的项; 如果未完成队列满了,那么服务端tcp将对后续的SYN分组不做处理,即无视,让客户端去超时重传;

accept()

accept()函数,除去listen完成队列中的一个返回连接,并生成一个“已连接套接字”,区分与上面调用了lisnten的监听套接字,该套接字是在建立连接后创立,传输完成后关闭,监听套接字在服务器监听期间一直存在;若完成队列为空,那么该进程将会被阻塞;

非阻塞accept()

非阻塞accept()一般都是在select检查到有连接建立时,并且在已完成建立的队列里面有对应的项返回后,在直接调用accept()去获取,一般不会阻塞,但是考虑一种情况,就是select()返回后,客户端那边发送了RST报文断开了连接,然后已完成队列中对于断开的连接是直接去除掉该项的,此时若队列为空,那么accept将会阻塞,直到有新的连接建立;

close()

当我们调用close()函数之后,他会同时关闭该socket的读写,即不能在作为read和write的参数进行读写;而如果需要在写完数据之后关闭写半部,即发送一个fin给另一端,但同时还要能够接受剩余的数据,如接受另一端的EOF,此时就需要使用shutdown()函数; shutdown函数关闭socket的写半部,同时只要一调用就会立马发送fin分组;而close()只是将对该socket文件描述符的引用数-1,当文件描述符引用数为0时,才会发送Fin分组;

连接的过程

服务客户端正常启动的过程

  1. 服务端socket处于accept阻塞
  2. 客户端建立socket,然后调用connect()来与服务器建立连接,发送SYN,经历三次握手,在第二次握手后返回;
  3. 服务器在收到SYN后会在未完成连接中加入一项,然后返回ACK和SYN,等第三个报文到达后进入完成连接队列,在被accept()取出,服务器拿到已连接socket后进行处理,然后再尝试从accept中获取;
  4. 双方建立连接后开始传输数据

服务客户正常关闭

  1. 客户端这边率先close()掉套接字,从而使Tcp向服务端发送一个fin报文,进入TIME_WAIT1
  2. 服务端收到后发送ACK报文,进入CLOSE_WAIT()状态,客户端收到后进入TIME_WAIT2状态;
  3. 等待服务器close()后,服务端tcp发送一个fin报文后进入LAST_ACK状态
  4. 客户端收到后在发送ACK 报文,进入TIME_WAIT状态,服务端收到后正式关闭连接;

所以只要一端close()后底层就会发送一个fin报文;而调用后立即返回;

不正常关闭连接

不正常关闭,服务器进程关闭(服务器进程提前关闭了socket)

服务器子进程关闭,调用close(),发送fin并收到ack后,而客户进程由于一直阻塞在等待用户输入,所以当用户输入完毕,打算往服务端socket中写入数据时(此时客户端是处于CLOSE_WAIT状态,是可以继续写入的),由于服务器那边已经没有该socket了,也即在端口上没有监听的进程,所以会返回RST,该socket也就不能继续写了,写了内核会发送SIPIPE信号来终止进程(默认),如果捕获后返回用函数,则会返回EPIPE错误;读了会返回0(EOF);

服务器主机断网

客户端在读写时陷入阻塞,直到不断的超时重传过一定的时间次数后返回错误(ETIMEOUT);

服务器主机崩溃重启(关机,重启)

重启之后,由于之前tcp的所有连接信息都已经丢失,所以对客户端发来的请求由于找不到对应的socket,都会返回RST; 在服务器崩溃关机时,进程会关闭,就会调用close(),所以客户端将会受到fin报文,客户端可以使用select等复用技术来接收到这些信息;

参考

《Unix网络编程-第四章》