Linux网络编程之套接字选项设置

时间:2022-12-18 00:24:09

转载:http://blog.163.com/jlz_325/blog/static/19174000920126893134309/

1.介绍

在Linux网络编程中,有时需要设置地址复用,允许发送广播包,将主机加入某个多播组,设置发送与接收缓冲区的大小,设置发送与接收的超时时间,将套接字绑定到某个接口上,发送TCP探测包查看客户端是否保持连接等,这些都需要对套接字选项进行设置.而对套接字选项进行操作的主要有以下两个函数,setsockopt与getsockopt. 这两个函数不仅能够操作套接字层,而且能够操作IP层与TCP层.

2.相关函数

#include <sys/types.h>

#include <sys/socket.h>

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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <unistd.h>
#include <netinet/tcp.h>

//获取与设置套接字选项的函数
/**
int getsockopt(int s,int level,int optname,void*optval,socklen_t *optlen);
int setsockopt(int s,int level,int optname,const void*optval,socklen_t optlen);
s:表示要获取或者是设置的套接字描述符
level:协议所在的层
optname:选项名
optval:getsockopt指向用于保存返回选项值的缓冲区,setsockopt指向设置参数缓冲区
optlen:getsockopt用于传入参数时表示optval的最大长度,传出参数时表示optval的实际长度。对于setsockopt表示optval的实际长度
**/
//定认一个共用体用于保存结果,由于返回值具有不同的类型所以必须用共用体
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选项
/**
主要来设置TCP连接关闭时,用户缓冲区怎么处理
struct linger{
int l_onoff; 是否设置延迟关闭
int l_linger; 超时时间
}
如果l_onoff=0,表示没有使用系统默认的close套接字行为,close函数会立即返回,在可能的情况下尽量发送缓冲区内的数据,但是用户没有办法知道缓冲区内的数据是否已经完全被发送,即不确定性
如果l_onoff=1,l_linger表示关闭连接的超时时间,l_linger为非0时,表示超时的秒数,会在超时之前尽量发送数据,检查close函数的返回值,如果发送成功,close返回0,否则缓冲区内的任何数据将会被丢失,返回EWOULDBLOCK.
如果l_onoff=1,l_linger=0,表示立刻关闭,缓冲区内的数据将会丢失。
**/
//设置超时间为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");

}
/**
SOL_SOCKET:对套接字级别进行操作
IPPROTO_IP:对IP层协议进行操作
IPPROTO_TCP:对TCP层协议进行操作
**/
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<len;i++){
printf("%02x ",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包等方面有着重要的应用.