Linux网络编程(七)——套接字的多种可选项

文章目录

7 套接字的多种可选项

7.1 套接字可选项和I/O缓冲大小

7.1.1 套接字多种可选项

7.1.2 getsockopt & setsockopt

7.1.3 SO_SNDBUF & SO_RCVBUF

7.2 地址再分配 SO_REUSEADDR

7.2.1 发生地址分配错误(Binding Error)

7.2.2 Time-wait状态

7.2.3 地址再分配

7.3 TCP_NODELAY

7.3.1 Nagle算法

7.3.2 禁用Nagle算法


7 套接字的多种可选项

7.1 套接字可选项和I/O缓冲大小

我们进行套接字编程时往往只关注数据通信,而忽略了套接字具有的不同特性。但是,理解这些特性并根据实际需要进行更改也很重要

7.1.1 套接字多种可选项

我们之前写的程序都是创建好套接字之后直接使用的,此时通过默认的套接字特性进行数据通信,这里列出了一些套接字可选项。

从表中可以看出,套接字可选项是分层的。

  • IPPROTO_IP 可选项是IP协议相关事项

  • IPPROTO_TCP 层可选项是TC 协议的相关事项

  • SOL_SOCKET 层是套接字的通用可选项。

7.1.2 getsockopt & setsockopt

我们几乎可以针对表中所有可选项进行读取(Get)和设置(Set),当然,有些可选项只能进行一种操作。可选项的读取和设置通过以下两个函数来完成。

#include <sys/socket.h>

int getsockopt(int sockfd, int level, int optname, 
                        void *optval, socklen_t *optlen);
/**
 * sockfd:用于查看选项套接字文件描述符
 * level:要查看的可选项的协议层
 * optname:要查看的可选项名
 * optval:指向一个变量的指针,该变量保存要查看的值
 * optlen:向第四个参数optval传递的缓冲大小。调用函数后,
 *         该变量中保存通过第四个参数返回的可选项信息的子节数(类似于缓冲区大小)
 * return:成功时返回 0,失败时返回 -1,并设置 errno 以指示错误。
 */

int setsockopt(int sockfd, int level, int optname, 
                        const void *optval, socklen_t optlen);
/**
 * sockfd:用于查看选项套接字文件描述符
 * level:要查看的可选项的协议层
 * optname:要查看的可选项名
 * optval:指向一个变量的指针,该变量包含选项的新值
 * optlen:向第四个参数optval传递的可选项信息的字节数(类似于缓冲区大小)
 * return:成功时返回 0,失败时返回 -1,并设置 errno 以指示错误。
 */

接下来介绍函数的调用方法。下面示例协议层为 SOL_SOCKET 、名为 SO_TYPE 的可选项查看套接字类型(TCP 和 UDP)

int main(int argc, char const *argv[])
{
    int sock_type,temp;
    socklen_t optlen = sizeof(sock_type);
    int tcp_sock = socket(AF_INET,SOCK_STREAM,0);
    int udp_sock = socket(AF_INET,SOCK_DGRAM,0);
    printf("SOCK_STREAM:%d\n",SOCK_STREAM);
    printf("SOCK_DGRAM:%d\n",SOCK_DGRAM);

    temp = getsockopt(tcp_sock,SOL_SOCKET,SO_TYPE,(void*)&sock_type,&optlen);
    handle_error("getsockopt_tcp",temp);
    printf("Socket type one:%d\n",sock_type);
    temp = getsockopt(udp_sock,SOL_SOCKET,SO_TYPE,(void*)&sock_type,&optlen);
    handle_error("getsockopt_udp",temp);
    printf("Socket type two%d\n",sock_type);
    return 0;
}

7.1.3 SO_SNDBUF & SO_RCVBUF

SO_RCVBUF输入缓冲大小相关可选项SO_SNDBUF输出缓冲大小相关可选项。用这 2 个可选项既可以读取当前 I/O 大小,也可以进行更改。通过下列示例读取创建套接字时默认的 I/O 缓冲大小。

int main(int argc, char const *argv[])
{
    int sed_buf,rcv_buf,temp;
    int sock = socket(AF_INET,SOCK_STREAM,0);

    socklen_t len = sizeof(sed_buf);
    temp=getsockopt(sock,SOL_SOCKET,SO_SNDBUF,(void*)&sed_buf,&len);
    handle_error("getsockopt_snd",temp);
    printf("Output buffer size:%d\n",sed_buf);

    len = sizeof(rcv_buf);
    temp=getsockopt(sock,SOL_SOCKET,SO_RCVBUF,(void*)&rcv_buf,&len);
    handle_error("getsockopt_rcv",temp);
    printf("Inupt buffer size:%d\n",rcv_buf);
    return 0;
}

这是我系统中的运行结果,与各位运行结果可能有较大差异,接下来的程序将更改 I/O 缓冲大小

int main(int argc, char const *argv[])
{
    int sed_buf = 1024*3,rcv_buf = 1024*3,temp;
    int sock = socket(AF_INET,SOCK_STREAM,0);
    
    socklen_t len = sizeof(sed_buf);
    temp = setsockopt(sock,SOL_SOCKET,SO_SNDBUF,(void*)&sed_buf,len);
    handle_error("getsockopt_snd",temp);
    printf("Output buffer size:%d\n",sed_buf);

    len = sizeof(rcv_buf);
    temp = setsockopt(sock,SOL_SOCKET,SO_RCVBUF,(void*)&rcv_buf,len);
    handle_error("getsockopt_rcv",temp);
    printf("Inupt buffer size:%d\n",rcv_buf);
    
    return 0;
}

