套接字的可选项
level | Optname | get | set | 说明 | 标志 | 数据类型 |
| | | | | | |
SOL_SOCKET | SO_BROADCAST | y | y | 允许发送广播数据报 | y | int |
| SO_DEBUG | y | y | 使能调试跟踪 | y | int |
| SO_DONTROUTE | y | y | 旁路路由表查询 | y | int |
| SO_ERROR | y | | 获取待处理错误并消除 | | int |
| SO_KEEPALIVE | y | y | 周期性测试连接是否存活 | y | int |
| SO_LINGER | y | y | 若有数据待发送则延迟关闭 | | linger{} |
| SO_OOBINLINE | y | y | 让接收到的带外数据继续在线存放 | y | int |
| SO_RCVBUF | y | y | 接收缓冲区大小 | | int |
| SO_SNDBUF | y | y | 发送缓冲区大小 | | int |
| SO_RCVLOWAT | y | y | 接收缓冲区低潮限度 | | int |
| SO_SNDLOWAT | y | y | 发送缓冲区低潮限度 | | int |
| SO_RCVTIMEO | y | y | 接收超时 | | timeval{} |
| SO_SNDTIMEO | y | y | 发送超时 | | timeval{} |
| SO_REUSEADDR | y | y | 允许重用本地地址 | y | int |
| SO_REUSEPORT | y | y | 允许重用本地地址 | y | int |
| SO_TYPE | y | | 取得套接口类型 | | int |
| SO_USELOOPBACK | y | y | 路由套接口取得所发送数据的拷贝 | y | int |
| | | | | | |
IPPROTO_IP | IP_HDRINCL | y | y | IP头部包括数据 | y | int |
| IP_OPTIONS | y | y | IP头部选项 | | 见后面说明 |
| IP_RECVDSTADDR | y | y | 返回目的IP地址 | y | int |
| IP_RECVIF | y | y | 返回接收到的接口索引 | y | int |
| IP_TOS | y | y | 服务类型和优先权 | | int |
| IP_TTL | y | y | 存活时间 | | int |
| IP_MULTICAST_IF | y | y | 指定外出接口 | | in_addr{} |
| IP_MULTICAST_TTL | y | y | 指定外出TTL | | u_char |
| IP_MULTICAST_LOOP | y | y | 指定是否回馈 | | u_char |
| IP_ADD_MEMBERSHIP | | y | 加入多播组 | | ip_mreq{} |
| IP_DROP_MEMBERSHIP | | y | 离开多播组 | | ip_mreq{} |
| | | | | | |
IPPROTO_ICMPV6 | ICMP6_FILTER | y | y | 指定传递的ICMPv6消息类型 | | icmp6_filter{} |
| | | | | | |
IPPROTO_IPV6 | IPV6_ADDRFORM | y | y | 改变套接口的地址结构 | | int |
| IPV6_CHECKSUM | y | y | 原始套接口的校验和字段偏移 | | int |
| IPV6_DSTOPTS | y | y | 接收目标选项 | y | int |
| IPV6_HOPLIMIT | y | y | 接收单播跳限 | y | int |
| IPV6_HOPOPTS | y | y | 接收步跳选项 | y | int |
| IPV6_NEXTHOP | y | y | 指定下一跳地址 | y | sockaddr{} |
| IPV6_PKTINFO | y | y | 接收分组信息 | y | int |
| IPV6_PKTOPTIONS | y | y | 指定分组选项 | | 见后面说明 |
| IPV6_RTHDR | y | y | 接收原路径 | y | int |
| IPV6_UNICAST_HOPS | y | y | 缺省单播跳限 | | int |
| IPV6_MULTICAST_IF | y | y | 指定外出接口 | | in6_addr{} |
| IPV6_MULTICAST_HOPS | y | y | 指定外出跳限 | | u_int |
| IPV6_MULTICAST_LOOP | y | y | 指定是否回馈 | y | u_int |
| IPV6_ADD_MEMBERSHIP | | y | 加入多播组 | | ipv6_mreq{} |
| IPV6_DROP_MEMBERSHIP | | y | 离开多播组 | | ipv6_mreq{} |
| | | | | | |
IPPROTO_TCP | TCP_KEEPALIVE | y | y | 控测对方是否存活前连接闲置秒数 | | int |
| TCP_MAXRT | y | y | TCP最大重传时间 | | int |
| TCP_MAXSEG | y | y | TCP最大分节大小 | | int |
| TCP_NODELAY | y | y | 禁止Nagle算法 | y | int |
| TCP_STDURG | y | y | 紧急指针的解释 | y | int |
从这个表可以看出,套接字的选项是分层的。IPPROTO_IP层可选项是IP协议相关事项,IPPROTO_TCP层可选项是TCP协议相关的事项,SOL_SOCKET层是套接字相关的通用可选项。
常见可选项:
1,协议层为SOL_SOCKET下的SO_TYPE可选项,可查看套接字的类型(TCP或UDP).
2,协议层为SOL_SOCKET下的SO_SNDBUF和SO_RCVBUF可选项,分别是可以设置输出缓冲和输入缓冲的大小(需要注意的是,缓冲区的大小不会完全按照我们的要求去设定)
3,协议层为SOL_SOCKET下的SO_REUSEADDR可选项,可以设置地址是否可以再分配。
查看和修改可选项的函数:
int getsockopt(int sock,int level,int optname,void* optval,socklen_t *optlen);
-------sock 用于查看选项套接字文件描述符。
-------level 要查看的可选项的协议层。
-------optname 要查看的可选项名。
-------optval 传递用于保存结果的变量的地址
-------optlen 传递保存有第四个变量长度的变量的地址
int setsockopt(int sock,int level,int optname,const void* optval,socklen_t optlen);
-------sock 用于更改可选项的套接字文件描述符。
-------level 要更改的可选项协议层。
-------optname 要更改的可选项名。
-------optval 传递保存有要修改的值的变量地址
-------optlen 传递第四个参数的长度
示例代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
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("getsocket() 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_TYPE是典型的只读可选项即: “套接字的类型只能在创建时决定,以后不能更改。”
Time_wait状体与地址再分配:
当客户端与服务器端通信完成后,服务器端先关闭时(即服务器端向客户端发送FIN消息),再次打开服务器端时,将会出现bind() error,这和TCP的四次握手有关,套接字经过四次握手过程后并非立即消除,而会经过一段时间的Time_wait状态(大约3分钟左右,保证对方收到此方发出去的应答消息二启动了一个Time_wait计时器),只有先断开连接(谁先发送FIN消息)的主机才经过Time_wait状态,因为Time_wait的状态,相应的端口依旧被占用,所以bind()会出错
注意:
1、无论是服务器端还是客户端都有Time_wait状态,先断开连接的套接字必然经过Time_wait过程,却无需考虑客户端的Time_wait状态,因为客户端套接字的端口号是动态分配的
2、当先断开连接的套接字进入Time_wait状态时(发送回复对方FIN的ACK时进入Time_wait状态),一旦ACK发送失败,当再次接收到对方的FIN时(对方认为自己发送过来的消息没有发送成功时重传的)将重启计时器,再次进入Time_wait状态
不一定全是优点的Time_wait状态
Time_wait状态其实在有些时候是不合理的存在,如系统发生故障从而紧急停止的情况,这时需要尽快重启服务器,但因处于Time_wait状态而不得不等待。可以通过可选项SO_REUSEADDR来修改在Time-wait状态下端口号是否可以重新分配给新套接字,从而解决这个问题。SO_REUSEADDR的默认值为0(假),将其修改为1(真)。
//加在调用bind()之前
int option = TRUE;
socklen_t optlen = sizeof(option);
setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (void *) &option, optlen);
Nagle算法
1、防止因数据包过多而发生网络过载
2、只有接收到对前一数据的ACK消息时,才发送下一数据,未收到ACK消息时最大限度的进行缓冲,当收到ACK消息后将缓冲的数据一并发出
3、传输速度比不使用Nagle算法时低,但是传输效率更高
启用Nagle算法时,未收到ACK不继续发,而未启用Nagle时不依靠是否接收ACK消息来判断能否继续发送下一数据,即数据到达输出缓冲后立即被发出去,若写一字节停一下停写一字节的方式写入输出缓冲区,由于未使用Nagle算法,算上发送的数据与对方的ACK一共需要字节数 * 2 个数据包!!!!!!!,而且数据包头信息可能有几十字节,这明显不是一桩划算的买卖
Nagle算法也不是什么时候都适用,最典型的就是”传输大文件数据”,将文件数据传入输处缓冲区比较快,即便不使用Nagle算法也会在装满输出缓冲时传输数据包,这样不仅不增加数据包数量,而且还无需等待ACK可连续传输,可大大提高传输速度
禁用方法:
int opt_val = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *)&opt_val, sizeof(opt_val));