351、三次握手与四次挥手
TCP 是面向连接的、可靠的协议,建立 TCP 连接需要三次对话(三次握手),拆除 TCP 连接需要四
次对话(四次握/挥手)。
一、三次握手
服务端调用 listen()函数后进入监听(等待连接)状态,这时候,客户端就可以调用 connect()函数
发起 TCP 连接请求,connect()函数会触发三次握手,三次握手完成后,客户端和服务端将建立一个双向
的传输通道。
情景类似:
1、客户端对服务端说:我可以给你发送数据吗?
2、服务端回复:ok,不过,我也要给你发送数据。(这时候,客户端至服务端的单向传输通道已建
立)
3、客户端回复:ok。(这时候,服务端至客户端的单向传输通道已建立)
细节:
1)客户端的 socket 也有端口号,对程序员来说,不必关心客户端 socket 的端口号,所以系统随机
分配。(socket 通讯中的地址包括 ip 和端口号,但是,习惯中的地址仅指 ip 地址)
2)服务端的 bind()函数,普通用户只能使用 1024 以上的端口,root 用户可以使用任意端口。
3)listen()函数的第二个参数+1 为已连接队列(ESTABLISHED 状态,三次握手已完成但是没有被
accept()的 socket,只存在于服务端)的大小。(在高并发的服务程序中,该参数应该调大一些)
4)SYN_RECV 状态的连接也称为半连接。
5)CLOSED 是假想状态,实际上不存在。 二、四次挥手(握手)
断开一个 TCP 连接时,客户端和服务端需要相互总共发送四个包以确认连接的断开。在 socket 编程
中,这一过程由客户端或服务端任一方执行 close()函数触发。
情景类似:
1)一端(A)对另一端(B)说:我不会给你发数据了,断开连接吧。
2)B 回复:ok。(这时候 A 不能对 B 发数据了,但是,B 仍可以对 A 发数据)
3)B 发完数据了,对 A 说:我也不会给你发数据了。(这时候 B 也不能对 A 发数据了)。
4、A 回复:ok。
细节:
1)主动断开的端在四次挥手后,socket 的状态为 TIME_WAIT,该状态将持续 2MSL(30 秒/1 分
钟/2 分钟)。 MSL(Maximum Segment Lifetime)报文在网络上存在的最长时间,超过这个时间报
文将被丢弃。
2)如果是客户端主动断开,TIME_WAIT 状态的 socket 几乎不会造成危害。a)客户端程序的 socket
很少,服务端程序的 socket 很多(成千上万);b)客户端的端口是随机分配的,不存在重用的问题。
3)如果是服务端主动断开,有两方面的危害:a)socket 没有立即释放;b)端口号只能在 2MSL
后才能重用。
在服务端程序中,用 setsockopt()函数设置 socket 的属性(一定要放在 bind()之前)。
int opt = 1;
setsockopt(m_listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
352、TCP 缓存
系统为每个 socket 创建了发送缓冲区和接收缓冲区,应用程序调用 send()/write()函数发送数据的
时候,内核把数据从应用进程拷贝 socket 的发送缓冲区中;应用程序调用 recv()/read()函数接收数据的
时候,内核把数据从 socket 的接收缓冲区拷贝应用进程中。
发送数据即把数据放入发送缓冲区中。
接收数据即从接收缓冲区中取数据。
查看 socket 缓存的大小:
int bufsize = 0;
socklen_t optlen = sizeof(bufsize);
getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &bufsize, &optlen); // 获取发送缓冲区的大小。
cout << "send bufsize=" << bufsize << endl;
getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &bufsize, &optlen); // 获取接收缓冲区的大小。
cout << "recv bufsize=" << bufsize << endl;
问题:
1)send()函数有可能会阻塞吗? 如果自己的发送缓冲区和对端的接收缓冲区都满了,会阻塞。
2)向 socket 中写入数据后,如果关闭了 socket,对端还能接收到数据吗?
Nagle 算法
在 TCP 协议中,无论发送多少数据,都要在数据前面加上协议头,同时,对方收到数据后,也需要
回复ACK表示确认。为了尽可能的利用网络带宽,TCP希望每次都能够以MSS(Maximum Segment Size,
最大报文长度)的数据块来发送数据。
Nagle 算法就是为了尽可能发送大块的数据,避免网络中充斥着小数据块。
Nagle 算法的定义是:任意时刻,最多只能有一个未被确认的小段,小段是指小于 MSS 的数据块,
未被确认是指一个数据块发送出去后,没有收到对端回复的 ACK。
举个例子:发送端调用 send()函数将一个 int 型数据(称之为 A 数据块)写入到 socket 中,A 数据
块会被马上发送到接收端,接着,发送端又调用 send()函数写入一个 int 型数据(称之为 B 数据块),
这时候,A 块的 ACK 没有返回(已经存在了一个未被确认的小段),所以 B 块不会立即被发送,而是等
A 块的 ACK 返回之后(大概 40ms)才发送。
TCP 协议中不仅仅有 Nagle 算法,还有一个 ACK 延迟机制:当接收端收到数据之后,并不会马上向
发送端回复 ACK,而是延迟 40ms 后再回复,它希望在 40ms 内接收端会向发送端回复应答数据,这样
ACK 就可以和应答数据一起发送,把 ACK 捎带过去。
如果 TCP 连接的一端启用了 Nagle 算法,另一端启用了 ACK 延时机制,而发送的数据包又比较小,
则可能会出现这样的情况:发送端在等待上一个包的 ACK,而接收端正好延迟了此 ACK,那么这个正要
被发送的包就会延迟 40ms。
解决方案
开启 TCP_NODELAY 选项,这个选项的作用就是禁用 Nagle 算法。
#include <netinet/tcp.h> // 注意,要包含这个头文件。
int opt = 1;
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY,&opt,sizeof(opt));
对时效要求很高的系统,例如联机游戏、证券交易,一般会禁用 Nagle 算法。
C++学习笔记(55)
最新推荐文章于 2024-11-12 13:55:23 发布