C++学习笔记(55)

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 算法。

相关推荐
俸涛努力学前端15 分钟前
ajax (一)
开发语言·前端·javascript·笔记·ajax
花糖纸木32 分钟前
算法练习:76. 最小覆盖子串
c++·算法
键盘敲没电1 小时前
【iOS】知乎日报总结
学习·ios·objective-c·xcode
慕卿扬1 小时前
基于python的机器学习(四)—— 聚类(一)
笔记·python·学习·机器学习·聚类
家有狸花1 小时前
Node.js笔记(二):Socket.io
笔记·node.js
虾球xz1 小时前
游戏引擎学习第22天
学习·游戏引擎
45W冲冲冲2 小时前
WIN10+CMAKE+MinGW+Opencv/C++ 和VScode开发环境搭建
c++·vscode·opencv
春蕾夏荷_7282977252 小时前
MFC 对话框中显示CScrollView实例
c++·mfc·cscrollview
micro_xx2 小时前
Matlab 深度学习工具箱 案例学习与测试————求二阶微分方程
深度学习·学习·matlab
等什么君!2 小时前
学习Servlet( Servlet实现方式2)
学习·servlet