Duangw

网络API

索引:

  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. gethostbyaddr函数
  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);

返回:主机字节序值。

 

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 ― 失败

说明:

 

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 ― 出错

这些函数需要自己实现。

 

5.测试描述符类型

#include <sys/stat.h>
int isfdtype(int fd, int fdtype);
返回:1 ― 是指定类型,0 ― 不是指定类型,-1 ― 出错

要测试是否为套接口描述子,fdtype应设为S_IFSOCK。

 

6.socket函数

#include <sys/socket.h>
int socket(int family, int type, int protocol);
返回:非负描述字―成功,-1―出错

family指定协议族,有如下取值:

type指定套接口类型:

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的三路握手过程,在阻塞情况下,只有在连接建立成功或出错时该函数才返回。

出错情况:

注意:如果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是值-结果参数。

使用场合:

 

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取值的三种情况:

在等待过程中,若进程捕获了信号并从信号处理程序返回,等待一般被中断,为了可移植性,必须准备好select返回EINTR错误。

timeout的值在返回时并不会被select修改(const标志)。

readset、writeset、exceptset指定我们要让内核测试读、写和异常条件所需的描述字。

当前支持的异常条件有两个:

  1. 套接口带外数据的到达;
  2. 控制状态信息的存在,可从一个已置为分组方式的伪终端主端读到。

描述字集的使用:

数据类型: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标记为既可读又可写。

 

14.shutdown函数

#include <sys/socket.h>
int shutdown(int sockfd, int howto);
返回:0―成功,-1―失败

函数的行为依赖于参数howto的值:

 

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的变化:

  1. pselect使用结构timespec:
  2. struct timespec {
        time_t tv_sec; /* seconds */
        long tv_nsec; /* nanoseconds */
    };
    新结构中的tv_nsec规定纳秒数。
    
  3. 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指定函数返回前等待多长时间,单位是毫秒。可能值如下:

标志的范围:

常量 能作为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。

 

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已连接套接口从监听套接口继承来的:

如果想在三路握手完成时确保这些套接口选项中的某一个是给已连接套接口设置的,我们必须先给监听套接口设置此选项。

 

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)。内核可立即以以下两种方式通知进程:

  1. 如果进程阻塞于次套接口的select调用,则无论是检查可读条件还是可写条件,select都返回并设置其中一个或所有两个条件。
  2. 如果进程使用信号驱动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就会自动给对方发一个保持存活探测分节,结果如下:

  1. 对方以期望的ACK响应,则一切正常,应用程序得不到通知;
  2. 对方以RST响应,套接口的待处理错误被置为ECONNRESET,套接口本身则被关闭;
  3. 对方对探测分节无任何响应,经过重试都没有任何响应,套接口的待处理错误被置为ETIMEOUT,套接口本身被关闭;若接收到一个ICMP错误作为某个探测分节的响应,则返回相应错误。

此选项一般由服务器使用。服务器使用它是为了检测出半开连接并终止他们。

SO_LINGER

此选项指定函数close对面向连接的协议如何操作(如TCP)。缺省close操作是立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方。

SO_LINGER选项用来改变此缺省设置。使用如下结构:

struct linger {
    int l_onoff; /* 0 = off, nozero = on */
    int l_linger; /* linger time */
};

有下列三种情况:

  1. l_onoff为0,则该选项关闭,l_linger的值被忽略,等于缺省情况,close立即返回;
  2. l_onoff为非0,l_linger为0,则套接口关闭时TCP夭折连接,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST给对方,而不是通常的四分组终止序列,这避免了TIME_WAIT状态;
  3. 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提供如下四个功能:

  1. SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知端口,即使以前建立的将此端口用做他们的本地端口的连接仍存在。这通常是重启监听服务器时出现,若不设置此选项,则bind时将出错。
  2. SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可。对于TCP,我们根本不可能启动捆绑相同IP地址和相同端口号的多个服务器。
  3. SO_REUSEADDR允许单个进程捆绑同一端口到多个套接口上,只要每个捆绑指定不同的本地IP地址即可。这一般不用于TCP服务器。
  4. SO_REUSEADDR允许完全重复的捆绑:当一个IP地址和端口绑定到某个套接口上时,还允许此IP地址和端口捆绑到另一个套接口上。一般来说,这个特性仅在支持多播的系统上才有,而且只对UDP套接口而言(TCP不支持多播)。

SO_REUSEPORT选项有如下语义:

  1. 此选项允许完全重复捆绑,但仅在想捆绑相同IP地址和端口的套接口都指定了此套接口选项才行。
  2. 如果被捆绑的IP地址是一个多播地址,则SO_REUSEADDR和SO_REUSEPORT等效。

使用这两个套接口选项的建议:

  1. 在所有TCP服务器中,在调用bind之前设置SO_REUSEADDR套接口选项;
  2. 当编写一个同一时刻在同一主机上可运行多次的多播应用程序时,设置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设置为如下的值:

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算法负面影响的方法:

  1. 使用writev而不是多次write;
  2. 合并缓冲区,对此缓冲区使用一次write;
  3. 设置TCP_NODELAY选项,继续调用write多次,这是最不可取的解决方法。

TCP_STDURG

它影响对TCP紧急指针的解释。

 

19.处理套接口的fcntl函数

#include <fcntl.h>
int fcntl(int fd, int cmd, … /* arg */);
返回:依赖于参数cmd―成功,-1―失败

函数fcntl提供了如下关于网络编程的特性:

  1. 非阻塞I/O:通过用F_SETFL命令设置O_NONBLOCK文件状态标志来设置套接口为非阻塞型。
  2. 信号驱动I/O:用F_SETFL命令来设置O_ASYNC文件状态标志,这导致在套接口状态发生变化时内核生成信号SIGIO。
  3. F_SETOWN命令设置套接口属主(进程ID或进程组ID),由它来接收信号SIGIO和SIGURG。SIGIO在设置套接口为信号驱动I/O型时生成,SIGURG在新的带外数据到达套接口时生成。
  4. F_GETOWN命令返回套接口的当前属主。

注意事项:

 

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>中的下列常值中的一个:

有函数hstrerror(),它将h_errno的值作为唯一的参数,返回一个指向相应错误说明的const char *型指针。

DNS小常识:

DNS中的条目称为资源记录RR(resource record),仅有少数几类RR会影响我们的名字与地址转换:

 

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

下面说明每个标志的作用:

 

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_flags中返回一些输入的flags的标志,所以我们应该只检查那些感兴趣的标志的值。

 

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)套接口操作

(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

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结构。

(4)接口操作

(5)ARP高速缓存操作

(6)路由表操作