一、套接字选项
1、getsockopt()、setsockopt()
#include <sys/types.h>
#include <sys/socket.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:操作的套接字描述符,通过socket()获得
- level:选项所在协议层
- optname:选项名
- optval:操作的内存缓冲区。getsockopt()指向获取返回选项值的缓冲区;setsockopt()指向设置的参数缓冲区
- optlen():缓冲区大小
当对套接字选项进行操作时,必须给出选项所处的层和选项的名称。函数执行成功时返回0,错误时返回-1,errno如下:
2、套接字选项
按照参数选项级别的level不同,套接字选项大致可以分为3类:
- 通用套接字选项:参数level值为SOL_SOCKET,用于设置或者获取通用的一些参数,例如接收和发送的缓冲区大小、地址重用等。
- IP选项:参数level值为IPPROTO_IP,用于设置或者获取IP层的参数。例如选项名IP_HDRINCL表示在数据中包含IP头部数据,IP_TOS表示服务类型、IP_TTL表示存活时间等。
- TCP选项:参数level的值为IPPROTO_TCP,用于获得或者设置TCP协议层的一些参数。例如选项名TCP_MAXRT对最大重传时间进行操作、选项名TCP_MAXSEG对最大分片大小进行操作、选项名TCP_KEEPALIVE对保持连接时间进行操作。
具体如下表:
3、示例
#include <netinet/tcp.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
/*结构保存获取结果*/
union Optval {
int val; /*整型值*/
struct linger linger; /*linger结构*/
struct timeval tv; /*时间结构*/
unsigned char str[16]; /*字符串*/
};
static Optval optval; /*用于保存数值*/
/*数值类型*/
enum Valtype{
VALINT, /*int类型*/
VALLINGER, /*struct linger类型*/
VALTIMEVAL, /*struct timeval类型*/
VALUCHAR, /*字符串*/
VALMAX /*错误类型*/
};
/*用于保存套接字选项的结构*/
struct sopts{
int level; /*套接字选项级别*/
int optname; /*套接字选项名称*/
char *name; /*套接字名称*/
Valtype valtype; /*套接字返回参数类型*/
};
sopts sockopts[] = {
{SOL_SOCKET, SO_BROADCAST, "SO_BROADCAST", VALINT},
{SOL_SOCKET, SO_DEBUG, "SO_DEBUG", VALINT},
{SOL_SOCKET, SO_DONTROUTE, "SO_DONTROUTE", VALINT},
{SOL_SOCKET, SO_ERROR, "SO_ERROR", VALINT},
{SOL_SOCKET, SO_KEEPALIVE, "SO_KEEPALIVE", VALINT},
{SOL_SOCKET, SO_LINGER, "SO_LINGER", VALINT},
{SOL_SOCKET, SO_OOBINLINE, "SO_OOBINLINE", VALINT},
{SOL_SOCKET, SO_RCVBUF, "SO_RCVBUF", VALINT},
{SOL_SOCKET, SO_RCVLOWAT, "SO_RCVLOWAT", VALINT},
{SOL_SOCKET, SO_RCVTIMEO, "SO_RCVTIMEO", VALTIMEVAL},
{SOL_SOCKET, SO_SNDTIMEO, "SO_SNDTIMEO", VALTIMEVAL},
{SOL_SOCKET, SO_TYPE, "SO_TYPE", VALINT},
{IPPROTO_IP, IP_HDRINCL, "IP_HDRINCL", VALINT},
{IPPROTO_IP, IP_OPTIONS, "IP_OPTIONS", VALINT},
{IPPROTO_IP, IP_TOS, "IP_TOS", VALINT},
{IPPROTO_IP, IP_TTL, "IP_TTL", VALINT},
{IPPROTO_IP, IP_MULTICAST_TTL, "IP_MULTICAST_TTL", VALUCHAR},
{IPPROTO_IP, IP_MULTICAST_LOOP, "IP_MULTICAST_LOOP",VALUCHAR},
{IPPROTO_TCP, TCP_KEEPCNT, "TCP_KEEPCNT", VALINT},
{IPPROTO_TCP, TCP_MAXSEG, "TCP_MAXSEG", VALINT},
{IPPROTO_TCP, TCP_NODELAY, "TCP_NODELAY", VALINT},
/*结尾,主程序中判断VALMAX*/
{0, 0, NULL, VALMAX}
};
/*显示查询结果*/
static void disp_outcome(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: default is %d\n",sockopt->name,
optval.val);
break;
case VALLINGER:/*struct linger*/
printf("optname %s: default is %d(ON/OFF), %d to linger\n",
sockopt->name, /*名称*/
optval.linger.l_onoff, /*linger打开*/
optval.linger.l_linger); /*延时时间*/
break;
case VALTIMEVAL: /*struct timeval结构*/
printf("optname %s: default is %.06f\n",
sockopt->name, /*名称*/
/*浮点型结果*/
((((double)optval.tv.tv_sec*100000+(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;
}
}
int main(int argc, char *argv[])
{
int i = 0;
int s = socket(AF_INET, SOCK_STREAM, 0); /*建立一个流式套接字*/
while(sockopts[i].valtype != VALMAX){ /*判断是否结尾,否则轮询执行*/
socklen_t len = sizeof(sopts); /*计算结构长度*/
int err = getsockopt(s, sockopts->level, sockopts->optname, &optval,&len); /*获取选项状态*/
disp_outcome(&sockopts[i], len, err); /*显示结果*/
i++;/*递增*/
}
close(s);
return 0;
}
4、SOL_SOCKET协议族部分选项
- SO_BROADCAST广播选项
用于进行广播设置,默认情况下系统的广播是禁止的。广播使用UDP套接字,此选项输入参数是int变量。输入为0时表示禁止广播,其他值表示允许广播。
#define YES 1
#define NO 0
int s = socket(AF_INET,SOCK_DGRAM,0);//建立套接字
int optval = YES;//选项
int err = setsockopt(s, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval));//设置选项
if(err)
perror("setsockopt");
-
SO_KEEPALIVE保持连接选项
用于设置TCP连接的保持,设置此项后,连接会测试连接的状态。常用与可能长时间没有数据交流的连接,通常在服务端设置。如果2h内没有数据通信,TCP会自动发送一个活动探测数据报文,对方必须进行响应,通常有如下3种情况:- TCP的连接正常,发送ack响应。
- 对方发送RST响应,对方在2h内进行了重启或崩溃,之前的连接已经失效,套接字收到一个ECONNRESET错误,之前套接字已经关闭。
- 对方没有任何响应,则本机会发送另外8个活动探测报文,时间间隔为75s。仍未有任何响应,则放弃探测,套接字错误类型设置为ETIMEOUT,并关闭套接字连接。若收到ICMP控制报文响应(一般为主机不可达ICMP报文),也关闭连接,此时套接字错误类型设置为EHOSTUNREACH。
不能实时地探测连接情况,可以探测空闲的连接,然后自动关闭服务器连接。
-
SO_LINGER缓冲区处理方式选项
用于设置TCP连接关闭时的方式,即关闭流式连接时, 发送缓冲区中的数据如何处理。Linux内核的默认处理方式是调用close()时,函数会立刻返回。在可能的情况下尽量发送缓冲区的数据,但不一定保证会发送剩余的数据。
使用选项SO_LINGER可以阻塞close()函数直至剩余数据全部发送到对方。SO_LINGER的操作是通过结构linger来进行的结构定义如下:
struct linger
{
int l_onoff; //是否设置延时关闭
int l_linger; //超时时间
};
- l_onoff的值为0,这时l_linger将会被忽略,即使用系统默认的关闭行为。
- l_onoff的值为1,此时l_linger表示关闭连接的超时时间,非0时表示超时的秒数,会在超时之前发送所有未发送的数据。
- l_onoff的值为1,l_linger的值设置为0,表示立刻关闭,发送缓冲区里面的剩余数据将被丢弃。
- SO_RCVBUF和SO_SNDBUF缓冲区大小选项
用于操作发送缓冲区和接收缓冲区的大小,这两个选项在TCP和UDP连接中的含义有所不同。- 在UDP连接中,发送缓冲区在数据通过网络设备发送后就可以丢弃,不用保存。而接收缓冲区则需要保存数据直到应用程序读取。
- 在TCP连接中,接收缓冲区大小就是滑动窗口大小。设置TCP接收缓冲区大小的时机很重要,是在建立连接时通过SYN获得的。对于客户端,接收缓冲区的大小要在调用connect()之前进行设置。而对于服务器程序要在listen()之前进行设置。
5、设置获取缓冲区大小示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <assert.h>
int main(int argc,char **argv)
{
int err = -1; /*返回值*/
int s = -1; /*socket描述符*/
int snd_size = 0; /*发送缓冲区大小*/
int rcv_size = 0; /*接收缓冲区大小*/
socklen_t optlen; /*选项值长度*/
/*
* 建立一个TCP套接字
*/
s = socket(PF_INET,SOCK_STREAM,0);
if( s == -1){
printf("建立套接字错误\n");
return -1;
}
/*
* 先读取缓冲区设置的情况
* 获得原始发送缓冲区大小
*/
optlen = sizeof(snd_size);
err = getsockopt(s, SOL_SOCKET, SO_SNDBUF,&snd_size, &optlen);
if(err){
printf("获取发送缓冲区大小错误\n");
}
/*
* 获得原始接收缓冲区大小
*/
optlen = sizeof(rcv_size);
err = getsockopt(s, SOL_SOCKET, SO_RCVBUF, &rcv_size, &optlen);
if(err){
printf("获取接收缓冲区大小错误\n");
}
/*
* 打印原始缓冲区设置情况
*/
printf(" 发送缓冲区原始大小为: %d 字节\n",snd_size);
printf(" 接收缓冲区原始大小为: %d 字节\n",rcv_size);
/*
* 设置发送缓冲区大小
*/
snd_size = 4096; /*发送缓冲区大小为8K*/
optlen = sizeof(snd_size);
err = setsockopt(s, SOL_SOCKET, SO_SNDBUF, &snd_size, optlen);
if(err){
printf("设置发送缓冲区大小错误\n");
}
/*
* 设置接收缓冲区大小
*/
rcv_size = 8192; /*接收缓冲区大小为8K*/
optlen = sizeof(rcv_size);
err = setsockopt(s,SOL_SOCKET,SO_RCVBUF, &rcv_size, optlen);
if(err){
printf("设置接收缓冲区大小错误\n");
}
/*
* 检查上述缓冲区设置的情况
* 获得修改后发送缓冲区大小
*/
optlen = sizeof(snd_size);
err = getsockopt(s, SOL_SOCKET, SO_SNDBUF,&snd_size, &optlen);
if(err){
printf("获取发送缓冲区大小错误\n");
}
/*
* 获得修改后接收缓冲区大小
*/
optlen = sizeof(rcv_size);
err = getsockopt(s, SOL_SOCKET, SO_RCVBUF, &rcv_size, &optlen);
if(err){
printf("获取接收缓冲区大小错误\n");
}
/*
* 打印结果
*/
printf(" 发送缓冲区大小为: %d 字节\n",snd_size);
printf(" 接收缓冲区大小为: %d 字节\n",rcv_size);
close(s);
return 0;
}
二、ioctl()
使用ioctl()函数与linux内核中的网络协议栈进行交互。
int ioctl(int d, int request, ...);
1、命令选项
主要包含对套接字、文件、网络接口、地址解析协议(ARP)和路由等操作请求。
2、ioctl()函数的IO请求
6个,第三个参数要求为一个执行整形数据的指针。
3、ioctl()函数的文件请求
请求命令都是FIOxxx类型,除了可以处理套接字外,对通用的文件系统也同样适用。
4、ioctl()函数的网络请求
网络接口常用的数据结构:
struct ifreq
{
#define IFHWADDRLEN 6 //网络接口硬件结构长度,即MAC长度
union
{
char ifrn_name[IFNAMSIZ]; //网络接口名称,如eth0
} ifr_ifrn;
union {
struct sockaddr ifru_addr; //本地IP地址
struct sockaddr ifru_dstaddr; //目标IP地址
struct sockaddr ifru_broadaddr;//广播IP地址
struct sockaddr ifru_netmask; //本地子网掩码地址
struct sockaddr ifru_hwaddr; //本地MAC地址
short ifru_flags; //网络接口标记
int ifru_ivalue; //值,不同请求含义不同
int ifru_mtu; //最大传输单元,mtu
struct ifmap ifru_map; //网卡地址映射
char ifru_slave[IFNAMSIZ]; //占位符
char ifru_newname[IFNAMSIZ]; //新名称
void __user * ifru_data; //用户数据
struct if_settings ifru_settings;//设备协议设置
} ifr_ifru;
};
#define ifr_name ifr_ifrn.ifrn_name //接口名称
#define ifr_hwaddr ifr_ifru.ifru_hwaddr //MAC地址
#define ifr_addr ifr_ifru.ifru_addr //本地IP地址
#define ifr_dstaddr ifr_ifru.ifru_dstaddr //p2p地址
#define ifr_broadaddr ifr_ifru.ifru_broadaddr //广播IP
#define ifr_netmask ifr_ifru.ifru_netmask //子网掩码
#define ifr_flags ifr_ifru.ifru_flags //标志
#define ifr_metric ifr_ifru.ifru_ivalue //接口侧度
#define ifr_mtu ifr_ifru.ifru_mtu //最大传输单元
#define ifr_map ifr_ifru.ifru_map //设备地址映射
#define ifr_slave ifr_ifru.ifru_slave //副设备
#define ifr_data ifr_ifru.ifru_data //接口使用
#define ifr_ifindex ifr_ifru.ifru_ivalue //网络接口序号
#define ifr_bandwidth ifr_ifru.ifru_ivalue //连接带宽
#define ifr_qlen ifr_ifru.ifru_ivalue //传输单元长度
#define ifr_newname ifr_ifru.ifru_newname //新名称
#define ifr_settings ifr_ifru.ifru_settings //设备协议设置
ifmap是网卡设备的映射属性:
struct ifmap
{
unsigned long mem_start; //开始地址
unsigned long mem_end; //结束地址
unsigned short base_addr; //基地址
unsigned char irq; //中断号
unsigned char dma; //DMA
unsigned char port; //端口
//3字节空闲
};
网络的配置结构体 一块缓冲区,可以转换为结构ifreq来方便操作,用于读取网络接口的配置情况。
struct ifconf
{
int ifc_len; //缓冲区ifr_buf大小
union
{
char __user *ifcu_buf;//缓冲区指针
struct ifreq __user *ifcu_req;//指向结构ifreq的指针
}ifc_ifcu;
};
#define ifc_buf ifc_ifcu.ifcu_buf //缓冲区地址的宏
#define ifc_req ifc_ifcu.ifcu_req //结构ifc_req的宏
具体示例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if_arp.h>
#include <string.h>
#include <linux/sockios.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int s; /*套接字描述符*/
int err = -1; /*错误值*/
/*建立一个数据报套接字*/
s = socket(AF_INET, SOCK_DGRAM, 0);
if (s < 0) {
printf("socket() 出错\n");
return -1;
}
/*获得网络接口的名称*/
{
struct ifreq ifr;
ifr.ifr_ifindex = 2; /*获取第2个网络接口的名称*/
err = ioctl(s, SIOCGIFNAME, &ifr);
if(err){
printf("SIOCGIFNAME Error\n");
}else{
printf("the %dst interface is:%s\n",ifr.ifr_ifindex,ifr.ifr_name);
}
}
/*获得网络接口配置参数*/
{
/*查询网卡“eth0”的情况*/
struct ifreq ifr;
memcpy(ifr.ifr_name, "eth0",5);
/*获取标记*/
err = ioctl(s, SIOCGIFFLAGS, &ifr);
if(!err){
printf("SIOCGIFFLAGS:%d\n",ifr.ifr_flags);
}
/*获取METRIC*/
err = ioctl(s, SIOCGIFMETRIC, &ifr);
if(!err){
printf("SIOCGIFMETRIC:%d\n",ifr.ifr_metric);
}
/*获取MTU*/
err = ioctl(s, SIOCGIFMTU, &ifr);
if(!err){
printf("SIOCGIFMTU:%d\n",ifr.ifr_mtu);
}
/*获取MAC地址*/
err = ioctl(s, SIOCGIFHWADDR, &ifr);
if(!err){
char *hw = ifr.ifr_hwaddr.sa_data;
printf("SIOCGIFHWADDR:%02x:%02x:%02x:%02x:%02x:%02x\n",hw[0],hw[1],hw[2],hw[3],hw[4],hw[5]);
}
/*获取网卡映射参数*/
err = ioctl(s, SIOCGIFMAP, &ifr);
if(!err){
printf("SIOCGIFMAP,mem_start:%ld,mem_end:%ld, base_addr:%d, irq:%d, dma:%d,port:%d\n",
ifr.ifr_map.mem_start, /*开始地址*/
ifr.ifr_map.mem_end, /*结束地址*/
ifr.ifr_map.base_addr, /*基地址*/
ifr.ifr_map.irq , /*中断*/
ifr.ifr_map.dma , /*直接访问内存*/
ifr.ifr_map.port ); /*端口*/
}
/*获取网卡序号*/
err = ioctl(s, SIOCGIFINDEX, &ifr);
if(!err){
printf("SIOCGIFINDEX:%d\n",ifr.ifr_ifindex);
}
/*获取发送队列长度*/
err = ioctl(s, SIOCGIFTXQLEN, &ifr);
if(!err){
printf("SIOCGIFTXQLEN:%d\n",ifr.ifr_qlen);
}
}
/*获得网络接口IP地址*/
{
struct ifreq ifr;
/*方便操作设置指向sockaddr_in的指针*/
struct sockaddr_in *sin = (struct sockaddr_in *)&ifr.ifr_addr;
char ip[16]; /*保存IP地址字符串*/
memset(ip, 0, 16);
memcpy(ifr.ifr_name, "eth0",5);/*查询eth0*/
/*查询本地IP地址*/
err = ioctl(s, SIOCGIFADDR, &ifr);
if(!err){
/*将整型转化为点分四段的字符串*/
inet_ntop(AF_INET, &sin->sin_addr.s_addr, ip, 16 );
printf("SIOCGIFADDR:%s\n",ip);
}
/*查询广播IP地址*/
err = ioctl(s, SIOCGIFBRDADDR, &ifr);
if(!err){
/*将整型转化为点分四段的字符串*/
inet_ntop(AF_INET, &sin->sin_addr.s_addr, ip, 16 );
printf("SIOCGIFBRDADDR:%s\n",ip);
}
/*查询目的IP地址*/
err = ioctl(s, SIOCGIFDSTADDR, &ifr);
if(!err){
/*将整型转化为点分四段的字符串*/
inet_ntop(AF_INET, &sin->sin_addr.s_addr, ip, 16 );
printf("SIOCGIFDSTADDR:%s\n",ip);
}
/*查询子网掩码*/
err = ioctl(s, SIOCGIFNETMASK, &ifr);
if(!err){
/*将整型转化为点分四段的字符串*/
inet_ntop(AF_INET, &sin->sin_addr.s_addr, ip, 16 );
printf("SIOCGIFNETMASK:%s\n",ip);
}
}
/*测试更改IP地址*/
{
struct ifreq ifr;
/*方便操作设置指向sockaddr_in的指针*/
struct sockaddr_in *sin = (struct sockaddr_in *)&ifr.ifr_addr;
char ip[16]; /*保存IP地址字符串*/
int err = -1;
/*将本机IP地址设置为192.169.1.175*/
printf("Set IP to 192.168.1.175\n");
memset(&ifr, 0, sizeof(ifr)); /*初始化*/
memcpy(ifr.ifr_name, "eth0",5); /*对eth0网卡设置IP地址*/
inet_pton(AF_INET, "192.168.1.175", &sin->sin_addr.s_addr);
/*将字符串转换为网络字节序的整型*/
sin->sin_family = AF_INET; /*协议族*/
err = ioctl(s, SIOCSIFADDR, &ifr); /*发送设置本机IP地址请求命令*/
if(err){ /*失败*/
printf("SIOCSIFADDR error\n");
}else{ /*成功,再读取一下进行确认*/
printf("check IP --");
memset(&ifr, 0, sizeof(ifr)); /*重新清零*/
memcpy(ifr.ifr_name, "eth0",5);/*操作eth0*/
ioctl(s, SIOCGIFADDR, &ifr); /*读取*/
inet_ntop(AF_INET, &sin->sin_addr.s_addr, ip, 16);
/*将IP地址转换为字符串*/
printf("%s\n",ip);/*打印*/
}
}
close(s);
return 0;
}
5、ioctl()函数对ARP高速缓存操作
操作通过类型为arpreq的参数进行的。定义在文件<net/if_arp.h>中。
struct arpreq {
struct sockaddr arp_pa; //协议地址
struct sockaddr arp_ha; //硬件地址
int arp_flags; //标志位
struct sockaddr arp_netmask; //网络掩码(只用于代理ARP)
char arp_dev[16]; //对应的网络设备接口的名称。
};
#define ATF_COM 0x02 //查找已完成的地址 (成员ha有效,且含有正确的MAC地址)
#define ATF_PERM 0x04 //永久性记录(邻居状态有NUD_PERMANENT)
#define ATF_PUBL 0x08 //发布记录
#define ATF_USETRAILERS 0x10 //使用扩展存档名称,不再使用
#define ATF_NETMASK 0x20 //仅用于代理ARP。
#define ATF_DONTPUB 0x40 //不回复
#define ATF_MAGIC 0X80 //自动添加的邻居
三、fcntl()
int fcntl(int fd,int cmd,void arg);
对套接字进行操作的fcntl()函数有4种,如下:
可见对套接字的fcntl()使用ioctl()函数可以完全替代。
注:
LINUX网络编程 第二版 第十二章 读书笔记