少点代码,多点头发linux
本文已经收录至个人GitHub,欢迎你们踊跃star 和 issues。git
https://github.com/midou-tech/articlesgithub
三次握手创建连接,四次挥手断开连接。这个问题算很是经典的问题,也是面试官很是喜欢问的问题。web
不夸张的说,龙叔在校招面试的时候每一家公司都问到过关于三次握手和四次挥手相关的问题,相信你们也都差很少被面试官各类怼。面试
这个问题的重要性,已经意识到。不说废话了,接下来就是听龙叔给你安排的明明白白。编程
先画个图,看下TCP的创建链接 和 断开链接的总体过程。缓存
看完这个图相信聪明的你在总体对三次握手和四次挥手有了一些基本把控。可是,里面的细节确定是会有些生疏或者模糊的,接下来就一个一个问题的揭露本质。服务器
在解释以前先看点基础知识作作铺垫。微信
状态 | 描述 |
---|---|
CLOSED | 阻塞或关闭状态,表示主机当前没有正在传输或者创建的连接 |
LISTEN | 监听状态,表示服务器作好准备,等待创建传输连接 |
SYN RECV | 收到第一次的传输请求,还未进行确认 |
SYN SENT | 发送完第一个SYN报文,等待收到确认 |
ESTABLISHED | 连接正常创建以后进入数据传输阶段 |
FIN WAIT1 | 主动发送第一个FIN报文以后进入该状态 |
FIN WAIT2 | 已经收到第一个FIN的确认信号,等待对方发送关闭请求 |
TIMED WAIT | 完成双向连接关闭,等待分组消失 |
CLOSING | 双方同时关闭请求,等待对方确认时 |
CLOSE WAIT | 收到对方的关闭请求并进行确认进入该状态 |
LAST ACK | 等待最后一次确认关闭的报文 |
首部有20字节的固定长度,含义以下:网络
各占2字节,就是存储源端口号和目的端口的
占4字节,表示的范围就是整形的范围[0~2^32]。序号使用在给数据部分每一个字节进行编号的,编号方式是mod 2^32 。
占4字节,范围也是无符号整数的范围。使用在对端传输给个人数据最后一个字节序号,例如A传输给B 101—500,此时B返回的确认号必定是小于等于501的。当B段正确接收数据以后才会返回确认号,换句话说确认号以前的数据已经所有接收。
占4bit,数据偏移不少人很容易想到是否是表示数据的长度,那就错了。偏移嘛,指的是TCP起始位置到数据部分的起始位置的偏移,也就是TCP首部的长度。
占6bit,保留字段顾名思义,就是为从此使用,默认置为0。
占用1bit,URG=1,表示紧急指针有效,此时tcp数据优先传输。至关于生活中的紧急通道,特殊状况时使用。
在网络中也会有特殊状况,例如,发送一个很长的程序在远程服务器上运行,此时发现程序有bug,须要中断运行,所以咱们从键盘输入Ctrl c,假如不使用紧急数据,须要在缓冲区里排队,都知道是bug了,还要排队,这怕是要出锅啊。
此时使用紧急数据传输,不须要排队,直接中断程序是否是更符合咱们的预期。
须要注意一点是,即便窗口为0时,也能够发送紧急数据。
如何使用紧急URG控制位,在socket编程中send函数flag参数
send(int socket, const void *buffer, size_t length, int flags);
flags参数传MSG_OOB宏时,表示此时有紧急数据。MSG_OOB是个宏,
占1bit,当ACK=1时生效。TCP有条硬性规定,当创建连接成功后全部传输的数据报文都必须把ACK置为1。
占1bit,发送方把PSH置为1时 会当即发送该数据包,接收方收到PSH=1的报文会当即处理交付给应用层处理。是否是感受和URG很像,其实仍是有些区别的。
URG与PSH二者都使用于紧急处理的状况,用来快速传输紧急数据。
URG置为1时,对于发送发,“带外数据”与正常状况下应该发送的消息数据一块儿,封装成数据报发送,省去了在队列中等待的时间。 在接收方,解析报文后,获取数据以后仍是要放在缓存区中,等待满了以后在向上往应用层交付。
PSH置为1时,对于发送方,代表这些数据不须要等向下发送的缓存区满,马上封装成报文,发送,省去了等待发送缓存区到达满的状态的时间。 在接收方,也不须要等接受缓存区满,直接向上交付给应用层。
占1bit,当RST=1时,TCP会主动释放连接,两种状况会用上。
TCP出现严重差错时,会主动释放链接,重建连接,传输数据。
遇到非法报文或者拒绝链接时会把RST置为1.
占1bit,同步控制位,用来在传输链接创建时同步传输链接序号。
SYN=1时,表示这是一个链接请求或链接确认报文。
SYN=1,ACK=0,代表这是一个链接请求数据段,若是对方赞成创建链接,则对方会返回一个SYN=一、ACK=1的确认。
占1bit,用于释放一个传输链接。
FIN=1时,表示数据已所有传输完成,发送端没有数据要传输了,要求释放当前链接,可是接收端仍然能够继续接收尚未接收完的数据。
FIN=0,正常传输数据。
占16bit,2byte,用于表示发送方能够接受的最大数据大小。
该窗口是动态变化的,用做流量控制时使用。
占16bit,2byte,用于对TCP头部,伪头部,数据三个部分进行校验。
占16bit,2byte,用于记录紧急数据的末尾在数据段中的位置。
当URG=1时,该指针才生效。
可选项最长可达40byte,是可选的,能够没有。当可选项不存在时,TCP头部长度为20byte。
可选项能够包括窗口缩放选项(Window ScaleOption,WSopt)、MSS(最大数据段大小)选项、SACK(选择性确认)选项、时间戳(Timestamp)选项等。
TCP数据部分,由应用层应用程序提交的数据。
TCP头部是基础知识,必须了解才能更好的理解TCP数据如何封装和传输,以及在创建连接和断开连接时都在操做那些地方。
从图中能够清楚的看到,三次握手的过程,我在在把过程清楚的解释一遍,顺便说下每一个过程容易被问到的知识点。
采用C/S模式解释,假设C端发起传输请求。
在发送创建连接请求以前,C端是保持CLOSED状态,S端最开始也是处于CLOSED状态,当执行listen函数套接字进入被动监听状态。
所谓被动监听,是指当没有客户端请求时,套接字处于“睡眠”状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求。
第一次:C端发送SYN=1的请求报文,此时C端进入SYN SENT状态,等待服务器确认。
C端发送报文以后会启动一个定时器,在超时以后未收到S端的确认,会再次发送SYN请求,每次尝试的时间会是第一次的二倍,若是总的总尝试时间为75秒,这次创建连接失败。
第二次:S端收到C端发送的SYN报文(创建连接请求)后,S端必须返回确认号而且同时发送一条SYN报文,此时进入SYN RCVD状态。
TCP是全双工通讯,协议规定当收到创建连接请求后必须返回序列号,同时创建本端到对端的通讯连接。这也叫作捎带应答机制。
在发送完ACK+SYN报文后会启动一个定时器,超时没有收到ACK确认,会再次发送,会进行屡次重试。超时时间依旧每次翻倍,重试次数可设置。
修改
/proc/sys/net/ipv4/tcp_synack_retries
的值![]()
第三次:C端收到S端发的ACK+SYN报文,须要返回一个应答ACK的报文,此时该链接会进入半链接状态的队列,当S端收到ACK后,一条完整的全双工TCP连接创建完成,双方进入ESTABLISHED状态。
这里有个经常使用攻击手段,攻击者伪造一个SYN请求发送给服务端,服务端响应以后,会收不到C端的ACK确认,服务端会不断的重试,默认会重试五次。
此时服务端会维持这个连接的全部资源,若是有大量这样的请求,服务端的资源会被耗完。
这就是DOS攻击。
S端在发出ACK+SYN报文后会启动一个定时器,在超时触发还没收到ACK就确认是丢失了,会重试一次发送。
这里面的每一个状态都必须搞明白,面试官也超级爱问上面的状态转移。
龙叔还遇到过一个面试官问我用过socket编程么?问我用过哪些socket函数?
C端socket编程代码
//C端
#define PORT 8080
#define BUFFER_SIZE 1024
int main(int argc, char **argv)
{
//定义IPV4的TCP链接的套接字描述符
int sock_cli = socket(AF_INET,SOCK_STREAM, 0);
//定义sockaddr_in
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
servaddr.sin_port = htons(PORT);
//链接服务器,成功返回0,错误返回-1
int ret = connect(sock_cli, (struct sockaddr *)&servaddr, sizeof(servaddr));
//客户端将控制台输入的信息发送给服务器端,服务器原样返回信息,阻塞
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
ret=send(sock_cli, sendbuf, strlen(sendbuf),0); ///发送
recv(sock_cli, recvbuf, sizeof(recvbuf),0); ///接收
fputs(recvbuf, stdout);
}
close(sock_cli); // 关闭链接
return 0;
}
S端socket编程代码
int main(int argc, char **argv)
{
//定义IPV4的TCP链接的套接字描述符
int server_sockfd = socket(AF_INET,SOCK_STREAM, 0);
//定义sockaddr_in
struct sockaddr_in server_sockaddr;
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
server_sockaddr.sin_port = htons(PORT);
//bind成功返回0,出错返回-1
if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1)
//listen成功返回0,出错返回-1,容许同时监听的链接数为QUEUE_SIZE
if(listen(server_sockfd,QUEUE_SIZE) == -1)
for(;;)
{
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
//进程阻塞在accept上,成功返回非负描述字,出错返回-1
int conn = accept(server_sockfd, (struct sockaddr*)&client_addr,&length);
//处理数据部分
...
}
close(server_sockfd);
return 0;
}
这问题问的,面试官是咋了?在这明知故问的,整些有的没的。确定是不行啊,RFC 标准就是这样写的啊。
可不敢这样回答啊,标准是说的三次握手创建连接,可没说四次不行啊。要是这样答,妥妥的会收到,同窗咱们今天的面试到此基本结束了,你回家等消息...
龙叔来讲说这个问题,为何不能两次?
若是第二次不发送SYN+ACK,只是发送确认应答消息ACK,会形成只能创建单向通讯,并且不能应答。而TCP是全双工通讯的,并且必须保证可靠性。
若是第二发送SYN+ACK,不用应答。此时会出现三种状况
1、二次握手失败,C端会重复发送SYN报文,等待对端发送确认报文,S端会保存tcp链接的全部资源,大量的这种状况会致使S资源耗尽。
2、二次握手成功,S收不到ACK会重复发送SYN+ACK报文。
3、二次握手完之后,双方觉得链接创建成功,便可开始通讯。假如此时链接并无真的创建成功,S端开始发送消息,会形成网络拥堵发生。
为何不能是四次?
四次其实原则上来讲是能够的,就是把第二次的ACK和SYN分两次发送。在理论上是彻底能够行得通的,可是TCP本着节约网络网络资源的前提。
还有一种是不拆开二次握手的捎带应答,三次握手以后C端继续发送SYN报文,其时这是徒劳的。第三次完成之后连接已经创建,后面不管多少次都是徒劳。
这就是双方同时创建连接的状况,状况还不错,反正能创建成功,这点是确定的。可是要注意两点
第1、此时只会创建一条全双工的TCP连接,不是两条。
第2、双方没有CS之分,两端都是同时承担两个角色,客户端和服务器。
先整个图看下四次挥手的整个过程和状态转移。状态转移会考看仔细点。
依旧采用C/S模式解释此过程。
第一次:当C端的应用程序结束数据传输是,会向S端发送一个带有FIN附加标记的报文段(FIN表示英文finish),此时C端进入FIN_WAIT1状态,C端不能在发送数据到S端。
第二次:S端收到FIN报文会响应一个ACK报文,S端进入CLOSE_WAIT状态。进入此状态后S端把剩余未发送的数据发送到C端,C端收到S端的ACK以后,进入FIN_WAIT2状态。
同时继续接受S端传输的其余数据包。
第三次:S端处理完本身待发送的数据以后,也会发送FIN断开连接的请求,S端进入LAST_ACK状态。
第四次:C端收到S端的断开连接请求后会启动一个定时器,该定时器时长是2MSL(最大段报文生存时间),同时发送最后一次ACK报文。
TCP是全双工的通讯机制,每一个方向必须单独进行关闭。
TCP传输链接关闭的原则以下:
当一端完成它的数据发送任务后就能够发送一个FIN字段置1的数据段来终止这个方向的数据发送;当另外一端收到这个FIN数据段后,必须通知它的应用层 对端已经终止了那个方向的数据传送。
这点到是很迷惑人,可是掌握了TCP传输的一些细节就会发现并不难。
TCP是全双工通讯的,S收到断开连接请求后只是表示C端不会传输数据到S端了,可是并不表示S端不传输数据到C端。
若是采用捎带应答,S端将没法把剩余的数据传输到C端。
网络是不可靠的,TCP是可靠协议,必须保证最后一次报文送达以后才能断开连接,不然会再次收到S端的FIN报文信息。
而等待2MSL时间就是为了保证最后最后一次报文丢失时还能从新发送。
2MSL是报文一个往返的最长时间,假设小于这个时间会发生,ACK丢了,可是还没接收到对方重传的FIN我方就从新发送了ACK。
这个不难TCP本身作了保证,TCP默认有个定时器,每次收到客户端的请求后会把定时器设置好,一般设置两小时,超过两小时还没收到数据。
服务端会发送一个探测报文,之后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭链接。
三次握手和四次挥手的知识基本告一段落了,就讲到这里了,若是有什么不明白的地方能够加我微信探讨。
后面还会出一篇网络编程经常使用的linux命令行工具,好比ping、tcpdump、netstat、nc等等,在出一篇计算机网络的总结文章。计算机网络这部分基本完结了,若是有不懂得能够看看公号里面前面的文章。