套接字可选项
套接字具有多种特性,这些特性可通过可选项更改,本篇文章将介绍更改套接字可选项的方法,并以此为基础进一步观察套接字内部
前面介绍了套接字通信的基本函数套接字(Socket)编程(一) 函数概念篇和套接字通信的基本原理接字(Socket)编程(二) 内部通信原理,有需要了解的可以看我前面的两篇文章。
我们之前写程序都是在创建好套接字之后(未经特殊操作)直接使用的,此时通过默认的套接字特性进行同性。之前的示例都较为简单,无需特别操作套接字特性,但有时的确需要更改。
一、可设置套接字的多种可选项
套接字可选项是分层的,不同的协议成可设置的套接字可选项是不一样的
| 协议层 | 功能 |
|---|---|
| SOL_SOCKET | 套接字相关通用可选项的设置 |
| IPPROTO_IP | 在 IP 层设置套接字的相关属性 |
| IPPROTO_TCP | 在 TCP 层设置套接字相关属性 |
下面我们列出套接字三个协议层部分可选项,并对其中常用的做下介绍和使用示例
| SOL_SOCKET 选项名 | 说明 | 数据类型 |
|---|---|---|
| SO_DEBUG | 打开或关闭调试信息 | int |
| SO_BROADCAST | 允许或禁止发送广播数据 | int |
| SO_DONTROUTE | 打开或关闭路由查找功能 | int |
| SO_ERROR | 获得套接字错误 | int |
| SO_KEEPALIVE | 开启套接字保活机制 | int |
| SO_REUSEADDR | 是否启用地址再分配,主要原理是操作关闭套接字的 Time-wait 时间等待的开启和关闭 | int |
| SO_LINGER | 是否开启延时关闭,开启的情况下调用 close() 函数会被阻塞,同时可以设置延迟关闭的超时时间,如果到超时未发送完数据则直接复位套接口的虚电路(属于异常关闭),如果超时时间内发送完数据则正常关闭套接字回调 close()函数 |
struct linger |
| SO_TYPE | 获得套接字类型(这个只能获取,不能设置) | int |
| SO_RCVBUF | 接收缓冲区大小 | int |
| SO_SNDBUF | 发送缓冲区大小 | int |
| SO_RCVLOWAT | 接收缓冲区下限 | int |
| SO_SNDLOWAT | 发送缓冲区下限 | int |
| SO_RCVTIMEO | 接收超时 | struct timeval |
| SO_SNDTIMEO | 发送超时 | struct timeval |
| IPPROTO_IP 选项名 | 说明 | 数据类型 |
|---|---|---|
| IP_MULTICAST_TTL | 生存时间(Time To Live),组播传送距离 | int |
| IP_ADD_MEMBERSHIP | 加入组播 | int |
| IP_DROP_MEMBERSHIP | 离开组播 | int |
| IP_MULTICAST_IF | 取默认接口或默认设置 | int |
| IP_MULTICAST_LOOP | 禁止组播数据回送 | int |
| IP_HDRINCL | 在数据包中包含 IP 首部 | int |
| IP_OPTINOS | IP 首部选项 | int |
| IPPROTO_TCP 选项名 | 说明 | 数据类型 |
|---|---|---|
| TCP 保活机制开启下,设置保活包空闲发送时间间隔 | int | |
| TCP_KEEPINTVL | TCP 保活机制开启下,设置保活包无响应情况下重发时间间隔 | int |
| TCP_KEEPCNT | TCP 保活机制开启下,设置保活包无响应情况下重复发送次数 | int |
| TCP_MAXSEG | TCP 最大数据段的大小 | int |
| TCP_NODELAY | 不使用 Nagle 算法 | int |
二、getsockopt & setsockopt
我们几乎可以对照上面所有可选项进行读取和设置(当然有些套接字只能进行其中的一中操作),可选项的读取和设置通过下面两个函数完成
#include <sys/socket.h>
int getsockopt(int sock, //用于查看选项套接字描述符
int level, //要查看可选项的协议层
int optname, //要查看可选项名字
void *optval, //保存查看结果的缓冲地址值
socklen_t *optlen //指向第四个参数optval传递的缓冲大小的指针
)
//成功时返回0,失败返回-1
#include <sys/socket.h>
int setsockopt(int sock, //用于更改可选项套接字描述符
int level, //要更改可选项的协议层
int optname, //要更改可选项名字
void *optval, //要更改可选项的值
socklen_t optlen //第四个参数optval传递的可选项信息的字节数
)
//成功时返回0,失败返回-1
三、应用举例
下面来介绍有关可选项的设置读取和示例,我将从我认为重要的往下列举
1. SO_KEEPALIVE
SO_KEEPALIVE 是否开启 TCP 的保活机制,平时开发中,常见的对于面向连接的 TCP 连接,断开分两种情况
- ①、连接正常关闭,调用
close()会经历断开的四次握手,send()、recv()立马返回错误; - ②、连接的对端异常关闭,比如网络断掉或突然断电,这种断开对方是检测不到连接出现异常的
解决这种异常断开的检测机制一般有两种:
- ①、自己在应用层定时发送心跳包来判断连接是否正常,此方法比较通用,灵活可控,但改变了现有的协议;
- ②、使用 TCP 的keepalive机制,TCP 协议自带的保活功能,使用起来简单,减少了应用层代码的复杂度, 推测也会更节省流量,因为一般来说应用层的数据传输到协议层时都会被加上额外的包头包尾,由 TCP 协议提供的检活,其发的探测包,理论上实现的会更精妙(用更少的字节完成更多的目标),耗费更少的流量;
keepalive 原理:TCP 内嵌有心跳包,以服务端为例,当 server 检测到超过一定时间(tcp_keepalive_time 7200s 即 2 小时)没有数据传输,那么会向 client 端发送一个 keepalive packet,此时 client 端有三种反应:
- client 端连接正常,返回一个 ACK,server 端收到 ACK 后重置计时器,在 2 小时后在发送探测(如果 2 小时内连接上有数据传输,那么在该时间的基础上向后推延 2 小时发送探测包)
- 客户端异常关闭或网络断开,client 无响应,server 收不到 ACK,在一定时间(
tcp_keepalive_intvl75 即 75 秒)后重发keepalive packet, 并且重发一定次数(tcp_keepalive_probes9 即 9 次),仍然得不到相应则返回超时 - 客户端曾经崩溃,但已经重启,server 收到的探测响应是一个复位,server 端终止连接
我们可以根据自己的需求来修改这三个参数的系统默认值,下面我们来通过一个简单的回响服务器代码来实现保活机制的逻辑实现
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
//这个头文件里面包含设置TCP协议层保活三个参数的可选项
#include <netinet/tcp.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
int serv_sockfd, clnt_sockfd;
struct sockaddr_in serv_addr,clnt_addr;
serv_addr.sin_len = sizeof(struct sockaddr_in);
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(2000);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (bind(serv_sockfd, (struct sockaddr*)&serv_addr, serv_addr.sin_len) < 0) return 0;
if (listen(serv_sockfd, 1) < 0) return 0;
socklen_t len_t = sizeof(struct sockaddr_in);
clnt_addr.sin_len = len_t;
clnt_sockfd = accept(serv_sockfd, (struct sockaddr*)&clnt_addr, &len_t);
if (clnt_sockfd < 0) return 0;
close(serv_sockfd);
printf("有客户端连接 IP:%s Port:%d\n",inet_ntoa(clnt_addr.sin_addr),ntohs(clnt_addr.sin_port));
//1. 开启keepalive机制
int keepAlive = 1; // 开启keepalive属性. 缺省值: 0(关闭)
if (setsockopt(clnt_sockfd, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(keepAlive)) == -1)
printf("开启keepalive机制失败 errorCode:%d descreption:%s\n",errno,strerror(errno));
//2. 设置保活包检测发送时间间隔(TCP_KEEPIDLE 好像被替换为 TCP_KEEPALIVE)
int keepIdle = 10; // 如果在60秒内没有任何数据交互,则进行探测. 缺省值:7200(s)
if (setsockopt(clnt_sockfd, IPPROTO_TCP, TCP_KEEPALIVE, &keepIdle, sizeof(keepIdle)) == -1)
printf("设置保活包检测发送时间间隔 errorCode:%d descreption:%s\n",errno,strerror(errno));
//3. 设置保活包发送无响应重发时间间隔
int keepInterval = 5; // 探测时发探测包的时间间隔为5秒. 缺省值:75(s)
if (setsockopt(clnt_sockfd, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(keepInterval)) == -1)
printf("设置保活包发送无响应重发时间间隔 errorCode:%d descreption:%s\n",errno,strerror(errno));
//4. 设置保活包发送无响应重发次数
int keepCount = 3; // 探测重试的次数. 全部超时则认定连接失效..缺省值:9(次)
if (setsockopt(clnt_sockfd, IPPROTO_TCP, TCP_KEEPCNT, &keepCount, sizeof(keepCount)) == -1)
printf("设置保活包发送无响应重发次数 errorCode:%d descreption:%s\n",errno,strerror(errno));
char recv_buffer[512];
while (1) {
memset(recv_buffer, 0, 512);
ssize_t recv_len = recv(clnt_sockfd, recv_buffer, sizeof(recv_buffer), 0);
if (recv_len == 0) {
printf("收到数据包长度为0,可能是EOF包 errorCode:%d descreption:%s\n",errno,strerror(errno));
break;
}else if (recv_len < 0) {
printf("读取数据出错 errorCode:%d descreption:%s\n",errno,strerror(errno));
break;
}else {
printf("recv: %s\n",recv_buffer);
ssize_t sendLen = send(clnt_sockfd, recv_buffer, recv_len, 0);
if (sendLen < 0) {
printf("发送失败 errorCode:%d description:%s\n",errno,strerror(errno));
}else {
printf("send: %s\n",recv_buffer);
}
}
}
printf("服务器关闭\n");
close(clnt_sockfd);
}
return 0;
}
对于运行结果的总结:
上面示例设置
- TCP_KEEPALIVE :设置保活包空闲发送时间间隔为 10s
- TCP_KEEPINTVL:设置保活包无响应情况下重发时间间隔为 5s
- TCP_KEEPCNT:设置保活包无响应情况下重复发送次数为 3 次
计算出来,如果对方 TCP 连接异常,服务器这边可以在 10+5*3 = 25 大概 25s 左右的时间内检测出异常,并在读取数据时抛出 errno 为 60 原因为 Operation timed out 的错误
2. SO_REUSEADDR
SO_REUSEADDR 是否启用地址再分配,设置这个可选项将影响套接字关闭的 Time-wait 状态,还是比较重要的一个可选项。
在第二篇文章接字(Socket)编程(二) 内部通信原理里面有讲到套接字关闭时的四次握手原理,从图中看出主动关闭的一方在接收到对方的 FIN 包后有一个 ACK 确认,然后进入 Time-wait 状态,当时讲到这个 Time-wait 等待状态主要的为了确认对方已经收到我们最后一个 ACK 确认消息,确认收到则关闭,没有或者超时则重发最后的确认包,上篇文章也详细的介绍了为什么有这个等待状态,这里就不做过多的重说了,需要了解的可以点击前面连接查看。
PS:这个 Wait-time 状态只发生在主动断开连接的一方,被断开一方是没有这个状态的。
作为服务器,通常都是客户端主动断开,这个时候没有 Wait-time 状态,所以不会发生特别的事情,重启也不成问题,但有的时候由于自己异常需要重启,自己作为断开的一方,系统对于这个 Socket 断开有 Wait-time 状态,重新运行起来绑定相同的地址和端口就会出现问题,系统会返回“bind error”的错误,因为这个端口正在被使用,这个时候需要 Time-wait 时间用完,大概 2 分钟,再运行服务器就可以绑定该端口了!
但是一般服务器是不能等这个时间的,有用户在那重启一次可能已经造成损失,再停个两分钟损失更大,这个时候就需要设置套接字可选项SO_REUSEADDR值为1,去掉最后的 Wait-time 时间。
#include <sys/socket.h>
int enableReuseAddr(int sock)
{
int optval = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) {
return 0;
}
return 1;
}
3. SO_LINGER
SO_LINGER 此选项从字面意思上来看就是延迟关闭,我们可以这么理解,在调用关闭套接字的情况下,设置了此参数就可以让系统尽量把缓冲区的数据发送出去(因为有超时时间),但是对于此参数的设置分为四种情况,下面我们来列举下
- ①. 设置结构体里面参数
l_onoff=0时,为内核缺失情况(系统默认情况),关闭套接字时系统正常调用close()函数 - ②. 设置结构体里面参数
l_onoff=1非 0 时,但是参数l_linger=0超时时间为 0 时,在关闭套接字时会向对方发送 RST 信号给对方(非正常的四次握手关闭),对方会读取到 Socket 重置的错误(errno = 45 \* Connection reset by peer *\) - ③. 设置结构体里面参数
l_onoff=1非 0 时,但是参数l_linger设置的超时时间太小,系统在规定的超时时间内没有将全部的数据发送出去,这个时候情况和上面第二种情况l_linger=0时是一样的,直接重置 Socket 向对方发送 RST 信号(非正常的四次握手关闭) - ④. 设置结构体里面参数
l_onoff=1非 0 时,参数l_linger设置的超时时间足够,在发送完全部数据后系统正常调用close()函数正常关闭
#include <sys/socket.h>
int enableLinger(int sock)
{
struct linger so_linger;
so_linger.l_onoff = 1; //开启(非0) 关闭(0)
so_linger.l_linger = 5; //滞留时间,设置为大于0时才起作用
if (setsockopt(sock, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger)) == -1) {
return 0;
}
return 1;
}
套接字关闭(主要针对 TCP 套接字断开)
可能通过上面的介绍,我们理解起来还是感觉哪不对劲,到底 Socket 关闭都经历了什么?套接字的几种关闭方式有什么区别?设置超时和不超时有什么区别?貌似直接调用 close() 函数,系统也是将数据全部发送给对方后才关闭….带着这些疑问,我们来断下面的分析
①. cloes 函数关闭:我们最常用的关闭套接字方式,说到这种关闭我们再次不得不提下多个进程使用同一个套接字(我看过一本有关网络编程的书,里面提到 fork 出子进程来使用同一个套接字,章标题叫编写多进程服务器端,书名叫《TCP/IP 网络编程》是韩国人写的),当多个进程使用同一个 Socket 时,其中某一个进程调用 close() 函数只是使套接字的引用计数 -1 ,此时该进程无法使用该 Socket 进行读写操作,其他使用该 Socket 的进程可以进行正常通信,当引用计数减到 0 时,系统才会开始释放该套接字,下面我们来说下该函数关闭的过程。
系统默认(就是在没有设置 SO_LINGER 可选项的情况下)在调用 close() 时,只是将 EOF 字段(即 FIN 报文段)写进套接字缓冲区,写进缓冲区就返回,这就是说调用 close() 函数返回,并不代表已经成功将 FIN 报文段发送给对方(拿 A、B 两个主机来举例)。
- A 主动断开,调用
close()函数将 EOF 字段(FIN 报文段)写进输出缓冲,系统等待前面的数据传送完再将后来加进缓冲区的 EOF 字段(FIN 报文段)发送给对方 - B 主机系统在收到 A 的 FIN 报文段会立即向对方回送一个 ACK 确认包(表示我收到了你的 FIN 报文段)
- B 主机的应用层这时调用
recv()函数会收到 EOF 字段(EOF 字符长度为 0,也就是recv()函数返回接收到数据长度为 0),就知道 A 要断开连接了,自己处理完数据也调用close()向对方发送 FIN 报文段 - A 主机系统在收到对方的 FIN 报文段后,会向对方发送 ACK 确认,确认发送成功之后系统释放掉该套接字,B 收到 ACK 后也会释放掉要关闭的套接字,关闭到此结束
上面就是调用 cloes() 函数经历的四次握手断开,看文字难理解可以结合我前面的一篇文章里面的套接字断开四次握手图对比着理解。
②. 套接字设置了 SO_LINGER 可选项下,调用 close 函数关闭:与第 ① 种情况不同的是,在调用 close() 函数并不是立即返回,超时时间足够的情况下,会等待里面所有的数据发送完以及最后调用 close() 函数加进输出缓冲的 FIN 报文段也发送给对方之后,才会返回,在此之前阻塞了调用 close() 函数的线程。
这个时候就会有疑问,这个可选项会在什么时候用到呢?设和不设置 SO_LINGER 可选项系统都会将数据都发送给对方后再释放套接字,只不过一个是写进输出缓冲就返回,一个是等发送成功才返回。我在开发中就遇到了一个必须设置该可选项的情况,因为我是做智能家居开发方面,其中有一种配置单片机联网方式就是连接上单片机的 LAN 网络,这个时候手机跟单片机在同一个局域网下,就可以跟单片机建立 TCP 连接并将路由器的 WiFi 名字和密码发送给对方,单片机收到后解析完会给手机端发送一个数据包,表示自己已经收到联网需要的信息然后重启自己连上该路由器,但有时网络不好这个信息并不一定发给手机端(单片机那边只是调用 send()函数返回后再close() 关闭套接字),这时只能说明数据已经写进单片机的输出缓冲,并不代表已经发送给手机端,然后紧接着重启,这时所有的数据都丢失,导致手机端应用层并不知道设备有没有收到发送的指令,也不知道设备有没有解析出报文连接上路由器,此时如果用 SO_LINGER 可选项对套接字进行设置就很容易解决这个问题,调用 close() 函数直到发送 FIN 报文段成功才会返回。
③. shutdown 半关闭套接字:
从字面意思上我们很容易理解,半关闭就是要么关闭套接字的输入流要么关闭套接字的输出流,要么两个都关闭。
int shutdown(int sock, //需要断开套接字的文件描述符
int howto //传递断开方式信息
);
//成功时返回0.失败返回-1
howto的可选值分别有如下三种
#define SHUT_RD 0 断开输入流
#define SHUT_WR 1 断开输出流
#define SHUT_RDWR 2 同时断开I/O流
- ①. 填写
SHUT_RD断开输入流,这时输出流还可以接着发送数据,这时调用recv()一直返回读取数据长度为 0,第一次是原因是“No such file or directory”,后面都是“Operation timed out” - ②. 填写
SHUT_WR断开输出流,这时输入流还可以接着读取数据,套接字对方调用recv()一直返回读取数据长度为 0,第一次可以理解为收到对方的 EOF 报文,输出原因是“No such file or directory”,后面都是“Operation timed out”,这时自己调用send()函数会抛出 SIGPIPE 信号量,内核缺失情况下项目停止运行 - ③. 填写
SHUT_RDWR同事断开 I/O 流,原理上就是先调SHUT_RD然后再调一次SHUT_WR
shutdown() 函数与 close()函数最大的区别就是不管大少个进程用同一个套接字,前者只要其中一个进程调用了半关闭,其他的进程使用该套接字也将进入半关闭状态
4. SO_RCVTIMEO & SO_SNDTIMEO
SO_RCVTIMEO 设置套接字读取数据的超时时间,SO_SNDTIMEO 设置套接字发送数据的超时时间。
#include <sys/socket.h>
int setRecvTimeout(int sock)
{
//设置套接字读取数据超时时间为5s
struct timeval timeout;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) == -1) {
return 0;
}
return 1;
}
int setSendTimeout(int sock)
{
//设置套接字发送数据的超时时间为5s
struct timeval timeout;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) == -1) {
return 0;
}
return 1;
}
这个超时在平时的开发中用来设置 TCP 套接字倒是不多,一般 UDP 套接字用的比较多,我前面的一篇iOS 开发-TFTP 客户端和服务器的实现文章里面在用 UDP 套接字发送数据给对方,对方收到之后会用 UDP 回发一个 ACK 包,这个时候我们才知道对方收到了我的上一个数据包,接着发送下一个数据包,这个里面就用到了套接字的超时选项设置,在规定的时间内没有收到对方的 ACK 确认包,则重发!UDP 套接字发送数据不用建立连接,所以数据发送出去并不知道对方是否收到,这样一应一答的通信设计用超时可选项进行超时设置是再合适不过!
4. SO_RCVBUF & SO_SNDBUF
SO_RCVBUF 设置或获取套接字输入缓冲区的大小,这个缓冲区是我们在创建套接字时系统为我们创建好的,SO_SNDBUF 设置或获取套接字输出缓冲区的大小
#include <sys/socket.h>
int getRecvBufferSize(int sock)
{
int recvSize;
socklen_t len;
if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &recvSize, &len) == -1) {
return 0;
}else {
printf("套接字的输入缓冲大小是: %d\n",recvSize);
}
return recvSize;
}
int setSendBufferSize(int sock)
{
int sendSize;
socklen_t len;
if (getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sendSize, &len) == -1) {
return 0;
}else {
printf("套接字的输出缓冲大小是: %d\n",sendSize);
}
return sendSize;
}
我自己写了一个客户端的TCP套接字,打印了一下看,这个应该以系统为准,系统不一样分配的大小也不一样!
套接字的输入缓冲大小是: 408300
套接字的输出缓冲大小是: 146988
5. SO_RCVBUF & SO_SNDBUF
SO_RCVLOWAT 可选项用来设置套接字接收缓冲区下限,对于 TCP 套接字而言,一般被 I/O 复用系统用来判断套接字是否可读,当套接字缓冲区中可读数据总数大于我们设置的接收缓冲区下限时,I/O 复用系统调用将通知应用程序可以从对应的 socket 上读取数据(触发 select() 函数 或 Linux 独有的 epoll() 函数),调用 recv() 函数才会返回,得到输入缓冲区中的数据。
SO_SNDLOWAT 可选项用来设置套接字发送缓冲区的下限,当 TCP 套接字发送缓冲区中空闲空间(可以写入数据的空间)大于设置的发送缓冲区下限时,I/O 复用系统调用将通知应用程序可以往对应的 socket 上写入数据,调用 send() 函数才有返会(才能将数据写进输出缓冲中)。
默认情况下,TCP 套接字接收缓冲区的下限和 TCP 套接字发送缓冲区的下限标记均为 1 字节
#include <sys/socket.h>
int setRcvBufferLowerLimit(int sock)
{
//设置套接字接收下限为10个字节
int opval = 10;
if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &opval, sizeof(opval)) == -1) {
return 0;
}
return 1;
}
int setSendBufferLowerLimit(int sock)
{
//设置套接字发送缓冲区的下限为10个字节
int opval = 10;
if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &opval, sizeof(opval)) == -1) {
return 0;
}
return 1;
}
6. SO_TYPE
SO_TYPE 获取套接字类型,只能用来获取而不能用来设置
#include <sys/socket.h>
int getSocketType(int sock)
{
int opval;
socklen_t opvalLen;
if (getsockopt(sock, SOL_SOCKET, SO_TYPE, &opval, &opvalLen) == -1) {
return 0;
}else {
if (opval == SOCK_DGRAM) {
printf("UDP套接字\n");
}else if (opval == SOCK_STREAM) {
printf("TCP套接字\n");
}else {
printf("套接字类型为: %d\n",opval);
}
}
return opval;
}
7. SO_BROADCAST
SO_BROADCAST 允许或禁止发送广播数据,以及后面有关多播的参数设置(IPPROTO_IP协议层的IP_MULTICAST_TTL设置多播传输距离,IP_ADD_MEMBERSHIP加入多播组,IP_DROP_MEMBERSHIP离开多播组…),都会在我的下篇文章里面做详细的介绍,这里就不做过多累述了。
8. TCP_NODELAY
TCP_NODELAY 是否禁用 Nagle 算法。
为了防止网络数据包过多而发生网络过载,Nagle 算法在 1984 年诞生了,他应用于 TCP 层,非常简单,是否使用差异看下图

TCP 套接字默认使用 Nagle 算法进行数据交换,因此最大限度的进行缓冲,只有收到上个包的确认 ACK 后才会传输下个数据包。
在不使用 Nagle 算法的情况下发送数据,不需要等待收到上个数据包的 ACK 确认,接着就开始发送下面的数据,这种情况下会对网络流量产生极大的负面影响,即使只传输 1 个字节的数据,其头信息都有可能是几十个字节。
因此为了提高网络传输效率,必须使用 Nagle 算法。
但是对于大文件的网络传输,有时不使用 Nagle 算法能提高传输速度
int enableNagel(int sock)
{
//禁用Nagle算法
int opval = 1;
if (setsockopt(sock, SOL_SOCKET, TCP_NODELAY, &opval, sizeof(opval)) == -1) {
return 0;
}
return 1;
}
四、结语
对于套接字可选项的总结就到这,开发中常用的都已经列举出来了,有遗漏的日后会补上。