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_SNDBUF
和 SO_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