套接字多种可选项
我们之前写的程序都是创建好套接字后(未经特别操作)直接使用的,此时通过默认的套接字特性进行数据通信。之前的示例较为简单,无需特别操作套接字特性,但有时的确需要更改。
协议层 | 选项名 | 读取 | 设置 |
---|---|---|---|
SOL_SOCKET | SO_SNDBUF | ✔ | ✔ |
SOL_SOCKET | SO_RCVBUF | ✔ | ✔ |
SOL_SOCKET | SO_REUSEADDR | ✔ | ✔ |
SOL_SOCKET | SO_KEEPALIVE | ✔ | ✔ |
SOL_SOCKET | SO_BROADCAST | ✔ | ✔ |
SOL_SOCKET | SO_DONTROUTE | ✔ | ✔ |
SOL_SOCKET | SO_OOBINLINE | ✔ | ✔ |
SOL_SOCKET | SO_ERROR | ✔ | ✖ |
SOL_SOCKET | SO_TYPE | ✔ | ✖ |
协议层 | 选项名 | 读取 | 设置 |
---|---|---|---|
IPPROTO_IP | IP_TOS | ✔ | ✔ |
IPPROTO_IP | IP_TTL | ✔ | ✔ |
IPPROTO_IP | IP_MULTICAST_TTL | ✔ | ✔ |
IPPROTO_IP | IP_MULTICAST_LOOP | ✔ | ✔ |
IPPROTO_IP | IP_MULTICAST_IF | ✔ | ✔ |
协议层 | 选项名 | 读取 | 设置 |
---|---|---|---|
IPPROTO_TCP | TCP_KEEPALIVE | ✔ | ✔ |
IPPROTO_TCP | TCP_NODELAY | ✔ | ✔ |
IPPROTO_TCP | TCP_MAXSEG | ✔ | ✔ |
从表中可以看出,套接字可选项是分层的。IPPROTO_IP层可选项是IP协议相关事项,IPPROTO_TCP层可选项是TCP协议相关事项,SOL_SOCKET层是套接字相关的通用可选项。
getsockopt&setsockopt
我们几乎可以针对表中的所有可选项进行读取(Get)和设置(Set),可选项的读取和设置通过如下两个函数完成。
#include <sys/socket.h>
int getsockopt(int sock, int level, int optname, void *optval,
socklen_t*optlen);
//sock 用于查看选向套接字文件描述符
//level 要查看的可选项的协议层
//optname 要查看的可选项名
//optval 保存查看结果的缓冲地址值
//optlen 向第4个参数optval传递的缓冲大小。调用函数后,该变量中保存通过第四个参数返回的可选项信息的字节数
//成功时返回0,失败时返回-1
下面介绍更改可选项时调用的函数
#include <sys/socket.h>
int setsockopt(int sock, int level, int optname, const void*optval, socklen_t optlen);
变量对应上面函数,不过这次是用于更改的值。
下面介绍这些函数的调用方法。
sock_type.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#define BUF_SIZE 30
void error_handling(char *message);
int main(int argc, char *argv[])
{
int tcp_sock, udp_sock;
int sock_type;
socklen_t optlen;
int state;
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);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
套接字类型只能在创建时决定, 以后不能再更改。
SO_SNDBUF & SO_RCVBUF
前面介绍过,创建套接字将同时生成I/O缓冲。SO_REVBUF是输入缓冲大小相关可选项,也可以进行更改。通过下列示例读取创建套接字时默认的I/O缓冲大小。
get_buf.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#define BUF_SIZE 30
void error_handling(char *message);
int main(int argc, char *argv[])
{
int sock;
int snd_buf, rcv_buf, state;
socklen_t len;
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_SNDBUF, (void*)&rcv_buf, &len);
if(state)
error_handling("getsockopt() error");
printf("Input buffer size: %d \n",rcv_buf);
printf("Output buffer size: %d \n", snd_buf);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
接下来程序中将更改I/O缓冲大小
set_buf.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#define BUF_SIZE 3096
void error_handling(char *message);
int main(int argc, char *argv[])
{
int sock;
int snd_buf = BUF_SIZE, rcv_buf = BUF_SIZE, state;
socklen_t len;
sock = socket(PF_INET, SOCK_STREAM, 0);
state = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf, sizeof(rcv_buf));
if(state)
error_handling("getsockopt() error1");
state = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, sizeof(snd_buf));
if(state)
error_handling("getsockopt() error2");
len = sizeof(snd_buf);
state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, &len);
if(state)
error_handling("getsockopt() error3");
len = sizeof(rcv_buf);
state = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf, &len);
if(state)
error_handling("getsockopt() error4");
printf("Input buffer size: %d \n",rcv_buf);
printf("Output buffer size: %d \n", snd_buf);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
缓冲大小的设置须谨慎处理,因此不会完全按照我们的要求进行。
SO_REUSEADDR
重点是可选项SO_REUSEADDR及其相关的Time-wait状态。
Time-wait状态,四次挥手过程中,先断开连接的主机经过Time-wait状态才结束。所以若服务器先断开连接,则无法立即重新运行。套接字处在Time-wait过程时,相应端口还是正在使用的状态。因此bind函数调用过程中当然会发生错误。
Time-wait也有很大的缺点,如果网络状态不理想,那么Time-wait状态有可能会持续较长一段时间。解决方案就是在套接字的可选项中更改SO_REUSEADDR的状态。适当调整参数,可将Time-wait状态下的套接字端口号重新分配给新的套接字。
代码实现实际上就是3句话:
SO_REUSEADDR默认值为0(假,此时无法重新分配),我们将其设置为真。
optlen = sizeof(option);
option = TRUE;
setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (void*) &option, optlen);
TCP_NODELAY
Nagle算法
这个算法是为防止数据包过多而发生网络过载而在1984就诞生了。应用于TCP层,很简单。
简单描述就是:只有收到前一数据的ACK消息时,Nagle算法才发送下一数据。
TCP套接字默认使用Nagle算法交换数据,因此最大限度地进行缓冲,直到收到ACK。
一般情况下,不使用Nagle算法可以提高传输速度。但如果无条件放弃使用Nagle算法,就会增加过多的网络流量,反而会影响传输。
禁用Nagle算法
禁用的典型情景就是“传输大文件数据”。Nagle算法使用与否在网络流量上差别不大时,使用Nagle算法的传输速度更慢。禁用方法非常简单,只需将套接字可选项TCP_NODELAY改为1(真)即可。
int optval = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&opt_val,sizeof(opt,val));
基于windows的实现
#include <winsock2.h>
int getsockopt(SOCKET sock, int level, int optname, char *optval, int *optlen);
//可以看到,除了optval类型变成char指针外,与Linux中的getsockopt函数相比并无太大区别。
//成功时返回0,失败时返回SOCKET_ERROR
#include <winsock2.h>
int setsockopt(SOCKET sock, int level, int optname,
const char * optval, int optlen);
//成功时返回0,失败时返回SOCKET_ERROR