1.介绍
在Linux网络编程中,有时需要设置地址复用,允许发送广播包,将主机加入某个多播组,设置发送与接收缓冲区的大小,设置发送与接收的超时时间,将套接字绑定到某个接口上,发送TCP探测包查看客户端是否保持连接等,这些都需要对套接字选项进行设置.而对套接字选项进行操作的主要有以下两个函数,setsockopt与getsockopt.
这两个函数不仅能够操作套接字层,而且能够操作IP层与TCP层.
2.相关函数
#include
#include
int getsockopt(int s,int level,int optname,void*optval,socklen_t
*optlen);
参数:
s-套接字描述符
level-选项所在的层,主要有SOL_SOCKET(套接字层),IPPROTO_IP(IP选项),IPPROTO_TCP(TCP选项).
optname-选项名
optval-所操作的缓冲区,即参数缓冲区
optlen-传入参数的最大长度的指针(返回参数的实际长度)
返回值:
函数执行成功返回0,失败返回-1.
int setsockopt(int s,int level,int optname,const
void*optval,socklen_t optlen);
参数:
s-套接字描述符
level-套接字所在的层
optname-套接字选项名
optval-所操作的缓冲区指针
optlen-所传入参数的实际长度
返回值:
函数执行成功返回0,失败返回-1.
套接字的一些选项值如下表所示:
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
3.套接字选项设置实例
#include
#include
#include
#include
#include
#include
#include
#include
//获取与设置套接字选项的函数
//定认一个共用体用于保存结果,由于返回值具有不同的类型所以必须用共用体
typedef union optval{
int val;
struct linger linger;
struct timeval tv;
unsigned char str[16];
}val;
static val optval;
//数据类型的定义
typedef enum valtype{
VALINT,//int 类型
VALLINGER,//struct linger类型
VALTIMEVAL,//struct timeval类型
VALUCHAR,//字符串
VALMAX,//错误类型
}valtype;
//保存套接字选项的结构
typedef struct sopts{
int level;//套接字选项级别
int optname;//套接字选项名称
char*name;//套接字名称
valtype valtype;//套接字返回参数类型
}sopts;
//套接字数组
sopts sockopts[]={
{SOL_SOCKET,SO_BROADCAST,"SO_BROADCAST",VALINT},//是否支持广播
{SOL_SOCKET,SO_DEBUG,"SO_DEBUG",VALINT},
//是否支持TCP调试
{SOL_SOCKET,SO_DONTROUTE,"SO_DONTROUTE",VALINT},//是否需要路由
{SOL_SOCKET,SO_ERROR,"SO_ERROR",VALINT},//返回错误信息
{SOL_SOCKET,SO_KEEPALIVE,"SO_KEEPALIVE",VALINT},//是否支持自动探测TCP连接
{SOL_SOCKET,SO_LINGER,"SO_LINGER",VALLINGER},//是否支持TCP关闭模式
{SOL_SOCKET,SO_OOBINLINE,"SO_OOBINLINE",VALINT},//是否支持带外数据与普通数据一起发送
{SOL_SOCKET,SO_RCVBUF,"SO_RECVBUF",VALINT},//设置接收缓冲区大小
{SOL_SOCKET,SO_SNDBUF,"SO_SNDBUF",VALINT},//设置发送缓冲区大小
{SOL_SOCKET,SO_SNDTIMEO,"SO_SNDTIMEO",VALTIMEVAL},//设置发送数据的超时时间
{SOL_SOCKET,SO_RCVTIMEO,"SO_RCVTIMEO",VALTIMEVAL},//设置接收数据的超时时间
{SOL_SOCKET,SO_RCVLOWAT,"SO_RCVLOWAT",VALINT},//设置接收缓冲区的最小字节数
{SOL_SOCKET,SO_SNDLOWAT,"SO_SNDLOWAT",VALINT},//设置发送缓冲区的最小字节数
{SOL_SOCKET,SO_REUSEADDR,"SO_REUSERADDR",VALINT},//是否允许端口重用
{SOL_SOCKET,SO_TYPE,"SO_TYPE",VALINT},//套接字类型
{SOL_SOCKET,SO_BINDTODEVICE,"SO_BINDTODEVICE",VALUCHAR},//是否将套接字绑定到指定端口
{SOL_SOCKET,SO_PRIORITY,"SO_PRIORITY",VALINT},//设置套接字的优先级别
{IPPROTO_IP,IP_HDRINCL,"IP_HDRINCL",VALINT},//用户是否自己填写IP头部
{IPPROTO_IP,IP_MULTICAST_TTL,"IP_MULTICAST_TTL",VALUCHAR},//多播的最大跳数
{0,0,NULL,VALMAX}
};
static void display(sopts *sockopt,int len,int err);
//获得套接字选项值
int main(int argc,char*argv[]){
int err=-1;
int len=0;
int i=0;
int s=socket(AF_INET,SOCK_STREAM,0);
//首先设置套接字选项信息,然后再返回套接字选项,先用setsockopt,再用getsockopt
//设置广播选项
int broadcast=1;
err=setsockopt(s,SOL_SOCKET,SO_BROADCAST,&broadcast,sizeof(broadcast));//broadcast指针指向缓冲区,缓冲区存储的是允许广播选项的值,值的长度为&len
if(err){
perror("setsockopt error");
}
//设置可调试选项
int
debug=1;//内核程序跟踪此套接字上的接收与发送数据,并将数据放到一个环形缓冲区,通过trpt查看,仅支持TCP
err=setsockopt(s,SOL_SOCKET,SO_DEBUG,&debug,sizeof(debug));
if(err){
printf("debug error\n");
}
//SO_KEEPALIVE用于探测TCP连接是否保持,主要使用在长时间没有数据响应的TCP连接,如TELNET客户端长时间不使用,这需要服务器去探测对方是否活动
int keepalive=10;
err=setsockopt(s,SOL_SOCKET,SO_KEEPALIVE,&keepalive,sizeof(keepalive));
if(err){
printf("keepalive error");
}
//SO_LINGER选项
//设置超时间为60秒
struct linger l;
l.l_onoff=1;//延迟关闭
l.l_linger=60;
err=setsockopt(s,SOL_SOCKET,SO_LINGER,&l,sizeof(l));
if(err){
perror("SO_LINGER ERROR");
}
//带外数据指的是那些紧急的数据,TCP优先发送带外数据,这些数据使用特殊的通道来接收。如果采用SO_OOBINLINE表示在普通数据流中接收带外数
int oobinline=1;
err=setsockopt(s,SOL_SOCKET,SO_OOBINLINE,&oobinline,sizeof(oobinline));
if(err){
perror("SO_OOBINLINE ERROR");
}
//设置发送缓冲区与接收缓冲区的大小SO_RCVBUF与SO_SNDBUF,将发送缓冲区的大小设置为8192字节,将接收缓冲区的大小设置为16384字节
//TCP具有流量控制的功能,不允许发送比滑动窗口大的数据,而UDP没有流量控制的功能,当发送的数据比接收缓冲区多时,会丢失数据,即快的发送者会淹没慢的接收者
int sndsize=8192;//真实的缓冲区大小为用户输入的两倍
int rcvsize=16384;
err=setsockopt(s,SOL_SOCKET,SO_SNDBUF,&sndsize,sizeof(sndsize));
if(err){
perror("SO_SNDBUF ERROR");
}
err=setsockopt(s,SOL_SOCKET,SO_RCVBUF,&rcvsize,sizeof(rcvsize));
if(err){
perror("SO_RCVBUF ERROR");
}
//发送超时时延与接收超时时延SO_RCVTIMEO,SO_SNDTIMEO默认情况下recv函数没有接收到数据会一直阻塞的
struct timeval t;
t.tv_sec=10;//设置为10秒
t.tv_usec=100;
int len1=sizeof(t);
err=setsockopt(s,SOL_SOCKET,SO_SNDTIMEO,&t,len1);
if(err){
perror("SO_SNDTIMEO error");
}
err=setsockopt(s,SOL_SOCKET,SO_RCVTIMEO,&t,len1);
if(err){
perror("SO_RCVTIMEO");
}
//SO_RCVLOWAT接收缓存有最小数据字节数,读才不会阻塞。SO_SNDLOWAT为发送缓冲区的最小值,默认情况下都为1字节,写才不会阻塞
//将发送缓冲区空闲空间的下限值设定为2048字节,将接收缓冲区的空闲空间设置为1字节
int rcvlowat=10;
int sndlowat=10;//发送缓冲区的最低空闲字节数不能设置
err=setsockopt(s,SOL_SOCKET,SO_RCVLOWAT,&rcvlowat,sizeof(rcvlowat));
if(err){
perror("SO_RCVLOWAT ERROR");
}
err=setsockopt(s,SOL_SOCKET,SO_SNDLOWAT,&sndlowat,sizeof(sndlowat));
if(err){
perror("SO_SNDLOWAT ERROR");
}
//SO_REUSERADDR表示允许重复使用本地端口,某些非正常退出的服务器程序会占用端口一段时间才释放,这时将不使用SO_REUSERADDR将不能绑定到端口
int reuseaddr=1;//开启端口重复使用功能
err=setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&reuseaddr,sizeof(reuseaddr));
if(err){
perror("SO_REUSEADDR ERROR");
}
//SO_BINDTODEVICE,可以将套接字与某个接口相绑定
char ifname[]="eth0";//sizeof表示空间大小为5,加\0,strlen遇到\0结束为4
err=setsockopt(s,SOL_SOCKET,SO_BINDTODEVICE,ifname,5);//数组名可以表示地址
if(err){
perror("SO_BINDTODEVICE ERROR");
}
//SO_PRIORITY对套接字优先权的设置,高优先权的数据被优处理,优先权范围0-6
int priority=6;
err=setsockopt(s,SOL_SOCKET,SO_PRIORITY,&priority,sizeof(priority));
if(err){
perror("SO_PRIORITY ERROR");
}
while(sockopts[i].valtype!=VALMAX){
len=sizeof(optval);//getsockopt传入时的最大长度
err=getsockopt(s,sockopts[i].level,sockopts[i].optname,&optval,&len);//返回的值保存在optval缓冲区中,传出值表示返回选项值的实际长度
//第一个参数是套接字描述符
//第二个参数是选项所在的协议层
//第三个参数是选项名称
//第四个参数是返回值的缓冲区
//第五个参数是返回值缓冲区的长度
display(&sockopts[i],len,err);
i++;
}
close(s);
return 0;
}
static void display(sopts *sockopt,int len,int err){
if(err==-1){
printf("optname %s not
support\n",sockopt->name);
return;
}
switch(sockopt->valtype){
case VALINT:
printf("optname %s:is
%d\n",sockopt->name,optval.val);//整形值
break;
case VALLINGER:
printf("optname %s:is %d(ON/OFF),%d to
linger\n",sockopt->name,optval.linger.l_onoff,optval.linger.l_linger);//linger打开,延迟时间
break;
case VALTIMEVAL:
printf("optname %s:default is
%.06f\n",sockopt->name,((((double)optval.tv.tv_sec*1000000+(double)optval.tv.tv_usec))/(double)1000000));
break;
case VALUCHAR:
{
int
i=0;
printf("optname %s:default is",sockopt->name);
for(i=0;i
printf("x ",optval.str[i]);
}
printf("\n");
}
break;
default:
break;
}
}
运行结果:
[root@localhost ~]# ./socket-opt
SO_SNDLOWAT ERROR: Protocol not available
optname SO_BROADCAST:is 1
optname SO_DEBUG:is 1
optname SO_DONTROUTE:is 0
optname SO_ERROR:is 0
optname SO_KEEPALIVE:is 1
optname SO_LINGER:is 1(ON/OFF),60 to linger
optname SO_OOBINLINE:is 1
optname SO_RECVBUF:is 32768
optname SO_SNDBUF:is 16384
optname SO_SNDTIMEO:is 10.001000
optname SO_RCVTIMEO:is 10.001000
optname SO_RCVLOWAT:is 10
optname SO_SNDLOWAT:is 1
optname SO_REUSERADDR:is 1
optname SO_TYPE:is 1
optname SO_BINDTODEVICE not support
optname SO_PRIORITY:is 6
optname IP_HDRINCL:is 0
optname IP_MULTICAST_TTL:is01 00 00 00
总结:
本文主要介绍了套接字选项设置的相关函数,以及套接字层(SOL_SOCKET),IP层(IPPROTO_IP)与TCPP层(IPPROTO_TCP)选项的含义,最后给出一个具体的例子来设置和得到套接字选项的值.
套接字选项的设置在广播和多播以及原始套接字直接得到IP包等方面有着重要的应用.