分类: 服务器与存储
2015-04-27 21:38:30
1.字节序函数
#include
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
void bzero(void *dest, size_t nbytes);
void bcopy(const void *src, void *dest, size_tnbytes);
int bcmp(const void *ptr1, const void *ptr2, size_tnbytes);
返回:0—相等,非0—不相等
#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_tnbytes);
返回:0—相同,>0或<0—不相同;进行比较操作时,假定两个不相等的字节均为无符号字符(unsigned char)。
3.地址转换函数
#include
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, constvoid *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
#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_tnbytes);
ssize-t writen(int filedes, void *buff, size_tnbytes);
ssize_t readline(int filedes, void *buff, size_tmaxlen);
返回:读写字节数,-1—出错。
实现程序见:unpv12e:lib/readn.c、lib/writen.c、lib/readline1.c和lib/readline.c。
5.测试描述符类型
#include
int isfdtype( int fd, int fdtype);
返回:1—是指定类型,0—不是指定类型,-1—出错。
要测试是否为套接口描述子,fdtype应设为S_IFSOCK。
该函数的一个实现程序,参见unpv12e:lib/isfdtype.c
6.socket函数
#include
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
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
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
int listen(int sockfd, int backlog);
返回:0—成功,-1—出错。
listen把未连接的套接口转化为被动套接口,指示内核应接受指向此套接口的连接请求。第二个参数规定了内核为此套接口排队的最大连接数。
参数backlog曾经规定为监听套接口上的未完成连接队列和已完成连接队列总和的最大值,但各个系统的定义方法都不尽相同;历史上常把backlog置为5,但对于繁忙的服务器是不够的;backlog的设置没有一个通用的方法,依情况而定,但不要设为0。
10.accept函数
#include
int accept(int sockfd, struct sockaddr *cliaddr,socklen_t *addrlen);
返回:非负描述字—OK,-1—出错。
accept从已完成连接队列头返回下一个连接,若已完成连接队列为空,则进程睡眠(套接口为阻塞方式时)。
参数cliaddr和addrlen返回连接对方的协议地址,其中addrlen是值-结果参数,调用前addrlen所指的整数值要置为cliaddr所指的套接口结构的长度,返回时由内核修改。
accept成功执行后,返回一个连接套接口描述字。
如果对客户的协议地址没有兴趣,可以把cliaddr和addrlen置为空指针。
11.close函数
#include
int close(int sockfd);
返回:0—OK,-1—出错。
TCP套接口的close缺省功能是将套接口做上“已关闭”标记,并立即返回到进程。这个套接口描述字不能再为进程使用,但TCP将试着发送已排队待发的任何数据,然后按正常的TCP连接终止序列进行操作。
close把描述字的访问计数减1,当访问计数仍大于0时,close并不会引发TCP的四分组连接终止序列。若确实要发一个FIN,可以用函数shutdown。
12.getsockname和getpeername
#include
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
#include
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指定我们要让内核测试读、写和异常条件所需的描述字。
当前支持的异常条件有两个:
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。
套接口准备好读的条件:
· 套接口接收缓冲区中的数据字节数大于等于套接口接收缓冲区低潮限度的当前值。对这样的套接口的读操作将不阻塞并返回一个大于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
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
#include
#include
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:
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
新结构中的tv_nsec规定纳秒数。
2. pselect增加了第六个参数:指向信号掩码的指针。允许程序禁止递交某些信号。
16.poll函数
#include
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
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 |