前面分析过了理论的三次握手的过程以及为什么需要三次握手, 本节我们再以socket编程的方面分析三次握手的过程.
从编程角度分析三次握手

三次握手, 三个函数, 分别是listen, connect和accept.
服务端调用listen函数一直监听网络中有没有要与服务端连接的客户端, 并且**listen初始化未决连接队列**.
未决连接队列分为未完成连接队列和已完成连接队列, 这里是我统一的简称, 为了说明方便.
未完成连接队列 : 服务端还未完成三次握手全部过程的一个队列.
已完成连接队列 : 服务端已经完成三次握手全部过程的一个队列, 等待
accept函数从这个队列中返回下一个已连接的(返回其实是取出, 该套接字不在已完成队列中了)套接字.注意 : 两个队列总和的个数都不超过
listen函数设置的值.
以下较为重要的地方用加粗提醒
- 客户端使用
connect函数向服务端发送连接请求, 服务端调用listen监听到有连接到来并且连接队列没有满, 就将此连接加入未完成连接队列中, 并且初始化ISN后向客户端发送ACK确认. - 当客户端调用
connect函数, 等待收到确认后connect函数才返回返回正确的值并确认, 此时服务端也收到ACK后, 将连接从未完成连接队列移动到已完成连接队列中. - 服务端调用
accept函数检测到已完成连接队列中有连接时停止阻塞, 从已完成队列中取出就绪连接并返回, 从而两端开始进行通信.
ok! 相信大家已经看完了三次握手的基本过程了, 下面对几个重要的地方做实验验证也是大家容易忽略的地方.
1. connect什么时候返回
认真看过前面的应该能够知道connect是收到服务端的ack就会返回. 为了验证我们来做一个实验吧.
完整代码 : return_server.c
修改了两处:
服务端 : 在accept 加上死循环, 保证accept没有被执行.
while(1); // +
clientfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
./a.out 1 1234
客户端 : 添加连接成功提示
if(connect(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) != 0)
EXIT("connect");
printf("connect successful\n"); // +
./a.out 2 127.0.0.1 1234


通过实验和抓包都可以看出来我们完成了三次握手.
小结 : connect在accept调用之前返回.
2. 没有执行accept是否可以连接成功
完成上面的问题之后, 这个问题其实可以直接回答. 可以.
如果觉得不对可以尝试把accept函数删除试试, 其实就是跟上面死循环是一样的.
3. listen未决队列
回顾 : listen有已完成连接队列和未完成连接队列, 而listen的第二个参数默认为两者的总和. 当调用accept之后会从已完成连接队列中取出一个连接.
查百度资料 [1] : SYN_RECV表示半连接状态; ESTABLISHED表示已连接状态.
验证 : 现在我们来验证是否存在listen的两个队列. 继续使用上面的例子来验证, 上述例子的listen的代码如下 :
listen(sockfd,1);
服务端 :
./a.out 1 8080
多个客服端连接 :
./a.out 2 127.0.0.1 8080
通过netstat查看状态
netstat -nt 8080
运行 :

状态:

可能是在本机验证, 结果不准确. 但我们确实验证了listen的这两个队列是存在的.
总结
本节的两个实验其实在前面理论中已经讲清楚了的, 实验只是为了加深印象毕竟只是看一遍理论能记住的时间可能也比较短暂. 还有一个listen连接队列的验证没有实现, 这个对实验好像在一个主机上实现不了.
- 网络编程的三次握手过程
connect的返回时间

本文从编程角度详细分析了TCP三次握手的过程,包括connect函数的返回时机、未执行accept时的连接状态以及listen未决队列的运作。通过实验验证了在三次握手期间,即使未执行accept,客户端和服务器也能完成连接,同时揭示了listen未决队列中未完成和已完成连接队列的存在。
357

被折叠的 条评论
为什么被折叠?



