Unix网络API
1.字节序函数
#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。
2.字节操作函数
#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)。
3.地址转换函数
#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—失败。
说明:
- inet_aton函数的指针若为空,则函数仍然执行输入串的有效性检查,但不存储任何结果。
- inet_addr的缺陷:出错返回值INADDR_NONE等于255.255.255.255(IPv4的有限广播地址),所以该函数不能处理此地址。
尽量使用inet_aton,不使用inet_addr。 - inet_ntoa函数的执行结果放在静态内存中,是不可重入的。
- 参数family可以是AF_INET,也可以是AF_INET6,若参数family不被支持,则出错,errno置为EAFNOSUPPORT。
- 指针addrptr是结构指针。
- len指定目标的大小,避免缓冲区溢出。如果len太小,则返回一个空指针,errno置为ENOSPC。为有助于规定该大小,有如下定义:
#include <netinet.h>
#define INET_ADDRSTRLEN 16 /*fro IPv4 dotted-decimal */
#define INET6_ADDRSTRLEN 46 /*for IPv6 hex string */ - inet_ntop函数的参数strptr不能为空指针,成功时,此指针即是函数的返回值。
实现IPv4版本的inet_pton和inet_ntop的程序,参见:unpv12e:libfree/inet_pton_ipv4.c和libfree/inet_ntop_ipv4.c。
4.readn、writen和readline
函数原型如下: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。
5.测试描述符类型
#include <sys/stat.h>int isfdtype( int fd, int fdtype);
返回:1—是指定类型,0—不是指定类型,-1—出错。
要测试是否为套接口描述子,fdtype应设为S_IFSOCK。
该函数的一个实现程序,参见unpv12e:lib/isfdtype.c
6.socket函数
#include <sys/socket.h>int socket(int family, int type, int protocol);
返回:非负描述字—成功,-1—出错。
family指定协议族,有如下取值:
- AF_INET IPv4协议
- AF_INET6 IPv6协议
- AF_LOCAL Unix域协议
- AF_ROUTE 路由套接口
- AF_KEY 密钥套接口
- SOCK_STREAM 字节流套接口
- SOCK_DGRAM 数据报套接口
- SOCK_RAW 原始套接口
并非所有family和type的组合都是有效的。
AF_LOCAL等于早期的AF_UNIX。
7.connect函数
#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的三路握手过程,在阻塞情况下,只有在连接建立成功或出错时该函数才返回,
出错情况:
- 没有收到SYN分节的响应,在规定时间内经过重发仍无效,则返回ETIMEDOUT;
- 如果对SYN分节的响应是RST,表示服务器在指定端口上没有相应的服务,返回ECONNREFUSED;
- 如果发出 SYN在中间路由器上引发一个目的地不可达ICMP错误,在规定时间内经过重发仍无效,则返回EHOSTUNREACH或ENETUNREACH错误。
8.bind函数
#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(地址已使用)。
9.listen函数
#include <sys/socket.h>int listen(int sockfd, int backlog);
返回:0—成功,-1—出错。
listen把未连接的套接口转化为被动套接口,指示内核应接受指向此套接口的连接请求。第二个参数规定了内核为此套接口排队的最大连接数。
参数backlog曾经规定为监听套接口上的未完成连接队列和已完成连接队列总和的最大值,但各个系统的定义方法都不尽相同;历史上常把backlog置为5,但对于繁忙的服务器是不够的;backlog的设置没有一个通用的方法,依情况而定,但不要设为0。
10.accept函数
#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置为空指针。
11.close函数
#include <unistd.h>int close(int sockfd);
返回:0—OK,-1—出错。
TCP套接口的close缺省功能是将套接口做上“已关闭”标记,并立即返回到进程。这个套接口描述字不能再为进程使用,但TCP将试着发送已排队待发的任何数据,然后按正常的TCP连接终止序列进行操作。
close把描述字的访问计数减1,当访问计数仍大于0时,close并不会引发TCP的四分组连接终止序列。若确实要发一个FIN,可以用函数shutdown。
12.getsockname和getpeername
#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是值-结果参数。
使用场合:
- 在不调用bind的TCP客户,当connect成功返回后,getsockname返回分配给此连接的本地IP地址和本地端口号;
- 在以端口号为0调用bind后,使用getsockname返回内核分配的本地端口号;
- getsockname可用来获取某套接口的地址族;
- 在捆绑了通配IP地址的TCP服务器上,当连接建立后,可以使用getsockname获得分配给此连接的本地IP地址;
- 当一个服务器调用exec启动后,他获得客户身份的唯一途径是调用getpeername函数。
13.select函数
#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取值的三种情况:
- 永远等下去:仅在有一个描述字准备好I/O时才返回,设置timeout为空指针;
- 等待固定时间:在有一个描述字准备好I/O时返回,但不超过由timeout参数所指定的秒数和微秒数;
- 根本不等待:检查描述字后立即返回,将timeout中的秒数和微秒数都设置为0。
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。
套接口准备好读的条件:
- 套接口接收缓冲区中的数据字节数大于等于套接口接收缓冲区低潮限度的当前值。对这样的套接口的读操作将不阻塞并返回一个大于0的值(即准备好读入的数据量)。可以用套接口选项SO_RCVLOWAT来设置低潮限度,对于TCP和UDP,缺省值为1;
- 连接的读这一半关闭(接收了FIN的TCP连接)。对这样的套接口读操作将不阻塞并且返回0(即文件结束符);
- 套接口是一个监听套接口且已完成的连接数为非0;
- 有一个套接口错误待处理。对这样的套接口读操作将不阻塞且返回一个错误,errno设置成明确的错误条件。这些待处理错误也可以通过指定套接口选项SO_ERROR调用getsockopt来取得并清除。
- 套接口发送缓冲区中的可用字节数大于等于套接口发送缓冲区低潮限度的当前值,且或者(1)套接口已连接,或者(2)套接口不要求连接(如UDP套接口)。可以用套接口选项SO_SNDLOWAT来设置此低潮限度,对于TCP和UDP,缺省值为2048;
- 连接的写这一半关闭。对这样的套接口写将产生信号SIGPIPE;
- 有一个套接口错误待处理。对这样的套接口写操作将不阻塞且返回一个错误,errno设置成明确的错误条件。这些待处理错误也可以通过指定套接口选项SO_ERROR调用getsockopt来取得并清除。
一个套接口出错时,它被select标记为既可读又可写。
14.shutdown函数
#include <sys/socket.h>int shutdown(int sockfd, int howto);
返回:0—成功,-1—失败。
函数的行为依赖于参数howto的值:
- SHUT_RD:关闭连接的读这一半,不再接收套接口中的数据且留在套接口缓冲区中的数据都作废。进程不能再对套接口任何读函数。调用此函数后,由TCP套接口接收的任何数据都被确认,但数据本身被扔掉。
- SHUT_WR:关闭连接的写这一半,在TCP场合下,这称为半关闭。当前留在套接口发送缓冲区中的数据都被发送,后跟正常的TCP连接终止序列。此半关闭不管套接口描述字的访问计数是否大于0。进程不能再执行对套接口的任何写函数。
15.pselect函数
#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的变化:
- pselect使用结构timespec:
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
新结构中的tv_nsec规定纳秒数。 - pselect增加了第六个参数:指向信号掩码的指针。允许程序禁止递交某些信号。
16.poll函数
#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指定函数返回前等待多长时间,单位是毫秒。可能值如下:
- INFTIM,永远等待;
- 0,立即返回,不阻塞;
- >0,等待指定数目的毫秒数。
常量 | 能作为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)。术语来自流的概念。
返回条件:
- 所有正规TCP数据和UDP数据都被认为是普通数据;
- TCP的带外数据被认为是优先级带数据;
- 当TCP连接的读这一半关闭时(如接收了一个FIN),这也认为是普通数据,且后续的读操作将返回0;
- TCP连接存在错误既可以认为是普通数据,也可以认为是错误(POLLERR)。无论哪种情况,后续的读操作将返回-1,并将errno置为适当的值,这就处理了诸如接收到RST或超时等条件;
- 在监听套接口上新连接的可用性既可认为是普通数据,也可以认为是优先级带数据,大多数实现都将其作为普通数据考虑。
- 如果不关心某个特定的描述字,可将其pollfd结构的fd成员置为一个负值,这样就可以忽略成员events,且返回时将成员revents的值置为0。
17.getsockopt和setsockopt
#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已连接套接口从监听套接口继承来的:
- SO_DEBUG;
- SO_DONTROUTE;
- SO_KEEPALIVE;
- SO_LINGER;
- SO_OOBINLINE;
- SO_RCVBUF;
- SO_SNDBUF。
18.套接口选项列表
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 |
详细说明:
SO_BROADCAST
使能或禁止进程发送广播消息的能力。只有数据报套接口支持广播,并且还必须在支持广播消息的网络上(如以太网、令牌环网等)。如果目的地址是广播地址但此选项未设,则返回EACCES错误。
SO_DEBUG
仅仅TCP支持。当打开此选项时,内核对TCP在此套接口所发送和接收的所有分组跟踪详细信息。这些信息保存在内核的环形缓冲区内,可由程序trpt进行检查。SO_DONTROUTE
此选项规定发出的分组将旁路底层协议的正常路由机制。该选项经常由路由守护进程(routed和gated)用来旁路路由表(路由表不正确的情况下),强制一个分组从某个特定接口发出。
SO_ERROR
当套接口上发生错误时,源自Berkeley的内核中的协议模块将此套接口的名为so_error的变量设为标准的UNIX Exxx值中的一个,它称为此套接口的待处理错误(pending error)。内核可立即以以下两种方式通知进程:- 如果进程阻塞于次套接口的select调用,则无论是检查可读条件还是可写条件,select都返回并设置其中一个或所有两个条件。
- 如果进程使用信号驱动I/O模型,则给进程或进程组生成信号SIGIO。
当进程调用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_KEEPALIVE
打开此选项后,如果2小时内在此套接口上没有任何数据交换,TCP就会自动给对方发一个保持存活探测分节,结果如下:- 对方以期望的ACK响应,则一切正常,应用程序得不到通知;
- 对方以RST响应,套接口的待处理错误被置为ECONNRESET,套接口本身则被关闭;
- 对方对探测分节无任何响应,经过重试都没有任何响应,套接口的待处理错误被置为ETIMEOUT,套接口本身被关闭;若接收到一个ICMP错误作为某个探测分节的响应,则返回相应错误。
SO_LINGER
此选项指定函数close对面向连接的协议如何操作(如TCP)。缺省close操作是立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方。SO_LINGER选项用来改变此缺省设置。使用如下结构:
struct linger {
int l_onoff; /* 0 = off, nozero = on */
int l_linger; /* linger time */
};
有下列三种情况:
- l_onoff为0,则该选项关闭,l_linger的值被忽略,等于缺省情况,close立即返回;
- l_onoff为非0,l_linger为0,则套接口关闭时TCP夭折连接,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST给对方,而不是通常的四分组终止序列,这避免了TIME_WAIT状态;
- l_onoff为非0,l_linger为非0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。如果套接口缓冲区中仍残留数据,进程将处于睡眠状态,直到(a)所有数据发送完且被对方确认,之后进行正常的终止序列(描述字访问计数为0)或(b)延迟时间到。此种情况下,应用程序检查close的返回值是非常重要的,如果在数据发送完并被确认前时间到,close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失。close的成功返回仅告诉我们发送的数据(和FIN)已由对方TCP确认,它并不能告诉我们对方应用进程是否已读了数据。如果套接口设为非阻塞的,它将不等待close完成。
让客户知道服务器已经读其数据的一个方法时:调用shutdown(SHUT_WR)而不是调用close,并等待对方close连接的本地(服务器)端。
SO_OOBINLINE
此选项打开时,带外数据将被保留在正常的输入队列中(即在线存放)。当发生这种情况时,接收函数的MSG_OOB标志不能用来读带外数据。SO_RCVBUF和SO_SNDBUF
每个套接口都有一个发送缓冲区和一个接收缓冲区,使用这两个套接口选项可以改变缺省缓冲区大小。当设置TCP套接口接收缓冲区的大小时,函数调用顺序是很重要的,因为TCP的窗口规模选项是在建立连接时用SYN与对方互换得到的。对于客户,SO_RCVBUF选项必须在connect之前设置;对于服务器,SO_RCVBUF选项必须在listen前设置。
TCP套接口缓冲区的大小至少是连接的MSS的三倍,而必须是连接的MSS的偶数倍。
SO_RCVLOWAT和SO_SNDLOWAT
每个套接口有一个接收低潮限度和一个发送低潮限度,他们由函数select使用。这两个选项可以修改他们。接收低潮限度是让select返回“可读”而在套接口接收缓冲区中必须有的数据量,对于一个TCP或UDP套接口,此值缺省为1。发送低潮限度是让select返回“可写”而在套接口发送缓冲区中必须有的可用空间,对于TCP套接口,此值常为2048。
SO_RCVTIMEO和SO_SNDTIMEO
使用这两个选项可以给套接口设置一个接收和发送超时。通过设置参数的值为0秒和0微秒来禁止超时。缺省时两个超时都是禁止的。接收超时影响5个输入函数:read、readv、recv、recvfrom和recvmsg;发送超时影响5个输出函数:write、writev、send、sendto和sendmsg。
SO_REUSEADDR和SO_REUSEPORT
SO_REUSEADDR提供如下四个功能:- SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知端口,即使以前建立的将此端口用做他们的本地端口的连接仍存在。这通常是重启监听服务器时出现,若不设置此选项,则bind时将出错。
- SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可。对于TCP,我们根本不可能启动捆绑相同IP地址和相同端口号的多个服务器。
- SO_REUSEADDR允许单个进程捆绑同一端口到多个套接口上,只要每个捆绑指定不同的本地IP地址即可。这一般不用于TCP服务器。
- SO_REUSEADDR允许完全重复的捆绑:当一个IP地址和端口绑定到某个套接口上时,还允许此IP地址和端口捆绑到另一个套接口上。一般来说,这个特性仅在支持多播的系统上才有,而且只对UDP套接口而言(TCP不支持多播)。
- 此选项允许完全重复捆绑,但仅在想捆绑相同IP地址和端口的套接口都指定了此套接口选项才性。
- 如果被捆绑的IP地址是一个多播地址,则SO_REUSEADDR和SO_REUSEPORT等效。
- 在所有TCP服务器中,在调用bind之前设置SO_REUSEADDR套接口选项;
- 当编写一个同一时刻在同一主机上可运行多次的多播应用程序时,设置SO_REUSEADDR选项,并将本组的多播地址作为本地IP地址捆绑。
SO_TYPE
该选项返回套接口的类型,返回的整数值是一个诸如SOCK_STREAM或SOCK_DGRAM这样的值。SO_USELOOPBACK
该选项仅用于路由域(AF_ROUTE)的套接口,它对这些套接口的缺省设置为打开(这是唯一一个缺省为打开而不是关闭的SO_xxx套接口选项)。当此套接口打开时,套接口接收在其上发送的任何数据的一个拷贝。禁止这些回馈拷贝的另一个方法是shutdown,第二个参数应设为SHUT_RD。
IP_HDRINCL
如果一个原始套接口设置该选项,则我们必须为所有发送到此原始套接口上的数据报构造自己的IP头部。IP_OPTIONS
设置此选项允许我们在IPv4头部中设置IP选项。这要求掌握IP头部中IP选项的格式信息。IP_RECVDSTADDR
该选项导致所接收到的UDP数据报的目的IP地址由函数recvmsg作为辅助数据返回。IP_RECVIF
该选项导致所接收到的UDP数据报的接口索引由函数recvmsg作为辅助数据返回。IP_TOS
该选项使我们可以给TCP或UDP套接口在IP头部中设置服务类型字段。如果我们给此选项调用getsockopt,则放到外出IP数据报头部的TOS字段中的当前值将返回(缺省为0)。还没有办法从接收到的IP数据报中取此值。可以将TOS设置为如下的值:
- IPTOS_LOWDELAY:最小化延迟
- IPTOS_THROUGHPUT:最大化吞吐量
- IPTOS_RELIABILITY:最大化可靠性
- IPTOS_LOWCOST:最小化成本
IP_TTL
用次选项,可以设置和获取系统用于某个给定套接口的缺省TTL值(存活时间字段)。与TOS一样,没有办法从接收到的数据报中得到此值。ICMP6_FILTER
可获取和设置一个icmp6_filter结构,他指明256个可能的ICMPv6消息类型中哪一个传递给在原始套接口上的进程。IPV6_ADDRFORM
允许套接口从IPv4转换到IPv6,反之亦可。IPV6_CHECKSUM
指定用户数据中校验和所处位置的字节偏移。如果此值为非负,则内核将(1)给所有外出分组计算并存储校验和;(2)输入时检查所收到的分组的校验和,丢弃带有无效校验和的分组。此选项影响出ICMPv6原始套接口外的所有IPv6套接口。如果指定的值为-1(缺省值),内核在此原始套接口上将不给外出的分组计算并存储校验和,也不检查所收到的分组的校验和。IPV6_DSTOPTS
设置此选项指明:任何接收到的IPv6目标选项都将由recvmsg作为辅助数据返回。此选项缺省为关闭。IPV6_HOPLIMIT
设置此选项指明:接收到的跳限字段将由recvmsg作为辅助数据返回。IPV6_HOPOPTS
设置此选项指明:任何接收到的步跳选项都将由recvmsg作为辅助数据返回。IPV6_NEXTHOP
这不是一个套接口选项,而是一个可指定个sendmsg的辅助数据对象的类型。此对象以一个套接口地址结构指定某个数据报的下一跳地址。IPV6_PKTINFO
设置此选项指明:下面关于接收到的IPv6数据报的两条信息将由recvmsg作为辅助数据返回:目的IPv6地址和到达接口索引。IPV6_PKTOPTIONS
大多数IPv6套接口选项假设UDP套接口使用recvmsg和sendmsg所用的辅助数据在内核与应用进程间传递信息。TCP套接口使用IPV6_PKTOPTIONS来获取和存储这些值。IPV6_RTHDR
设置此选项指明:接收到的IPv6路由头部将由recvmsg作为辅助数据返回。IPV6_UNICAST_HOPS
类似于IPv4的IP_TTL,它的设置指定发送到套接口上的外出数据报的缺省跳限,而它的获取则返回内核将用于套接口的跳限值。为了从接收到的IPv6数据报中得到真实的跳限字段,要求使用IPV6_HOPLIMIT套接口选项。TCP_KEEPALIVE
它指定TCP开始发送保持存活探测分节前以秒为单位的连接空闲时间。缺省值至少为7200秒,即2小时。该选项仅在SO_KEEPALIVE套接口选项打开时才有效。TCP_MAXRT
它指定一旦TCP开始重传数据,在连接断开之前需经历的以秒为单位的时间总量。值0意味着使用系统缺省值,值-1意味着永远重传数据。TCP_MAXSEG
允许获取或设置TCP连接的最大分节大小(MSS)。返回值是我们的TCP发送给另一端的最大数据量,他常常就是由另一端用SYN分节通告的MSS,除非我们的TCP选择使用一个比对方通告的MSS小的值。如果此选项在套接口连接之前取得,则返回值为未从另一端收到的MSS选项的情况下所用的缺省值。TCP_NODELAY
如果设置,此选项禁止TCP的Nagle算法。缺省时,该算法是使能的。Nagle算法的目的是减少WAN上小分组的数目。
Nagle算法常常与另一个TCP算法联合使用:延迟ACK(delayed ACK)算法。
解决多次写导致Nagle算法和延迟ACK算法负面影响的方法:
- 使用writev而不是多次write;
- 合并缓冲区,对此缓冲区使用一次write;
- 设置TCP_NODELAY选项,继续调用write多次,这是最不可取的解决方法。
TCP_STDURG
它影响对TCP紧急指针的解释。19.处理套接口的fcntl函数
#include <fcntl.h>int fcntl(int fd, int cmd, … /* arg */);
返回:依赖于参数cmd—成功,-1—失败。
函数fcntl提供了如下关于网络编程的特性:
- 非阻塞I/O:通过用F_SETFL命令设置O_NONBLOCK文件状态标志来设置套接口为非阻塞型。
- 信号驱动I/O:用F_SETFL命令来设置O_ASYNC文件状态标志,这导致在套接口状态发生变化时内核生成信号SIGIO。
- F_SETOWN命令设置套接口属主(进程ID或进程组ID),由它来接收信号SIGIO和SIGURG。SIGIO在设置套接口为信号驱动I/O型时生成,SIGURG在新的带外数据到达套接口时生成。
- F_GETOWN命令返回套接口的当前属主。
- 设置某个文件状态标志时,先取得当前标志,与新标志路逻辑或后再设置标志。
- 信号SIGIO和SIGURG与其他信号不同之处在于,这两个信号只有在已使用命令F_SETOWN给套接口指派了属主后才会生成。F_SETOWN命令的整参数arg既可以是一个正整数,指明接收信号的进程ID,也可以是一个负整数,它的绝对值是接收信号的进程组ID。
- 当一个新的套接口由函数socket创建时,他没有属主,但是当一个新的套接口从一个监听套接口创建时,套接口属主便由已连接套接口从监听套接口继承而来。
20.gethostbyname函数
#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>中的下列常值中的一个:
- HOST_NOT_FOUND;
- TRY_AGAIN;
- NO_RECOVERY;
- NO_DATA(等同于NO_ADDRESS)。
DNS小常识:
DNS中的条目称为资源记录RR(resource record),仅有少数几类RR会影响我们的名字与地址转换:- A:A记录将主机名映射为32位的IPv4地址;
- AAAA:“四A”记录将主机名映射为128位的IPv6地址;
- PTR:PTR记录(称为“指针记录”)将IP地址映射为主机名;
- MX:MX记录指定一主机作为某主机的“邮件交换器”。
- CNAME:CNAME代表“canonical name(规范名字)”,其常见的用法是为常用服务如ftp和www指派一个CNAME记录。
21.gethostbyname2函数
#include <netdb.h>struct hostent *gethostbyname2(const char *hostname, int family);
返回:非空指针—成功,空指针—出错,同时设置h_errno。
该函数允许指定地址族,其他与gethostbyname相似。
22.gethostbyaddr函数
#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记录。
23.uname函数
#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。
24.gethostname函数
#include <unistd.h>int gethostname(char *name, size_t namelen);
返回:0—成功,-1—失败。
返回当前主机的名字。name是指向主机名存储位置的指针,namelen是此数组的大小,如果有空间,主机名以空字符结束。
主机名的最大大小通常是头文件<sys/param.h>定义的常值MAXHOSTNAMELEN。
25.getservbyname函数
#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。
26.getservbyport函数
#include <netdb.h>struct servent *getservbyport(int port, const char *protname);
返回:非空指针—成功,空指针—出错。
port必须为网络字节序。例如:
sptr = getservbyport(htons(53), “udp”);
27.recv和send
#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 |
下面说明每个标志的作用:
- MSG_DONTROUTE:这个标志告诉内核目的主机在直接连接的本地网络上,不要查路由表。这是对提供这种特性的SO_DONTROUTE套接口选项的补充。该标志可以对单个输出操作提供这种特性,而套接口选项则针对某个套接口上的所有输出操作。
- MSG_DONTWAIT:这个标志将单个I/O操作设为非阻塞方式,而不需要在套接口上打开非阻塞标志,执行I/O操作,然后关闭阻塞标志。
- MSG_OOB:用send时,这个标志指明发送的是带外数据,用recv时,该标志指明要读的是带外数据而不是一般数据。
- MSG_PEEK:这个标志可以让我们查看可读的数据,在recv或recvfrom后系统不会将这些数据丢弃。
- MSG_WAITALL:由4.3BSD Reno引入,他告诉内核在没有读到请求的字节数之前不使读操作返回。如果系统支持这个标志,则可以去掉readn函数。即使设定了该标志 ,如果发生如下情况:(1)捕获了一个信号;(2)连接被终止;(3)在套接口上发生错误,这个函数返回的字节数仍会比请求的少。
28.readv和writev
#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算法。
29.readmsg和writemsg
#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_BCAST:当收到的数据报是一个链路层的广播或其目的IP地址为广播地址时,将返回此标志。
- MSG_MCAST:当收到的数据报是链路层的多播时,将返回该标志。
- MSG_TRUNC:这个标志在数据报被截断时返回。
- MSG_CTRUNC:这个标志在辅助数据被截断时返回。
- MSG_EOR:如果返回的数据不是一个逻辑记录的结尾,该标志被清位,反之则置位。TCP不使用这个标志,因为它是一种字节流协议。
- MSG_OOB:这个标志不是为TCP的带外数据返回的,它用于其他协议族(譬如OSI协议等)。
30.socketpair函数
#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管道类似,但流管道是全双工的,两个描述字都是可读写的。
31.套接口ioctl函数
#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 |
(1)套接口操作
- SIOCATMARK:如果套接口的读指针当前在带外标志上,则通过第三个参数指向的整数返回一个非零值,否则返回零。Posix.1g用sockatmark代替了这种请求。
- SIOCGPGRP:通过第三个参数指向的整数返回为接收来自这个套接口的SIGIO或SIGURG信号而设置的进程ID或进程组ID。这和fcntl的F_GETOWN相同。
- SIOCSPGRP:用第三个参数指向的整数设置进程ID或进程组ID以接收这个套接口的SIGIO或SIGURG信号。这和fcntl的F_SETOWN相同。
(2)文件操作
- FIONBIO:套接口的非阻塞标志会根据第三个参数指向的值是否为零而清除或设置。等价于fcntl的F_SETFL设置/清除O_NONBLOCK标志。
- FIOASYNC:根据第三个参数指向的值是否为零决定清除或接收套接口上的异步I/O信号。等价于fcntl的F_SETFL设置和清除O_AYNC标志。
- FIONREAD:在第三个参数指向的整数中返回套接口接收缓冲区中当前的字节数。
- FIOSETOWN:在套接口上等价于SIOCSPGRP。
- FIOGETOWN:在套接口上等价于SIOCGPGRP。
(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
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
(4)接口操作
- SIOCGIFCONF:从内核中获取系统中配置的所有接口。