套接字可选项和I/O缓冲大小
套接字的多种可选项
套接字可选项分为 IPPROTO_IP
、IPPROTO_TCP
、SOL_SOCKET
三层,各层的含义为:
IPPROTO_IP
:IP 协议相关事项;
IPPROTO_TCP
:TCP 协议相关事项;
SOL_SOCKET
:套接字相关的通用可选项。
下表列出了其中部分可选项,这些可选项无需立即掌握,用到什么学什么即可。
函数 - getsockopt & setsockopt
可选项的读取和设置由如下两个函数来是实现:
getsockopt & setsockopt
#include <sys/socket.h>
int getsockopt(int sock, int level, int optname, void* optval, socklen_t* optlen);
// 功能:读取套接字可选项
// 参数:sock:套接字文件描述符;level:可选项所属协议层;optname:要查看的可选项名称;
// optval:用于保存查看结果的缓冲地址;optlen:调用函数后,optlen 会保存 optval 返回的可选信息项的字节数
// 返回值:成功时返回 0,失败时返回 -1。
int setsockopt(int sock, int level, int optname, void* optval, socklen_t optlen);
// 功能:更改套接字可选项
// 参数:sock:套接字文件描述符;level:可选项所属协议层;optname:要更改的可选项名称;
// optval:保存要更改的选项信息的缓冲地址;optlen:指明参数 optval 所指对象的大小
// 返回值:成功时返回 0,失败时返回 -1。
示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <winsock2.h>
void error_handling(char *message);
int main(int argc, char *argv[])
{
WSADATA wsaData;
int tcp_sock, udp_sock;
int sock_type;
int optlen;
int state;
if(WSAStartup(MAKEWORD(2, 2), &wsaData)!=0)
error_handling("WSAStartup() error!");
optlen=sizeof(sock_type);
tcp_sock=socket(PF_INET, SOCK_STREAM, 0);
udp_sock=socket(PF_INET, SOCK_DGRAM, 0);
printf("SOCK_STREAM: %d \n", SOCK_STREAM);
printf("SOCK_DGRAM: %d \n", SOCK_DGRAM);
state=getsockopt(tcp_sock, SOL_SOCKET, SO_TYPE, (void*)&sock_type, &optlen);
if(state)
error_handling("getsockopt() error!");
printf("Socket type one: %d \n", sock_type);
state=getsockopt(udp_sock, SOL_SOCKET, SO_TYPE, (void*)&sock_type, &optlen);
if(state)
error_handling("getsockopt() error!");
printf("Socket type two: %d \n", sock_type);
WSACleanup();
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
选项 - SO_SNDBUF & SO_RCVBUF
创建套接字同时将生成 I/O 缓冲
SO_SNDBUF
可选项表示输出缓冲大小相关信息,SO_RCVBUF
可选项表示输入缓冲大小相关信息。
这两个选项都是可读可写的。默认的输入输出缓冲大小可能在几万字节(几十)左右。
可以修改缓冲区大小,但是系统并不一定会完全按照我们的要求进行修改,修改结果可能会有所出入。
示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <winsock2.h>
void error_handling(char *message);
int main(int argc, char *argv[])
{
WSADATA wsaData;
int sock;
int snd_buf, rcv_buf, state;
int len;
if(WSAStartup(MAKEWORD(2, 2), &wsaData)!=0)
error_handling("WSAStartup() error!");
sock=socket(PF_INET, SOCK_STREAM, 0);
len=sizeof(snd_buf);
state=getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, &len);
if(state)
error_handling("getsockopt() error");
len=sizeof(rcv_buf);
state=getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf, &len);
if(state)
error_handling("getsockopt() error");
printf("Input buffer size: %d \n", rcv_buf);
printf("Outupt buffer size: %d \n", snd_buf);
WSACleanup();
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
选项 - SO_REUSEADDR
time-wait状态
主动结束连接的一方(先发送 FIN
消息的)会经历 time-wait
状态。
地址分配错误(bind() error)
当使用 Ctrl+C 终止服务器程序时,服务器程序成为主动终止连接的一方,会经历 time-wait
状态。这时服务器之前所用的套接字是无法立即使用的(如果立即执行会发生 bind() error
),只能等几分钟再执行或修改端口号(即修改套接字)。
客户端的 time-wait
状态无需关心,因为它的端口号是动态分配的。
地址再分配
有时需要立即重启服务器程序,这可以通过更改可选项 SO_REUSEADDR
的状态来实现。
SO_REUSEADDR
的默认值为 0,将其修改为 1 即可将 time-wait
状态下的套接字端口号重新分配给新的套接字。
使用方式:
int option = 1;
setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (void*)&option, sizeof(option));
选项 - TCP_NODELAY
Nagle 算法是应用于 TCP 层的一个简单算法:只有收到前一数据的 ACK 消息时,Nagle 算法才会发送下一数据。
TCP 默认使用 Nagle 算法,因此会最大限度地进行缓冲,直到收到 ACK 才将数据发送出去。
Nagle 算法的优点:可以避免产生大量网络流量。如果不使用 Nagle 算法,数据到达输出缓冲后立即发送出去,会产生多个体积很小的包(如上图所示),增加网络负载。
Nagle 算法的缺点:很多时候会降低传输速度。不使用 Nagle 算法时,数据无需等待 ACK 报文就可以发送出去,没有等待时间。在发送大文件数据时尤其明显。因为传输大文件数据无论是否使用 Nagle 算法都不会产生大量的小数据包,而不使用 Nagle 算法则不用等待 ACK 报文,速度更快。
应根据情况选择是否禁用 Nagle 算法。
禁用Nagle算法
可选项 TCP_NODELAY
默认为 0,表示开启 Nagle 算法,将其修改为 1 即可禁用 Nagle 算法。
禁用 Nagle 算法的方式:
int opt_val = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&opt_val, sizeof(opt_val));
可以通过 TCP_NODELAY
的值查看 Nagle
算法的设置状态。
int opt_val = 1;
int opt_len = sizeof(opt_val);
getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&opt_val, &opt_len);
如果正在使用 Nagle 算法,opt_val 变量中会保存 0;如果已禁用 Nagle 算法,则保存 1。