《TCP/IP网络编程》(第九章)套接字的多种选项

1.套接字的多种可选项

在这里插入图片描述
SOL_SOCKET的选项是套接字相关的选项
IPPROTO_IP的可选项是IP协议相关的选项
IPPROTO_TCP的可选项是TCP协议相关的选项

接下来会简单介绍几个选项和用法

2.对选项信息进行读取和设置

使用getsockopt()setsockopt()进行读取和设置,语法如下:

2.1 Linux系统

2.1.1 getsockopt()读取套接字选项

int getsockopt(
int sockfd,           //sockfd:要查看的套接字的文件描述符。
int level,            //level:要查看的协议层,如SOL_SOCKET。
int optname,          //要查询的选项的名称。
void *optval,         //指向一个缓冲区,该缓冲区将接收查询的选项值。
socklen_t *optlen     //向optval缓冲区传递的缓冲大小。调用成功后,它会被设置为选项值的实际长度。
);  

2.1.2 setsockopt()设置套接字选项

int setsockopt(
int sockfd,            //sockfd:要设置的套接字文件描述符。
int level,             //level:要设置的协议层,如SOL_SOCKET。
int optname,           //optname:要设置的选项的名称。
const void *optval,    //optval:指向包含要设置的选项值的缓冲区。
socklen_t optlen       //optval缓冲区的长度。
);

2.2 Windows系统

2.2.1 getsockopt()读取套接字选项

int getsockopt(
    SOCKET s,
    int level,
    int optname,
    char *optval,
    int *optlen
);

2.2.2 setsockopt()设置套接字选项

int getsockopt(
    SOCKET s,
    int level,
    int optname,
    char *optval,
    int *optlen
);

3.SOL_SOCKET协议层中的SO_SNDBUFSO_RCVBUF

前者是输入缓冲大小的相关选项,后者是输出缓冲大小的相关选项

3.1 Linux系统

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
void error_handling(const char *message);

int main(int argc, char *argv[]){
    int sock;
    int result;// 用于存储函数调用结果
    socklen_t optlen;// 用于存储选项长度
    int recvBufSize, sendBufSize;// 用于存储接收和发送缓冲区大小
    int buffer_value = 1024; // 我们想要设置的缓冲区大小

 // 创建套接字(示例中使用IPv4 TCP套接字)
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (!sock) {
        error_handling(" sock buliding error");
        return 1;
    }

    // 获取默认的接收缓冲区大小
    optlen = sizeof(recvBufSize);
    result = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&recvBufSize, &optlen);
    if (result) {
        error_handling("getsockopt SO_RCVBUF error");
    } else {
        printf("Default receive buffer size: %d\n", recvBufSize);
    }

    // 获取默认的发送缓冲区大小
    result = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&sendBufSize, &optlen);
    if (result) {
        error_handling("getsockopt SO_SNDBUF error");
    } else {
        printf("Default send buffer size: %d\n", sendBufSize);
    }

    // 设置接收缓冲区大小为1024
    result = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (const char*)&buffer_value, sizeof(buffer_value));
    if (result) {
        error_handling("setsockopt SO_RCVBUF error");
    } else {
        // 验证接收缓冲区大小设置
        result = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&recvBufSize, &optlen);
        if (result) {
            error_handling("get new SO_RCVBUF error");
        } else {
            printf("New receive buffer size: %d\n", recvBufSize);
        }
    }

    // 设置发送缓冲区大小为1024
    result = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (const char*)&buffer_value, sizeof(buffer_value));
    if (result) {
        error_handling("setsockopt SO_SNDBUF error");
    } else {
        // 验证发送缓冲区大小设置
        result = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&sendBufSize, &optlen);
        if (result) {
            error_handling("get new SO_SNDBUF error");
        } else {
            printf("New send buffer size: %d\n", sendBufSize);
        }
    }
    
    return 0;

}

void error_handling(const char *msg){
    fputs(msg, stderr);
    fputc('\n', stderr);
    exit(1);
}

在这里插入图片描述

输出的结果和我们预设的不同,受限于系统,可能无法按照我们的要求100%实现,操作系统可能有自己的缓冲区大小限制,这些限制可能高于或低于您尝试设置的值。如果设置的值超出了系统允许的范围,系统会自动调整到允许的最大或最小值。