7.2 地址再分配 SO_REUSEADDR

7.2.1 发生地址分配错误(Binding Error)

不知道大家有没有发现这么一个现象,在前面回声服务器端/客户端的代码里,如果我们先在服务器端输入 CTRL+C 强制关闭服务器,那么服务器端重新运行时将产生问题。如果用同一端口重新运行服务器端,将输出 “bind:Address already in use” 消息,并且无法再次运行,在这种情况下,再过几分钟即可重新运行服务器端。然而,如果在客户端输入 Q/q 或 CTRL+C 关闭服务器端,再次运行时服务器仍能正常运行。

7.2.2 Time-wait状态

假设图中主机 A 是服务器,因为是主机 A 向 B 发送 FIN 消息,故可想象成服务器端在控制台中输入 CTRL+C 。但问题是,套接字经过四次握手后并没有立即消除,而是要经过一段时间的 Time-wait 状态当然,只有先断开连接的(先发送 FIN 消息的)主机才经过 Time-wait 状态。因此,若服务器端先断开连接,则无法立即重新运行。因为套接字正处在 Time-wait 状态,相应端口正处在使用的状态。因此,就像之前验证过的,bind 函数调用过程中会发生错误。

有些人会误以为 Time-wait 过程只存在于服务器端。但实际上,不管是服务器端还是客户端,套接字都会有Time-wait过程。先断开连接的套接字必然会经过 Time-wait 过程。但无需考虑客户端Time-wait 状态。因为客户端套接字的端口号是任意指定的。与服务器端不同,客户端每次运行程序时都会动态分配端口号,因此无需过多关注 Time-wait 状态。

那到底为什么会有 Time-wait 状态呢?在图中假设,主机 A 向主机 B 传输 ACK 消息(SEQ 5001 , ACK 7502 )后立刻消除套接字。但是最后这条 ACK 消息在传递过程中丢失,没有传递主机 B ,这时主机 B 就会试图重传。但是此时主机 A 已经是完全终止状态,因此主机 B 永远无法收到从主机 A 最后传来的 ACK 消息。

7.2.3 地址再分配

Time-wait 状态看似重要,但是不一定讨人喜欢。如果系统发生故障紧急停止,这时需要尽快重启服务起以提供服务,但因处于 Time-wait 状态而必须等待几分钟。因此,Time-wait 并非只有优点,这些情况下容易引发大问题。

从图可以看出,在主机 A 四次握手的过程中,如果最后的数据丢失,则主机 B 会认为主机 A 未能收到自己发送的 FIN 信息,因此重传。此时 A 将重启 Time-wait 计时器,如果网络状况不理想, Time-wait 将持续。解决方案就是在套接字的可选项中更改 SO_REUSEADDR 的状态。适当调整该参数,将处于 Time-wait 状态下的套接字端口号重新分配给新的套接字。SO_REUSEADDR 的默认值为 0,这就意味着无法分配 Time-wait 状态下的套接字端口号。因此需要将这个值改成 1 。

int option = 1;
int optlen = sizeof(option);
setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (void *)&option, optlen);

7.3 TCP_NODELAY

7.3.1 Nagle算法

为了防止因数据包过多而发生网络过载,Nagle 算法诞生了。它应用于 TCP 层。它是否使用会导致下图所示的差异

图中展示了通过 Nagle 算法发送字符串 Nagle 和未使用 Nagle 算法的差别。可以得到一个结论只有接收到前一数据的 ACK 消息, Nagle 算法才发送下一数据。

  • TCP 套接字默认使用 Nagle 算法交换数据,因此最大限度的进行缓冲,直到收到 ACK 。左图为了发送字符串 “Nagle”,将其传递到输出缓冲。此时头字符 “N” 之前没有其他数据,因此立即传输。之后开始等待 N 字符的 ACK 消息,等待过程中将剩下的 “agle” 填入输出缓冲。收到 N 字符的 ACK 消息后,将输出缓冲的 “agle” 装入一个数据包发送。也就是说,共传递4个数据包以传递一个字符串。
  • 右图的发送过程与 ACK 接收与否无关,因此数据到达输出缓冲后立即被发送出去,此时可以看到,发送同样一个字符串一共传递了 10 个数据包。

由此可知,不使用 Nagle 算法将对网络流量产生负面影响。即使只传输一个字节的数据,其头信息都可能是几十个字节。因此,为了提高网络传输效率,必须使用 Nagle 算法。

Nagle 算法并不是什么情况下都适用,网络流量未受太大影响时,不使用 Nagle 算法要比使用它时传输速度快。最典型的就是 “传输大文件数据”。将文件数据传入输出缓冲不会花太多时间,因此,即使不使用 Nagle 算法,也会在装满输出缓冲时传输数据包。这不仅不会增加数据包的数量,反而在无需等待 ACK 的前提下连续传输,因此可以大大提高传输速度。所以,未准确判断数据性质时不应禁用 Nagle 算法。

7.3.2 禁用Nagle算法

禁用方法非常简单,只需要将套接字可选项 TCP_NODELAY 改为1(真)

int opt_val = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *)&opt_val, sizeof(opt_val));

可以通过 TCP_NODELAY 的值查看 Nagle 算法的设置状态

opt_len = sizeof(opt_val);
getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *)&opt_val, &opt_len);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值