分类: C/C++
2006-12-05 12:53:08
uint16_t ntohs(uint16_t net16bitvalue);
uint32_t ntohl(uint32_t net32bitvalue);
返回:主机字节序值
一个测试本机字节序的程序,可参见见unpv12e:intro/byteorder.c。
#include
void *memset(void *dest, int c, size_t len);
void *memcpy(void *dest, void *src, size_t nbytes);
int memcmp(const void *ptr1, const void *ptr2, size_t nbytes);
返回:0—相同,>0或<0—不相同;进行比较操作时,假定两个不相等的字节均为无符号字符(unsigned char)。
in_addr_t inet_addr(const char *strptr);
返回:若成功,返回32为二进制的网络字节序地址;若有错,则返回INADDR_NONE。
char *inet_ntoa(struct in_addr inaddr);
返回:指向点分十进制数串的指针。
int inet_pton(int family, const char *strptr, void *addrptr);
返回:1—成功;0—输入不是有效的表达格式,-1—出错。
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
返回:指向结果的指针—成功,NULL—失败。
说明:
实现程序见:unpv12e:lib/readn.c、lib/writen.c、lib/readline1.c和lib/readline.c。
要测试是否为套接口描述子,fdtype应设为S_IFSOCK。
该函数的一个实现程序,参见unpv12e:lib/isfdtype.c
family指定协议族,有如下取值:
并非所有family和type的组合都是有效的。
AF_LOCAL等于早期的AF_UNIX。
sockfd是socket函数返回的套接口描述字,servaddr和addrlen是指向服务器的套接口地址结构指针和结构大小。
在调用connect之前不必非得调用bind函数。
如果是TCP,则connect激发TCP的三路握手过程,在阻塞情况下,只有在连接建立成功或出错时该函数才返回,
出错情况:
进程可以把一个特定的IP地址捆绑到他的套接口上,但此IP地址必须是主机的一个接口。
对于IPv4,通配地址是INADDR_ANY,其值一般为0;使用方法如下:
struct sockaddr_in servaddr;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
对于IPv6,方法如下:
struct sockaddr_in6 serv;
serv.sin6_addr = in6addr_any; (系统分配变量in6addr_any并将其初始化为常值IN6ADDR_ANY_INIT。)
如果让内核选择临时端口,注意的是bind并不返回所选的断口值,要得到一个端口,必须使用getsockname函数。
bind失败的常见错误是EADDRINUSE(地址已使用)。
listen把未连接的套接口转化为被动套接口,指示内核应接受指向此套接口的连接请求。第二个参数规定了内核为此套接口排队的最大连接数。
参数backlog曾经规定为监听套接口上的未完成连接队列和已完成连接队列总和的最大值,但各个系统的定义方法都不尽相同;历史上常把backlog置为5,但对于繁忙的服务器是不够的;backlog的设置没有一个通用的方法,依情况而定,但不要设为0。
accept从已完成连接队列头返回下一个连接,若已完成连接队列为空,则进程睡眠(套接口为阻塞方式时)。
参数cliaddr和addrlen返回连接对方的协议地址,其中addrlen是值-结果参数,调用前addrlen所指的整数值要置为cliaddr所指的套接口结构的长度,返回时由内核修改。
accept成功执行后,返回一个连接套接口描述字。
如果对客户的协议地址没有兴趣,可以把cliaddr和addrlen置为空指针。
TCP套接口的close缺省功能是将套接口做上“已关闭”标记,并立即返回到进程。这个套接口描述字不能再为进程使用,但TCP将试着发送已排队待发的任何数据,然后按正常的TCP连接终止序列进行操作。
close把描述字的访问计数减1,当访问计数仍大于0时,close并不会引发TCP的四分组连接终止序列。若确实要发一个FIN,可以用函数shutdown。
getsockname函数返回与套接口关联的本地协议地址。
getpeername函数返回与套接口关联的远程协议地址。
addrlen是值-结果参数。
使用场合:
结构timeval的定义:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
timeout取值的三种情况:
timeout的值在返回时并不会被select修改(const标志)。
readset、writeset、exceptset指定我们要让内核测试读、写和异常条件所需的描述字。
当前支持的异常条件有两个:
参数maxfdp1指定被测试的描述字个数,它的值是要被测试的最大描述字加1。描述字0,1,2,…,maxfdp1-1都被测试。
readset、writeset、exceptset是值-结果参数,select修改三者所指的描述字集。所以,每次调用select时,我们都要将所有描述字集中关心的位置为1。
套接口准备好读的条件:
一个套接口出错时,它被select标记为既可读又可写。
函数的行为依赖于参数howto的值:
pselect是Posix.1g发明的。相对select的变化:
第一个参数是指向一个结构数组的第一个元素的指针,每个数组元素都是一个pollfd结构:
struct pollfd {
int fd; /* descriptor to check */
short events; /* events of interest on fd */
short revents; /* events that occurred on fd */
};
要测试的条件由成员events规定,函数在相应的revents成员中返回描述字的状态(一个描述字有两个变量:一个为调用值,一个为结果)。
第二个参数指定数组中元素的个数。
第三个参数timeout指定函数返回前等待多长时间,单位是毫秒。可能值如下:
常量 | 能作为events的输入吗? | 能作为revents的结果吗? | 解释 |
POLLIN | yes | yes | 普通或优先级带数据可读 |
POLLRDNORM | yes | yes | 普通数据可读 |
POLLRDBAND | yes | yes | 优先级带数据可读 |
POLLPRI | yes | yes | 高优先级数据可读 |
POLLOUT | yes | yes | 普通或优先级带数据可写 |
POLLWRNORM | yes | yes | 普通数据可写 |
POLLWRBAND | yes | yes | 优先级带数据可写 |
POLLERR | yes | 发生错误 | |
POLLHUP | yes | 发生挂起 | |
POLLNVAL | yes | 描述字不是一个打开的文件 |
poll识别三个类别的数据:普通(normal)、优先级带(priority band)、高优先级(high priority)。术语来自流的概念。
返回条件:
sockfd必须是一个打开的套接口描述字;level(级别)指定系统中解释选项的代码:普通套接口代码或特定于协议的代码);optval是一个指向变量的指针;此变量的大小由最后一个参数决定。
对于某些套接口选项,什么时候进行设置或获取是有差别的。下面的套接口选项是由TCP已连接套接口从监听套接口继承来的:
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 |
详细说明:
如果目的地址是广播地址但此选项未设,则返回EACCES错误。
该选项经常由路由守护进程(routed和gated)用来旁路路由表(路由表不正确的情况下),强制一个分组从某个特定接口发出。
当进程调用read且没有数据返回时,如果so_error为非0值,则read返回-1且errno设为so_error的值,接着so_error的值被复位为0。如果此套接口上有数据在排队,则read返回那些数据而不是返回错误条件。
如果进程调用write时so_error为非0值,则write返回-1且errno设为so_error的值,随后so_error也被复位。
SO_LINGER选项用来改变此缺省设置。使用如下结构:
struct linger {
int l_onoff; /* 0 = off, nozero = on */
int l_linger; /* linger time */
};
有下列三种情况:
让客户知道服务器已经读其数据的一个方法时:调用shutdown(SHUT_WR)而不是调用close,并等待对方close连接的本地(服务器)端。
当设置TCP套接口接收缓冲区的大小时,函数调用顺序是很重要的,因为TCP的窗口规模选项是在建立连接时用SYN与对方互换得到的。对于客户,SO_RCVBUF选项必须在connect之前设置;对于服务器,SO_RCVBUF选项必须在listen前设置。
TCP套接口缓冲区的大小至少是连接的MSS的三倍,而必须是连接的MSS的偶数倍。
接收低潮限度是让select返回“可读”而在套接口接收缓冲区中必须有的数据量,对于一个TCP或UDP套接口,此值缺省为1。发送低潮限度是让select返回“可写”而在套接口发送缓冲区中必须有的可用空间,对于TCP套接口,此值常为2048。
接收超时影响5个输入函数:read、readv、recv、recvfrom和recvmsg;发送超时影响5个输出函数:write、writev、send、sendto和sendmsg。
禁止这些回馈拷贝的另一个方法是shutdown,第二个参数应设为SHUT_RD。
可以将TOS设置为如下的值:
Nagle算法的目的是减少WAN上小分组的数目。
Nagle算法常常与另一个TCP算法联合使用:延迟ACK(delayed ACK)算法。
解决多次写导致Nagle算法和延迟ACK算法负面影响的方法:
函数fcntl提供了如下关于网络编程的特性:
函数返回的非空指针指向的结构如下:
struct hostent {
char *h_name; /*规范主机名 */
char **h_aliases; /* 别名列表 */
int h_addrtype; /* AF_INET or AF_INET6 */
int h_length; /* 地址长度 */
char **h_addr_list; /* IPv4或IPv6地址结构列表 */
};
#define h_addr h_addr_list[0];
按照DNS的说法,gethostbyname执行一个对A记录的查询或对AAAA记录的查询,返回IPv4或IPv6地址。
h_addr的定义是为了兼容,在新代码中不应使用。
返回的h_name称为主机的规范(canonical)名字。当返回IPv6地址时,h_addrtype被设置为AF_INET6,成员h_length被设置为16。
gethostbyname的特殊之处在于:当发生错误时,他不设置errno,而是将全局整数h_errno设置为定义在头文件
该函数允许指定地址族,其他与gethostbyname相似。 函数根据一个二进制的IP地址并试图找出相应于此地址的主机名,我们关心的是规范主机名h_name。 参数addr不是char *类型,而是一个真正指向含有IPv4或IPv6地址的结构in_addr或in6_addr的指针;len是该结构的大小,对于IPv4是4,对于IPv6是16;family或为AF_INET或为AF_INET6。 按照DNS的说法,该函数查询PTR记录。 返回当前主机的名字,存放在如下的结构里: 该函数经常与gethostbyname一起用来确定本机的IP地址:先调用uname获得主机名字,然后调用gethostbyname得到所有的IP地址。 获得本机IP地址的另一个方法是ioctl的命令SIOCGIFCONF。 返回当前主机的名字。name是指向主机名存储位置的指针,namelen是此数组的大小,如果有空间,主机名以空字符结束。 主机名的最大大小通常是头文件 函数返回如下结构的指针: 服务名servname必须指定,如果还指定了协议(protoname为非空指针),则结果表项必须有匹配的记录。如果没有指定协议名而服务支持多个协议,则返回哪个端口是依赖于实现的。 结构中的端口号是以网络字节序返回的,所以在将它存储在套接口地址结构时,绝对不能调用htons。 port必须为网络字节序。例如: 前三个参数与read和write相同,参数flags的值或为0,或由以下的一个或多个常值逻辑或构成: 下面说明每个标志的作用: readv和writev可以让我们在一个函数调用中读或写多个缓冲区,这些操作被称为分散读和集中写。 iovec结构定义如下: 在具体的实现中对iovec结构数组的元素个数有限制,4.3BSD最多允许1024个,而Solaris2.5上限是16。Posix.1g要求定义一个常值IOV_MAX,而且它的值不小于16。 readv和writev可用于任何描述字。writev是一个原子操作,可以避免多次写引发的Nagle算法。 example: /* prototype: ssize_t writev(int fd, const struct iovec *iov, int cnt); */ 这两个函数是最通用的套接口I/O函数,可以用recvmsg代替read、readv、recv和recvfrom,同样,各种输出函数都可以用sendmsg代替。 参数msghdr结构的定义如下: 该结构源自4.3BSD Reno,也是Posix.1g中所说明的,有些系统仍使用一种老的msghdr结构,此种结构中没有msg_flags成员,而且msg_control和msg_controllen成员分别被叫做msg_accrights和msg_accrightslen。老系统中支持的唯一一种辅助数据形式是文件描述字(称为访问权限)的传递。 msg_name和msg_namelen成员用于未经连接的套接口,他们与recvfrom和sendto的第五和第六个参数类似:msg_name指向一个套接口地址结构,如果不需要指明协议地址,msg_name应被设置为空指针,msg_namelen对sendmsg是一个值,而对recvmsg是一个值-结果参数。 msg_iov和msg_iovlen成员指明输入或输出的缓冲区数组。 msg_control和msg_controllen指明可选的辅助数据的位置和大小,msg_controllen对recvmsg是一个值-结果参数。 msg_flags只用于revmsg,调用recvmsg时,flags参数被拷贝到msg_flags成员,而且内核用这个值进行接收处理,接着它的值会根据recvmsg的结果而更新,sendmsg会忽略msg_flags成员,因为它在进行输出处理时使用flags参数。 内核检查的flags和返回的msg_flags如下表所示: 前四个标志只检查不返回,下两个标志既检查又返回,最后四个只返回。返回的六个标志含义如下: family必须为AF_LOCAL,protocol必须为0,type可以是SOCK_STREAM或SOCK_DGRAM。新创建的两个套接口描述字作为sockfd[0]和sockfd[1]返回。 这两个描述字相互连接,没有名字,即没有涉及隐式bind。 以SOCK_STREAM作为type调用所得到的结果称为流管道(stream pipe)。这与一般的UNIX管道类似,但流管道是全双工的,两个描述字都是可读写的。 第三个参数总是一个指针,但指针的类型依赖于request。 ioctl和网络有关的请求可分为如下6类:
结构定义如下: struct ifreq { 在调用ioctl之前分配一个缓冲区和一个ifconf结构,然后初始化后者,iotctl的第三个参数指向ifconf结构。 一个实现获取所有接口的程序,可参见unpv12e:lib/get_ifi_info.c
有函数hstrerror(),它将h_errno的值作为唯一的参数,返回一个指向相应错误说明的const char *型指针。
DNS小常识:
DNS中的条目称为资源记录RR(resource record),仅有少数几类RR会影响我们的名字与地址转换:
21.gethostbyname2函数
#include
struct hostent *gethostbyname2(const char *hostname, int family);
返回:非空指针—成功,空指针—出错,同时设置h_errno。
22.gethostbyaddr函数
#include
struct hostent *gethostbyaddr(const char *addr, size_t len, int family);
返回:非空指针—成功,空指针—出错,同时设置h_error。
23.uname函数
#include
int uname(struct utsname *name);
返回:非负值—成功,-1—失败。
#define UTS_NAMESIZE 16
#define UTS_NODESIZE 256
struct utsname {
char sysname[UTS_NAMESIZE];
char nodename[UTS_NODESIZE];
char release[UTS_NAMESIZE];
char version[UTS_NAMESIZE];
char machine[UTS_NAMESIZE];
};
24.gethostname函数
#include
int gethostname(char *name, size_t namelen);
返回:0—成功,-1—失败。
25.getservbyname函数
#include
struct servent *getservbyname(const char *servname, const char *protoname);
返回:非空指针—成功,空指针—失败。
struct servent {
char *s_name;
char **s_aliases;
int s_port;
char *s_proto;
};
26.getservbyport函数
#include
struct servent *getservbyport(int port, const char *protname);
返回:非空指针—成功,空指针—出错。
sptr = getservbyport(htons(53), “udp”);
27.recv和send
#include
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
ssize_t send(int sockfd, void *buf, size_t nbytes, int flags);
返回:成功返回读入或写出的字节数,出错返回-1。
flags
描述
recv
send
MSG_DONTROUTE
不查路由表
y
MSG_DONTWAIT
本操作不阻塞
y
y
MSG_OOB
发送或接收带外数据
y
y
MSG_PEEK
查看外来的消息
y
MSG_WAITALL
等待所有数据
y
28.readv和writev
#include
ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
返回:读到或写出的字节数,出错返回-1。
struct iovec {
void *iov_base; /* starting address of buffer */
size_t iov_len; /* size of buffer */
};
#include
#include
#include
#include
#include
int main(void)
{
char buf0[] = "xuzhengyang", buf1[] = "yinshulei\n";
struct iovec iov[2];
iov[0].iov_base = buf0;
iov[0].iov_len = strlen(iov[0].iov_base);
iov[1].iov_base = buf1;
iov[1].iov_len = strlen(iov[1].iov_base);
if (writev(STDOUT_FILENO, iov, 2) == -1) {
perror("writev");
exit(1);
}
exit(0);
}
29.readmsg和writemsg
#include
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags);
返回:成功时为读入或写出的字节数,出错时为-1。
struct msghdr {
void *msg_name; /* protocol address */
socklen_t msg_namelen; /* size of protocol address */
struct iovec *msg_iov; /* scatter/gather array */
size_t msg_iovlen; /* elements in msg_iov */
void *msg_control; /* ancillary data; must be aligned for a cmsghdr structure */
socklen_t msg_controllen; /* length of ancillary data */
int msg_flags; /* flags returned by recvmsg() */
};
标志
在send flags、
sendto flags、
sendmsg flags中检查在recv flags、
recvfrom flags、
recvmsg flags中检查在recvmsg msg_flags
中返回
MSG_DONTROUTE
y
MSG_DONTWAIT
y
y
MSG_PEEK
y
MSG_WAITALL
y
MSG_EOR
y
y
MSG_OOB
y
y
y
MSG_BCAST
y
MSG_MCAST
y
MSG_TRUNC
y
MSG_CTRUNC
y
具体的实现可能会在msg_flags中返回一些输入的flags的标志,所以我们应该只检查那些感兴趣的标志的值。
30.socketpair函数
#include
int socketpair(int family, int type, int protocol, int sockfd[2]);
返回:成功返回0,出错返回-1。
31.套接口ioctl函数
#include
int ioctl(int fd, int request, … /* void *arg */ );
返回:成功返回0,出错返回-1。
类别
request
描述
数据类型
套接口
SIOCATMARK
在带外标志上吗
int
SIOCSPGRP
设置套接口的进程ID或进程组ID
int
SIOCGPGRP
获取套接口的进程ID或进程组ID
int
文件
FIONBIO
设置/清除非阻塞标志
int
FIOASYNC
设置/清除异步I/O标志
int
FIONREAD
获取接收缓冲区中的字节数
int
FIOSETOWN
设置文件的进程ID或进程组ID
int
FIOGETOWN
获取文件的进程ID或进程组ID
int
接口
SIOCGIFCONF
获取所有接口的列表
struct ifconf
SIOCSIFADDR
设置接口地址
struct ifreq
SIOCGIFADDR
获取接口地址
struct ifreq
SIOCSIFFLAGS
设置接口标志
struct ifreq
SIOCGIFFLAGS
获取接口标志
struct ifreq
SIOCSIFDSTADDR
设置点到点地址
struct ifreq
SIOCGIFDSTADDR
获取点到点地址
struct ifreq
SIOCGIFBRDADDR
获取广播地址
struct ifreq
SIOCSIFBRDADDR
设置广播地址
struct ifreq
SIOCGIFNETMASK
获取子网掩码
struct ifreq
SIOCSIFNETMASK
设置子网掩码
struct ifreq
SIOCGIFMETRIC
获取接口的测度(metric)
struct ifreq
SIOCSIFMETRIC
设置接口的测度(metric)
struct ifreq
SIOCxxx
(有很多,依赖于实现)
ARP
SIOCSARP
创建/修改ARP项
struct arpreq
SIOCGARP
获取ARP项
struct arpreq
SIOCDARP
删除ARP项
struct arpreq
路由
SIOCADDRT
增加路径
struct rtentry
SIOCDELRT
删除路径
struct rtentry
流
I_xxx
(1)套接口操作
(2)文件操作
(3)接口配置
SIOCGIFCONF:从内核中获取系统中配置的所有接口。它使用了结构ifconf,ifconf又使用了ifreq结构。
struct ifconf {
int ifc_len; /* size of buffer, value-result */
union {
caddr_t ifcu_buf; /* input from user->kernel */
struct ifreq *ifcu_req; /* return from kernel->user */
}ifc_ifcu;
};
#define ifc_buf ifc_ifcu.ifcu_buf
#define ifc_req ifc_ifcu.ifcu_req
#define IFNAMSIZ 16
char ifr_name[IFNAMSIZ];
union {
struct sockaddr ifru_addr;
struct sockaddr ifru_dstaddr;
struct sockaddr ifru_broadaddr;
short ifru_flags;
int ifru_metric;
caddr_t ifru_data;
}ifr_ifru;
};
#define ifr_addr ifr_ifru.ifru_addr
#define ifr_dstaddr ifr_ifru.ifru_dstaddr
#define ifr_broadaddr ifr_ifru.broadaddr
#define ifr_flags ifr_ifru.ifru_flags
#define ifr_metric ifr_ifru.ifru_metric
#define ifr_data ifr_ifru.ifru_data
(4)接口操作
(5)ARP高速缓存操作
(6)路由表操作