iOS面试

iOS 网络相关面试题(TCP、三次握手、四次挥手、代码实现)

2019-08-08  本文已影响15人  程序员_秃头怪

一、TCP的特点和报文结构

1、面向连接、可靠传输、面向字节流、全双工服务

2、TCP的报文结构

TCP报文段由首部字段和一个数据字段组成。
数据字段包含一块应用数据。最大报文长度MSS(Maximum Segment Size)限制了报文段数据字段的最大长度。MSS选项用于在TCP连接建立时,收发双方协商通信时每一个报文段所能承载的最大数据长度。
所以当TCP发送一个大文件(比如一张高清图片)时,通常是将该文件划分为MSS长度的若干块(最后一块除外,通常会小于MSS)。而实际交互式应用通常传送长度小于MSS的数据块。

如图,与UDP一样,首部包括源端口号目的端口号,用于多路复用/分解来自上层或送到上层应用的数据。TCP首部也同样包括检验和字段

TCP首部还包含下列字段:

3、序号字段Seq和确认号字段Ack

序号Seq
TCP把数据看成一个无结构的、有序的字节流。一个报文段的序号因此是该报文段的首字节的字节流编号。
比如数据流由一个包含100000字节的文件组成,其MSS1000字节,数据流的首字节编号是0。该TCP将为该数据流构建100个报文段。给第一个报文段分配序号0,第二个则是1000,第三个是2000,以此类推。每一个序号被填入到相应TCP报文段首部的序号字段中。

确认号Ack:
TCP是全双工服务的,因此主机A在向主机B发送数据的同时,也许也在接收主机B的数据。
主机A填充进报文段的确认号是主机A期望从主机B收到的下一个字节的序号。

在上个例子中,假如服务端已经接收包含字节0-999的报文段和包含字节2000-2999的报文段,但由于某种原因,还未收到包含字节1000-1999的报文段,那么将仍会等待字节1000(及其后的字节)。因此服务端发给客户端的下一个报文段将在确认号Ack字段中包含1000。
因为TCP只确认该流中至第一个丢失字节为止的字节,所以TCP被称为累积确认

二、三次握手

-数据开始传输前,需要通过 三次握手来建立连接
事实上我认为,这里称呼三步握手(three-way handshake)才更贴切些

第一步:

第二步:

第三步:

  1. List item

三、四次挥手

参与TCP连接的两个进程中的任何一个都能终止该连接,当连接结束后,主机中的资源(缓存和变量)会被释放。

上边说到,SYN和FIN标志位分别对应着TCP连接的建立和拆除。

第一步:

第二步:

第三步:

第四步:

一些问题

1、问:为什么建立连接只用三次握手,而断开连接却要四次挥手?

2、在四次挥手中,客户端为什么在TIME_WAIT后必须等待2MSL时间呢?

这个ACK报文段有可能丢失,因而使处在LAST_ACK端的服务端收不到对已发送的FIN报文段的ACK报文段,从而服务端会去不断重传FIN报文段。
而客户端就能在2MSL时间内收到重传的FIN报文段。接着客户端重传一次确认,重新启动2MSL计时器。直至服务端收到后,客户端和服务端就都会进入CLOSED状态,关闭TCP连接。
而如果客户端不等待2MSL时间,而是在发送完ACK确认后立即释放资源,关闭连接,那么就无法收到服务端重传的FIN报文段,因而也不会再发送一次ACK确认报文段,这样,服务端就无法正常进入CLOSED状态,资源就一直无法释放了。

3、TCP在创建连接时,为什么需要三次握手而不是两次或四次?

一个简单的例子:

之所以不用四次握手的原因很容易理解,就是浪费资源,服务端的SYNACK可以一起发,完全没必要分开两次。

而如果是两次握手
客户端发出的第一个连接请求SYN报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达服务端。本来这是一个早已失效的报文段。但服务端收到此失效的连接请求SYN报文段后,就误认为是客户端再次发出的一个新的连接请求SYN报文段。于是就向客户端发出ACK确认报文段,同意建立连接。假设不采用三次握手,那么只要服务端发出确认,新的连接就建立了。
由于现在客户端并没有发出建立连接的SYN请求,因此不会理睬服务端的确认,也不会向服务端发送数据。但服务端却以为新的运输连接已经建立,并一直等待客户端发来数据。这样,服务端的很多资源就白白浪费掉了。

事实上:TCP对有数据的TCP报文段必须确认的原则,所以,客户端对服务端的SYN报文段必须回复一个ACK报文段表示确认。并且,TCP不会为没有数据的ACK超时重传,那么当服务端没收到客户端的ACK确认报文段时,会超时重传自己的SYN报文段,一直到收到客户端的ACK为止。

代码实现

参考UDP的代码,其实TCP在代码实现上也很相似,首先·socket·初始化时不再用·SOCK_DGRAM·,而是用·SOCK_STREAM·

fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

TCP服务端要多了一道监听、接受连接的过程:

int listen_ret = listen(fd,5);
int listen_socket = accept(_fd,(sockaddr *)&addr,&addr_len);

UDP则是多了一道连接的过程:

int ret = connect(_fd, (struct sockaddr *) &addr, sizeof(addr));

然后就是在接收和发送数据时,不用再传主机和端口了。即由recvfrom、sendto改为recvsend

send(_fd, [buffer bytes], [buffer length], 0);
recv(_fd, receiveBuffer, sizeof(receiveBuffer), 0);

python的客户端代码如下:

from socket import *
serverName = '127.0.0.1'
serverPort = 12000
clientSocket = socket(AF_INET,SOCK_STREAM)
clientSocket.connect((serverName,serverPort))
sentence = raw_input('Input lowercase:\n')
clientSocket.send(sentence)
modifiedSentence = clientSocket.recv(1029)
print 'From server:\n',modifiedSentence
clientSocket.close()

服务端代码:

from socket import *
serverPort = 12000
serverSocket = socket(AF_INET,SOCK_STREAM)
serverSocket.bind(('',serverPort))
serverSocket.listen(1)
print 'server is ready to receive'
connectionSocket,addr = serverSocket.accept()
sentence = connectionSocket.recv(1029)
capitalizeSentence = sentence.upper()
print capitalizeSentence
connectionSocket.send(capitalizeSentence)
connectionSocket.close()

热文推荐

2019 全网 iOS 面试题以及答案总结!

上一篇下一篇

猜你喜欢

热点阅读