索引:
1. 字节序函数
2. 字节操作函数
3. 地址转换函数
4. readn、writen和readline
5. 测试描述符类型
6. socket函数
7. connect函数
8. bind函数
9. listen函数
10. accept函数
11. close函数
12. getsockname和getpeername
13. select函数
14. shutdown函数
15. pselect函数
16. poll函数
17. getsockopt和setsockopt
18. 套接口选项列表
19. 处理套接口的fcntl函数
20. gethostbyname函数
21. gethostbyname2函数
22. ethostbyaddr函数
23. uname函数
24. gethostname函数
25. getservbyname函数
26. getservbyport函数
27. recv和send
28. readv和writev
29. readmsg和writemsg
30. socketpair函数
31. 套接口ioctl函数
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 密钥套接口
type指定套接口类型:
- SOCK_STREAM 字节流套接口
- SOCK_DGRAM 数据报套接口
- SOCK_RAW 原始套接口
protocol一般设为0,除非用在原始套接口上。
并非所有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错误。
注意:如果connect失败,则套接口将不能再使用,必须关闭,不能对此套接口再调用函数connect。
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。
在等待过程中,若进程捕获了信号并从信号处理程序返回,等待一般被中断,为了可移植性,必须准备好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。
套接口准备好读的条件:
- 套接口接收缓冲区中的数据字节数大于等于套接口接收缓冲区低潮限度的当前值。对这样的套接口的读操作将不阻塞并返回一个大于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。进程不能再执行对套接口的任何写函数。
SHUT_RDWR:连接的读这一半和写这一半都关闭。这等效于调用shutdown两次:第一次调用时用SHUT_RD,第二次调用时用SHUT_WR。
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。
poll没有select存在的最大描述字数目问题。但可移植性select要好于poll。
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。
进程然后可以通过获取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也被复位。
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完成。
l_linger的单位依赖于实现,4.4BSD假设其单位是时钟滴答(百分之一秒),但Posix.1g规定单位为秒。
让客户知道服务器已经读其数据的一个方法时:调用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不支持多播)。
SO_REUSEPORT选项有如下语义:
- 此选项允许完全重复捆绑,但仅在想捆绑相同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>
回调函数 callback