#include <netinet.h>
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);
返回:网络字节序值
uint16_t ntohs(uint16_t net16bitvalue);
uint32_t ntohl(uint32_t net32bitvalue);
返回:主机字节序值
一个测试本机字节序的程序,可参见见unpv12e:intro/byteorder.c。
#include <strings.h>
void bzero(void *dest, size_t nbytes);
void bcopy(const void *src, void *dest, size_t nbytes);
int bcmp(const void *ptr1, const void *ptr2, size_t nbytes);
返回:0—相等,非0—不相等
#include <string.h>
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)。
#include <arpa/inet.h>
int inet_aton(const char *strptr, struct in_addr *addrptr);
返回:1—串有效,0—串有错。
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—失败。
说明:
实现IPv4版本的inet_pton和inet_ntop的程序,参见:unpv12e:libfree/inet_pton_ipv4.c和libfree/inet_ntop_ipv4.c。
函数原型如下:
ssize_t readn(int filedes, void *buff, size_t nbytes);
ssize-t writen(int filedes, void *buff, size_t nbytes);
ssize_t readline(int filedes, void *buff, size_t maxlen);
返回:读写字节数,-1—出错。
实现程序见:unpv12e:lib/readn.c、lib/writen.c、lib/readline1.c和lib/readline.c。
#include <sys/stat.h>
int isfdtype( int fd, int fdtype);
返回:1—是指定类型,0—不是指定类型,-1—出错。
要测试是否为套接口描述子,fdtype应设为S_IFSOCK。
该函数的一个实现程序,参见unpv12e:lib/isfdtype.c
#include <sys/socket.h>
int socket(int family, int type, int protocol);
返回:非负描述字—成功,-1—出错。
family指定协议族,有如下取值:
type指定套接口类型:
protocol一般设为0,除非用在原始套接口上。
并非所有family和type的组合都是有效的。
AF_LOCAL等于早期的AF_UNIX。
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
返回:0—成功,-1—出错。
sockfd是socket函数返回的套接口描述字,servaddr和addrlen是指向服务器的套接口地址结构指针和结构大小。
在调用connect之前不必非得调用bind函数。
如果是TCP,则connect激发TCP的三路握手过程,在阻塞情况下,只有在连接建立成功或出错时该函数才返回,
出错情况:
注意:如果connect失败,则套接口将不能再使用,必须关闭,不能对此套接口再调用函数connect。
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *maddr, socklen_t addrlen);
返回:0—成功,-1—出错。
进程可以把一个特定的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(地址已使用)。
#include <sys/socket.h>
int listen(int sockfd, int backlog);
返回:0—成功,-1—出错。
listen把未连接的套接口转化为被动套接口,指示内核应接受指向此套接口的连接请求。第二个参数规定了内核为此套接口排队的最大连接数。
参数backlog曾经规定为监听套接口上的未完成连接队列和已完成连接队列总和的最大值,但各个系统的定义方法都不尽相同;历史上常把backlog置为5,但对于繁忙的服务器是不够的;backlog的设置没有一个通用的方法,依情况而定,但不要设为0。
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
返回:非负描述字—OK,-1—出错。
accept从已完成连接队列头返回下一个连接,若已完成连接队列为空,则进程睡眠(套接口为阻塞方式时)。
参数cliaddr和addrlen返回连接对方的协议地址,其中addrlen是值-结果参数,调用前addrlen所指的整数值要置为cliaddr所指的套接口结构的长度,返回时由内核修改。
accept成功执行后,返回一个连接套接口描述字。
如果对客户的协议地址没有兴趣,可以把cliaddr和addrlen置为空指针。
#include <unistd.h>
int close(int sockfd);
返回:0—OK,-1—出错。
TCP套接口的close缺省功能是将套接口做上“已关闭”标记,并立即返回到进程。这个套接口描述字不能再为进程使用,但TCP将试着发送已排队待发的任何数据,然后按正常的TCP连接终止序列进行操作。
close把描述字的访问计数减1,当访问计数仍大于0时,close并不会引发TCP的四分组连接终止序列。若确实要发一个FIN,可以用函数shutdown。
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
返回:0—OK,-1—出错。
getsockname函数返回与套接口关联的本地协议地址。
getpeername函数返回与套接口关联的远程协议地址。
addrlen是值-结果参数。
使用场合:
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
返回:准备好描述字的正数目,0—超时,-1—出错。
结构timeval的定义:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
timeout取值的三种情况:
在等待过程中,若进程捕获了信号并从信号处理程序返回,等待一般被中断,为了可移植性,必须准备好select返回EINTR错误。
timeout的值在返回时并不会被select修改(const标志)。
readset、writeset、exceptset指定我们要让内核测试读、写和异常条件所需的描述字。
当前支持的异常条件有两个:
描述字集的使用:
数据类型:fd_set;
void FD_ZERO(fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
void FD_CLR(int fd, fd_set *fdset);
void FD_ISSET(int fd, fd_set *fdset);
参数maxfdp1指定被测试的描述字个数,它的值是要被测试的最大描述字加1。描述字0,1,2,…,maxfdp1-1都被测试。
readset、writeset、exceptset是值-结果参数,select修改三者所指的描述字集。所以,每次调用select时,我们都要将所有描述字集中关心的位置为1。
套接口准备好读的条件:
套接口准备好写的条件:
如果一个套接口存在带外数据或者仍处于带外标记,那它有异常条件待处理。
一个套接口出错时,它被select标记为既可读又可写。
#include <sys/socket.h>
int shutdown(int sockfd, int howto);
返回:0—成功,-1—失败。
函数的行为依赖于参数howto的值:
SHUT_RDWR:连接的读这一半和写这一半都关闭。这等效于调用shutdown两次:第一次调用时用SHUT_RD,第二次调用时用SHUT_WR。
#include <sys/select.h>
#include <signal.h>
#include <time.h>
int pselect(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timespec *timeout, const sigset_t *sigmask);
返回:准备好描述字的个数,0—超时,-1—出错。
pselect是Posix.1g发明的。相对select的变化:
#include <poll.h>
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
返回:准备好描述字的个数,0—超时,-1—出错。
第一个参数是指向一个结构数组的第一个元素的指针,每个数组元素都是一个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)。术语来自流的概念。
返回条件:
poll没有select存在的最大描述字数目问题。但可移植性select要好于poll。
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
返回:0—OK,-1—出错。
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错误。
仅仅TCP支持。当打开此选项时,内核对TCP在此套接口所发送和接收的所有分组跟踪详细信息。这些信息保存在内核的环形缓冲区内,可由程序trpt进行检查。
此选项规定发出的分组将旁路底层协议的正常路由机制。
该选项经常由路由守护进程(routed和gated)用来旁路路由表(路由表不正确的情况下),强制一个分组从某个特定接口发出。
当套接口上发生错误时,源自Berkeley的内核中的协议模块将此套接口的名为so_error的变量设为标准的UNIX Exxx值中的一个,它称为此套接口的待处理错误(pending error)。内核可立即以以下两种方式通知进程:
进程然后可以通过获取SO_ERROR套接口选项来得到so_error的值。由getsockopt返回的整数值就是此套接口的待处理错误。so_error随后由内核复位为0。
当进程调用read且没有数据返回时,如果so_error为非0值,则read返回-1且errno设为so_error的值,接着so_error的值被复位为0。如果此套接口上有数据在排队,则read返回那些数据而不是返回错误条件。
如果进程调用write时so_error为非0值,则write返回-1且errno设为so_error的值,随后so_error也被复位。
打开此选项后,如果2小时内在此套接口上没有任何数据交换,TCP就会自动给对方发一个保持存活探测分节,结果如下:
此选项一般由服务器使用。服务器使用它是为了检测出半开连接并终止他们。
此选项指定函数close对面向连接的协议如何操作(如TCP)。缺省close操作是立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方。
SO_LINGER选项用来改变此缺省设置。使用如下结构:
struct linger {
int l_onoff; /* 0 = off, nozero = on */
int l_linger; /* linger time */
};
有下列三种情况:
l_linger的单位依赖于实现,4.4BSD假设其单位是时钟滴答(百分之一秒),但Posix.1g规定单位为秒。
让客户知道服务器已经读其数据的一个方法时:调用shutdown(SHUT_WR)而不是调用close,并等待对方close连接的本地(服务器)端。
此选项打开时,带外数据将被保留在正常的输入队列中(即在线存放)。当发生这种情况时,接收函数的MSG_OOB标志不能用来读带外数据。
每个套接口都有一个发送缓冲区和一个接收缓冲区,使用这两个套接口选项可以改变缺省缓冲区大小。
当设置TCP套接口接收缓冲区的大小时,函数调用顺序是很重要的,因为TCP的窗口规模选项是在建立连接时用SYN与对方互换得到的。对于客户,SO_RCVBUF选项必须在connect之前设置;对于服务器,SO_RCVBUF选项必须在listen前设置。
TCP套接口缓冲区的大小至少是连接的MSS的三倍,而必须是连接的MSS的偶数倍。
每个套接口有一个接收低潮限度和一个发送低潮限度,他们由函数select使用。这两个选项可以修改他们。
接收低潮限度是让select返回“可读”而在套接口接收缓冲区中必须有的数据量,对于一个TCP或UDP套接口,此值缺省为1。发送低潮限度是让select返回“可写”而在套接口发送缓冲区中必须有的可用空间,对于TCP套接口,此值常为2048。
使用这两个选项可以给套接口设置一个接收和发送超时。通过设置参数的值为0秒和0微秒来禁止超时。缺省时两个超时都是禁止的。
接收超时影响5个输入函数:read、readv、recv、recvfrom和recvmsg;发送超时影响5个输出函数:write、writev、send、sendto和sendmsg。
SO_REUSEADDR提供如下四个功能:
SO_REUSEPORT选项有如下语义:
使用这两个套接口选项的建议:
该选项返回套接口的类型,返回的整数值是一个诸如SOCK_STREAM或SOCK_DGRAM这样的值。
该选项仅用于路由域(AF_ROUTE)的套接口,它对这些套接口的缺省设置为打开(这是唯一一个缺省为打开而不是关闭的SO_xxx套接口选项)。当此套接口打开时,套接口接收在其上发送的任何数据的一个拷贝。
禁止这些回馈拷贝的另一个方法是shutdown,第二个参数应设为SHUT_RD。
如果一个原始套接口设置该选项,则我们必须为所有发送到此原始套接口上的数据报构造自己的IP头部。
设置此选项允许我们在IPv4头部中设置IP选项。这要求掌握IP头部中IP选项的格式信息。
该选项导致所接收到的UDP数据报的目的IP地址由函数recvmsg作为辅助数据返回。
该选项导致所接收到的UDP数据报的接口索引由函数recvmsg作为辅助数据返回。
该选项使我们可以给TCP或UDP套接口在IP头部中设置服务类型字段。如果我们给此选项调用getsockopt,则放到外出IP数据报头部的TOS字段中的当前值将返回(缺省为0)。还没有办法从接收到的IP数据报中取此值。
可以将TOS设置为如下的值:
用次选项,可以设置和获取系统用于某个给定套接口的缺省TTL值(存活时间字段)。与TOS一样,没有办法从接收到的数据报中得到此值。
可获取和设置一个icmp6_filter结构,他指明256个可能的ICMPv6消息类型中哪一个传递给在原始套接口上的进程。
允许套接口从IPv4转换到IPv6,反之亦可。
指定用户数据中校验和所处位置的字节偏移。如果此值为非负,则内核将(1)给所有外出分组计算并存储校验和;(2)输入时检查所收到的分组的校验和,丢弃带有无效校验和的分组。此选项影响出ICMPv6原始套接口外的所有IPv6套接口。如果指定的值为-1(缺省值),内核在此原始套接口上将不给外出的分组计算并存储校验和,也不检查所收到的分组的校验和。
设置此选项指明:任何接收到的IPv6目标选项都将由recvmsg作为辅助数据返回。此选项缺省为关闭。
设置此选项指明:接收到的跳限字段将由recvmsg作为辅助数据返回。
设置此选项指明:任何接收到的步跳选项都将由recvmsg作为辅助数据返回。
这不是一个套接口选项,而是一个可指定个sendmsg的辅助数据对象的类型。此对象以一个套接口地址结构指定某个数据报的下一跳地址。
设置此选项指明:下面关于接收到的IPv6数据报的两条信息将由recvmsg作为辅助数据返回:目的IPv6地址和到达接口索引。
大多数IPv6套接口选项假设UDP套接口使用recvmsg和sendmsg所用的辅助数据在内核与应用进程间传递信息。TCP套接口使用IPV6_PKTOPTIONS来获取和存储这些值。
设置此选项指明:接收到的IPv6路由头部将由recvmsg作为辅助数据返回。
类似于IPv4的IP_TTL,它的设置指定发送到套接口上的外出数据报的缺省跳限,而它的获取则返回内核将用于套接口的跳限值。为了从接收到的IPv6数据报中得到真实的跳限字段,要求使用IPV6_HOPLIMIT套接口选项。
它指定TCP开始发送保持存活探测分节前以秒为单位的连接空闲时间。缺省值至少为7200秒,即2小时。该选项仅在SO_KEEPALIVE套接口选项打开时才有效。
它指定一旦TCP开始重传数据,在连接断开之前需经历的以秒为单位的时间总量。值0意味着使用系统缺省值,值-1意味着永远重传数据。
允许获取或设置TCP连接的最大分节大小(MSS)。返回值是我们的TCP发送给另一端的最大数据量,他常常就是由另一端用SYN分节通告的MSS,除非我们的TCP选择使用一个比对方通告的MSS小的值。如果此选项在套接口连接之前取得,则返回值为未从另一端收到的MSS选项的情况下所用的缺省值。
如果设置,此选项禁止TCP的Nagle算法。缺省时,该算法是使能的。
Nagle算法的目的是减少WAN上小分组的数目。
Nagle算法常常与另一个TCP算法联合使用:延迟ACK(delayed ACK)算法。
解决多次写导致Nagle算法和延迟ACK算法负面影响的方法:
它影响对TCP紧急指针的解释。
#include <fcntl.h>
int fcntl(int fd, int cmd, … /* arg */);
返回:依赖于参数cmd—成功,-1—失败。
函数fcntl提供了如下关于网络编程的特性:
注意事项:
#include <netdb.h>
struct hostent *gethostbyname(const char *hostname);
返回:非空指针—成功,空指针—出错,同时设置h_errno。
函数返回的非空指针指向的结构如下:
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设置为定义在头文件<netdb.h>中的下列常值中的一个:
有函数hstrerror(),它将h_errno的值作为唯一的参数,返回一个指向相应错误说明的const char *型指针。
DNS中的条目称为资源记录RR(resource record),仅有少数几类RR会影响我们的名字与地址转换:
#include <netdb.h>
struct hostent *gethostbyname2(const char *hostname, int family);
返回:非空指针—成功,空指针—出错,同时设置h_errno。
该函数允许指定地址族,其他与gethostbyname相似。
#include <netdb.h>
struct hostent *gethostbyaddr(const char *addr, size_t len, int family);
返回:非空指针—成功,空指针—出错,同时设置h_error。
函数根据一个二进制的IP地址并试图找出相应于此地址的主机名,我们关心的是规范主机名h_name。
参数addr不是char *类型,而是一个真正指向含有IPv4或IPv6地址的结构in_addr或in6_addr的指针;len是该结构的大小,对于IPv4是4,对于IPv6是16;family或为AF_INET或为AF_INET6。
按照DNS的说法,该函数查询PTR记录。
#include <sys/utsname.h>
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];
};
该函数经常与gethostbyname一起用来确定本机的IP地址:先调用uname获得主机名字,然后调用gethostbyname得到所有的IP地址。
获得本机IP地址的另一个方法是ioctl的命令SIOCGIFCONF。
#include <unistd.h>
int gethostname(char *name, size_t namelen);
返回:0—成功,-1—失败。
返回当前主机的名字。name是指向主机名存储位置的指针,namelen是此数组的大小,如果有空间,主机名以空字符结束。
主机名的最大大小通常是头文件<sys/param.h>定义的常值MAXHOSTNAMELEN。
#include <netdb.h>
struct servent *getservbyname(const char *servname, const char *protoname);
返回:非空指针—成功,空指针—失败。
函数返回如下结构的指针:
struct servent {
char *s_name;
char **s_aliases;
int s_port;
char *s_proto;
};
服务名servname必须指定,如果还指定了协议(protoname为非空指针),则结果表项必须有匹配的记录。如果没有指定协议名而服务支持多个协议,则返回哪个端口是依赖于实现的。
结构中的端口号是以网络字节序返回的,所以在将它存储在套接口地址结构时,绝对不能调用htons。
#include <netdb.h>
struct servent *getservbyport(int port, const char *protname);
返回:非空指针—成功,空指针—出错。
port必须为网络字节序。例如:
sptr = getservbyport(htons(53), “udp”);
#include <sys/socket.h>
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。
前三个参数与read和write相同,参数flags的值或为0,或由以下的一个或多个常值逻辑或构成:
flags | 描述 | recv | send |
MSG_DONTROUTE | 不查路由表 | y | |
MSG_DONTWAIT | 本操作不阻塞 | y | y |
MSG_OOB | 发送或接收带外数据 | y | y |
MSG_PEEK | 查看外来的消息 | y | |
MSG_WAITALL | 等待所有数据 | y |
下面说明每个标志的作用:
#include <sys/uio.h>
ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
返回:读到或写出的字节数,出错返回-1。
readv和writev可以让我们在一个函数调用中读或写多个缓冲区,这些操作被称为分散读和集中写。
iovec结构定义如下:
struct iovec {
void *iov_base; /* starting address of buffer */
size_t iov_len; /* size of buffer */
};
在具体的实现中对iovec结构数组的元素个数有限制,4.3BSD最多允许1024个,而Solaris2.5上限是16。Posix.1g要求定义一个常值IOV_MAX,而且它的值不小于16。
readv和writev可用于任何描述字。writev是一个原子操作,可以避免多次写引发的Nagle算法。
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags);
返回:成功时为读入或写出的字节数,出错时为-1。
这两个函数是最通用的套接口I/O函数,可以用recvmsg代替read、readv、recv和recvfrom,同样,各种输出函数都可以用sendmsg代替。
参数msghdr结构的定义如下:
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() */
};
该结构源自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如下表所示:
标志 | 在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的标志,所以我们应该只检查那些感兴趣的标志的值。
#include <sys/socket.h>
int socketpair(int family, int type, int protocol, int sockfd[2]);
返回:成功返回0,出错返回-1。
family必须为AF_LOCAL,protocol必须为0,type可以是SOCK_STREAM或SOCK_DGRAM。新创建的两个套接口描述字作为sockfd[0]和sockfd[1]返回。
这两个描述字相互连接,没有名字,即没有涉及隐式bind。
以SOCK_STREAM作为type调用所得到的结果称为流管道(stream pipe)。这与一般的UNIX管道类似,但流管道是全双工的,两个描述字都是可读写的。
#include <unistd.h>
int ioctl(int fd, int request, … /* void *arg */ );
返回:成功返回0,出错返回-1。
第三个参数总是一个指针,但指针的类型依赖于request。
ioctl和网络有关的请求可分为如下6类:
类别 | 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 |
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
struct ifreq {
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
在调用ioctl之前分配一个缓冲区和一个ifconf结构,然后初始化后者,iotctl的第三个参数指向ifconf结构。
一个实现获取所有接口的程序,可参见unpv12e:lib/get_ifi_info.c
http://www.cnblogs.com/riky/archive/2006/11/24/570713.aspx
转自:http://blog.chinaunix.net/space.php?uid=20564848&do=blog&id=73226