3.2 Windows系统

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    WSADATA wsaData;
    SOCKET sock = INVALID_SOCKET;
    int result;// 用于存储函数调用结果
    socklen_t optlen;// 用于存储选项长度
    int recvBufSize, sendBufSize;// 用于存储接收和发送缓冲区大小
    int buffer_value = 1024; // 我们想要设置的缓冲区大小

    // 初始化WinSock
    result = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (result != 0) {
        printf("WSAStartup failed: %d\n", result);
        return 1;
    }

    // 创建套接字(示例中使用IPv4 TCP套接字)
    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock == INVALID_SOCKET) {
        printf("Could not create socket: %ld\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }

    // 获取默认的接收缓冲区大小
    optlen = sizeof(recvBufSize);
    result = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&recvBufSize, &optlen);
    if (result == SOCKET_ERROR) {
        printf("getsockopt failed for SO_RCVBUF: %d\n", WSAGetLastError());
    } else {
        printf("Default receive buffer size: %d\n", recvBufSize);
    }

    // 获取默认的发送缓冲区大小
    result = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&sendBufSize, &optlen);
    if (result == SOCKET_ERROR) {
        printf("getsockopt failed for SO_SNDBUF: %d\n", WSAGetLastError());
    } else {
        printf("Default send buffer size: %d\n", sendBufSize);
    }

    // 设置接收缓冲区大小为1024
    result = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (const char*)&buffer_value, sizeof(buffer_value));
    if (result == SOCKET_ERROR) {
        printf("setsockopt failed for SO_RCVBUF: %d\n", WSAGetLastError());
    } else {
        // 验证接收缓冲区大小设置
        result = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&recvBufSize, &optlen);
        if (result == SOCKET_ERROR) {
            printf("getsockopt failed for SO_RCVBUF: %d\n", WSAGetLastError());
        } else {
            printf("New receive buffer size: %d\n", recvBufSize);
        }
    }

    // 设置发送缓冲区大小为1024
    result = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (const char*)&buffer_value, sizeof(buffer_value));
    if (result == SOCKET_ERROR) {
        printf("setsockopt failed for SO_SNDBUF: %d\n", WSAGetLastError());
    } else {
        // 验证发送缓冲区大小设置
        result = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&sendBufSize, &optlen);
        if (result == SOCKET_ERROR) {
            printf("getsockopt failed for SO_SNDBUF: %d\n", WSAGetLastError());
        } else {
            printf("New send buffer size: %d\n", sendBufSize);
        }
    }

    // 清理资源
    closesocket(sock);
    WSACleanup();
    system("pause");
    return 0;
}

在这里插入图片描述

4.Time-wait状态

基于TCP的服务器端/客户端(2)中讲述“四次挥手的时,第四次挥手中,主动关闭方发送一个ACK作为回应,便会进入TIME_WAIT状态。
在这里插入图片描述
若没有TIME_WAIT状态,如果最后这条ACK消息在传输途中丢失了,未能传给主机B,则主机B就会重传FIN消息,而不是关闭连接,但此时主机A已经关闭连接无法收到消息,那么主机B将无法按照程序关闭连接。
但因为有TIME_WAIT状态,主机A就会收到重传的FIN消息,然后重新发送ACK消息给主机B,让主机B正常关闭连接。

但是TIME_WAIT状态也有缺点,如果需要尽快重启服务器,但因为网络不好,便会一直处于TIME_WAIT状态。
在这里插入图片描述
解决方法就是使用SO_REUSEADDR

5.SOL_SOCKET协议层中的SO_REUSEADDR

SO_REUSEADDR 是一个 socket 选项,允许一个 socket 绑定到一个已经在使用中的地址和端口上。当应用程序关闭处于 TIME_WAIT 状态的连接时,如果启用了 SO_REUSEADDR,新的 socket 可以立即重用该地址和端口,即使之前的连接仍在 TIME_WAIT 状态。

5.1 基本语法

int optval = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&optval, sizeof(optval));

当服务器端添加了这个代码后,即让服务器处于SO_REUSEADDR状态,即使由于网络问题,导致服务器无法正常关闭,也可以立刻重新运行服务器程序,让服务器重新绑定在之前的地址和端口上。

6.Nagle算法

TCP套接字默认使用Nagle算法,下图是极端例子下使用和不使用Nagle算法:
在这里插入图片描述
左图是使用Nagle算法,右图是不使用Nagle算法,这里再次强调,即使是不使用Nagle算法,数据传输也不是逐字传输,这里只是方便理解,极端化处理。

6.1 Nagle算法优点:

  • 减少网络拥塞: 通过减少小包的数量,可以降低网络拥塞,特别是在高延迟网络中。
  • 提高带宽利用率: 通过发送更大的数据包,可以更有效地利用可用带宽。

6.2 Nagle算法缺点:

  • 增加交互式应用的延迟: 对于需要快速响应的交互式应用(如远程登录或在线游戏),Nagle算法可能会导致感知上的延迟,因为它延迟了小数据包的发送。
  • 与某些应用协议不兼容: 一些应用协议依赖于小数据包的快速传输,Nagle算法可能会干扰这些协议的正常工作。

为了解决这些问题,TCP提供了一个称为TCP_NODELAY的选项,允许禁用Nagle算法。如果应用程序需要发送小的、需要快速确认的数据包,可以设置这个选项来禁用Nagle算法,从而允许立即发送数据包,不进行合并。

7.IPPROTO_TCP协议层中的TCP_NODELAY

TCP_NODELAY 是一个用于控制 TCP 连接中 Nagle 算法行为的套接字选项。默认情况下,Nagle 算法通过合并小的数据包来减少网络拥塞和提高带宽利用率,但这可能会导致交互式应用的延迟增加。

当启用 TCP_NODELAY 选项时,它会禁用 Nagle 算法的合并过程,允许立即发送小的数据包,而不需要等待更多的数据来填满一个最大报文段(MSS)。这可以减少延迟,特别是在需要快速响应的应用中,如远程登录(SSH)、在线游戏或任何需要实时通信的场景。

7.1 启动TCP_NODELAY

int optval = 1;

// 启用 TCP_NODELAY ,禁用 Nagle 算法
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&optval, sizeof(optval)) 

7.2 查看状态

int opt_val;
socklent_ opt_len;
opt_len=sizeof(opt_val);
getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&opt_val, &opt_len)
//使用状态opt_val会保存为0,禁用状态则保存为1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青石横刀策马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值