本章开始讲解套接字API。
套接字地址结构
IPv4套接字地址结构
它以sockaddr_in命名,下面给出它的POSIX定义
struct in_addr
{
in_addr_t s_addr;
}; stuct sockaddr_in
{
uint8_t sin_len;
sa_family_t sin_family; /* AF_INET */
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[];
};
POSIX规范只需要这个结构中的3个字段:sin_family、sin_port和sin_addr。
下图列出了POSIX定义的关于socket编程的数据类型
通用的套接字地址结构
为了解决任意套接字地址结构都可以传递进使用套接字地址的套接字函数中,定义了一个通用的套接字地址结构。
任何套接字地址结构的指针都通过类型强制转换(变成通用套接字地址结构的指针)来做为函数参数。
通用的套接字地址结构如下所示
struct sockaddr
{
uint8_t sa_len;
sa_family_t sa_family;
char sa_data[];
};
IPv6套接字地址结构
struct in_addr
{
in_addr_t s_addr;
}; struct sockaddr_in
{
uint8_t sin_len;
sa_family_t sin_family; /* AF_INET6 */
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[];
};
字节排序函数
网络字节序采用大端字节序,而不同主机使用不同的字节序。
下面4个函数用于主机字节序和网络字节序之间的相互转换
#include <netinet/in.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);
//均返回:主机字节序的值;
h代表host,n代表network,s代表short,l代表long。
字节操纵函数
下面函数用于字节的设置、复制和比较。
#include <strings.h>
void bzero(void *dest, size_t nbytes);
void bcopy(const void *src, void *dest, size_t nbytes);
void bcmp(const void *ptr1, const void *ptr2, size_t nbytes);
//返回:相等则为0,否则为非0
#include <string.h>
void *memset(void *dest, int c, size_t nbytes);
void *memcpy(void *dest, const void *src, size_t nbytes);
int memcmp(const void *s1, const void *s2, size_t nbytes);
//返回:相等则为0,否则为非0
函数inet_aton、inet_addr和inet_ntoa
这几个函数用于在点分十进制数串(如“206.168.112.96”)于它长度为32位的网络字节序二进制值间转换IPv4地址。
#include <arpa/inet.h>
int_addr_t inet_addr(const char *strptr);
//已被舍弃,使用inet_aton函数代替
int inet_aton(const char *strptr, struct in_addr *addrptr);
//返回:字符串有效返回1,否则为0
char *inet_ntoa(struct in_addr inaddr);
//返回:指向一个点分十进制的字符串的指针
函数inet_pton和inet_ntop
相对于上面的几个函数,这两个函数对于IPv4地址和IPv6地址都适用
#include <arpa/inet.h>
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
p代表表达(presentation) n代表数值(numeric)
family参数既可以是AF_INET,也可以是AF_INET6。
inet_pton尝试转换由strptr指针指向的字符串,并通过addrptr指针存放二进制结果。
inet_ntop进行相反的转换,len参数是目标储存单元的大小,以免该函数一处其调用者的缓冲区。对此,<netinet/in.h>头文件中由如下定义:
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46
UNP中定义的几个函数
inet_ntop函数的一个基本问题是:要求调用者必须知道这个结构的格式和地址族
我们可以使用自己定义的sock_ntop函数来解决这个问题
#include "unp.h"
char *sock_ntop(const struct sodkaddr *sockaddr, socklen_t addrlen);
该函数内部使用sockaddr指针来确定地址族,然后调用inet_ntop。
同样的,我们为操作套接字地址结构定义了如下几个函数
readn、writen和readline函数
字节流套接字(例如TCP套接字)上read和write函数所表现的行为不同于通常的文件
字节流套接字上调用read或write输入或输出的字节数可能比请求的数量少,然而这不是出错的状态,此时所需的是调用者再次调用read或write函数,以输入或输出剩余的字节。
我们提供一下3个函数是每当我们读或写一个字节流套接字时总要使用的函数
#include "unp.h"
ssize_t readn(int filedes,void *buff,size_t nbytes);
ssize_t writen(int filedes,const void *buff,size_t nbytes);
ssize_t readline(int filedes,void *buff,size_t nbytes);
//返回值:读或写的字节数,若出错则为-1
readn实现代码
#include "unp.h"
ssize_t readn(int fd,void *vptr,size_t n)
{
size_t nleft;
ssize_t nread;
char *ptr; ptr = vptr;
nleft = n;
while(nleft > )
{
if((nread = read(fd,ptr,nleft)) < )
{
if(errno == EINTR)
{
nread = ;
}
else
{
return -;
}
}
else if(nread == )
{
break;
}
nleft -= nread;
ptr += nread;
}
return (n-nleft);
}
writen实现代码
#include "unp.h"
ssize_t written(int fd,void *vptr,size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while(nlfet > )
{
if((nwritten = writen(fd,ptr,nleft)) <= )
{
if(nwritten < && errno ==EINTR)
{
nwritten =;
}
else
{
return -;
}
} nleft -= nwritten;
ptr += nwritten;
}
return n;
}
readline实现代码
#include "unp.h"
ssize_t readline(int fd, void *vptr, size_t maxlen)
{
ssize_t n,rc;
char c,*ptr; ptr = vptr;
for(n = ; n < maxlen; n++)
{
if( (rc = read(fd, &c)) == )
{
*ptr ++ = c;
if(c == '\n')
break;
}
else if (r c== )
{
*ptr = ;
return (n-);
}else
return (-);
} *ptr = ;
return (n);
}