大多数套接字函数都需要一个指向套接字地址结构的指针作为参数。每个协议族都定义自己的套接字地址结构,名字均以sockaddr_
开头,并以对应每个协议族的唯一后缀结尾。
以sockaddr_in
命名,定义在
头文件中。定义如下:
struct in_addr
{
in_addr_t s_addr; /*32-bit IPv4 address*/
};
struct sockaddr_in
{
unit8_t sin_len; /*length of structure(16)*/
sa_family_t sin_family;/*AF_INET*/
in_port_t sin_port;/*16位TCP或UDP端口号*/
struct in_addr sin_addr;/*32位IPv4地址*/
char sin_zero[8];
};
说明:
当作为一个参数传递进任何套接字函数时,套接字地址结构总是以引用形式(也就是以指向该结构的指针)来传递。
在
头文件中定义了一个通用的套接字地址结构:
struct sockaddr
{
uint8_t sa_len;
sa_family_t sa_family;
char sa_data[4];
};
于是套接字函数被定义为以指向某个通用套接字地址结构的一个指针作为其参数之一,例如:
int bind(int ,struct sockaddr*,socklen_t);
这就要求对这些函数的任何调用都必须要指向特定于协议的套接字地址结构的指针进行强制类型转换,变成指向某个通用套接字地址结构的指针。
IPv6套接字地址结构在
头文件中定义:
struct in6_addr
{
unit8_t s6_addr[16];/*128位IPv6地址*/
};
#define SIN6_LEN
struct sockaddr_in6
{
uint8_t sin6_len;
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
};
说明:
SIN6_LEN
长治必须定义AF_INET6
,而IPv4的地址族是AF_INET
sin6_flowinfo
字段分成两个字段
作为IPv6套接字API的一部分而定义的新的通用套接字地址结构克服了现有struct sockaddr
的一些缺点。不像struct sockaddr
,新的struct sockaddr_storage
足以容纳系统所支持的任何套接字地址结构。sockaddr_storage
结构在
头文件中定义。
struct sockaddr_storage
{
uint8_t ss_len;
sa_family_t ss_family;
};
sockaddr_storage
类型提供的通用套接字地址结构相比sockaddr
存在以下两点差别:
注意:除了ss_family和ss_len外(如果有的话),sockaddr_storage结构中的其他字段对用户来说是透明的。sockaddr_storage结构必须强制类型转换成或复制到适合于ss_family字段所给出地址类型的套接字地址结构中,才能访问其他字段。
当往一个套接字函数传递一个套接字地址结构时,该结构总是以引用形式来传递,即传递的是指向该结构的一个指针。该结构的长度也作为一个参数传递,不过其传递的方式取决于该结构的传递方向:是从进程到内核,还是从内核到进程。
(1)从进程到内核传递套接字地址结构的函数有三个:bind、connect、和sendto。这些函数的一个参数是指向某个套接字地址结构的指针,另一个蚕食是该结构的整数大小,例如:
struct sockaddr_in serv;
connect(sockfd,(SA*)&serv,sizeof(serv));
内核知道到底需要从进程复制多少数据进来。
(2)从内核到进程传递套接字地址结构的函数有4个:accept、recvfrom、getsockname和getpeername。这4个函数的其中两个参数是指向某个套接字地址结构的指针和指向表示该结构大小的整数变量的指针,例如:
struct sockaddr_un cli;
socklen_t len;
len=sizeof(cli);
getpeername(unixfd,(SA*)&cli,&len);
其中,传入&len
的原因在于:当函数被调用时,结构大小是一个值
,它告诉内核该结构的大小,当函数返回时,结构大小是一个结果
,它告诉进程内核在该结构中究竟存储了多少信息。这种类型的参数称为值-结果参数。
当使用值-结果参数作为套接字地址结构的长度时,如果套接字地址结构是固定长度,那么从内核返回的值总是那个固定长度,然而对于可变长度的套接字地址结构,返回值可能小于该结构的最大长度。
考虑一个16位整数,由2个字节组成。内存中存储这两个字节有两种方法:一种是将低序字节存储在起始地址,称为小端字节序;另一种方法是将高序字节存储在起始地址,称为大端字节序。
将某个给定系统所用的字节序称为主机字节序。下图程序输出主机字节序。
#include"unp.h"
int main(int argc,char* argv[])
{
union{
short s;
char c[sizeof(short)];
}un;
un.s=0x0102;
printf("%s: ",CPU_VENDOR_OS);
if(sizeof(short)==2)
{
if(un.c[0]==1&&un.c[1]==2)
printf("big-endian\n");
else if(un.c[0]==2&&un.c[1]==1)
printf("littlie-endian\n");
else
printf("unknown\n");
}
else
printf("sizeof(short)=%d\n",sizeof(short));
exit(0);
}
编译后运行结果如下:
在一个短整数变量中存放两个字节的值0x0102
,然后查看其两个连续字节c[0]
和c[1]
,以此确定字节序。
字符串CPU_VENDOR_OS
是由GNU的autoconf程序在配置此教材中的软件时确定的,标识CPU类型、厂家和操作系统版本。
网络协议必须指定一个网络字节序,因此必须清楚不同字节序之间的差异。
举例来说,每个TCP分组中都有16位端口号和32位的IPv4地址。发送协议栈和接收协议栈必须就这些多字节字段各个字节的传送顺序达成一致。网际协议使用大端字节序来传送这些多字节整数。
主机字节序和网络字节序之间的转换使用以下4个函数:
#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);
#include
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
bzero把目标字节串中指定数目的字节置0。经常使用该函数把一个套接字地址结构初始化为0。bcopy将指定数目的字节从源字节串移到目标字节串。bcmp比较两个任意的字节串,若相同则返回值为0,否则返回值为非0。
地址转换函数:在ASCII字符串与网络字节序的二进制(存放在套接字地址结构中的值)之间转换网际地址。
(1)inet_aton
、inet_addr
和inet_ntoa
在点分十进制数串(例如"206.168.112.96")与它长度为32位的网络字节序二进制间转换IPv4地址。
#include
int inet_aton(const char* strptr,struct in_addr* addrptr);//若字符串有效为1,否则为0
in_addr_t inet_addr(const char* strptr);
//若字符串有效则为32位二进制网络字节序的IPv4地址,否则为INADDR_NONE
char* inet_ntoa(struct in_addr inaddr);//返回一个指向一个点分十进制数串的指针
inet_aton
将strptr所指C字符串转换成一个32位的网络字节序二进制值,并通过指针addrptr
来存储。若成功则返回1,否则返回0。
inet_addr
进行相同的转换,返回值为32位的网络字节序二进制值。如今已被废弃。
inet_ntoa
函数将一个32位的网络字节序二进制IPv4地址转换成相应的点分十进制数串。注意该函数的参数是一个结构而不是一个结构指针。
这两个函数是随IPv6出现的新函数,对于IPv4和IPv6地址都适用。
#include
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。len参数是目标存储单元的大小
这两个函数的family参数既可以是AF_INET
,也可以是AF_INET6
。
为有助于指定inet_ntop函数len参数,在
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46
字节流套接字(例如TCP套接字)上的read和write函数所表现的行为不同于通常的文件I/O。字节流套接字上调用read或write输入或输出的字节数可能比请求的数量少,然而这不是出错的状态。
这个现象的原因在于内核中用于套接字的缓冲区可能已经到达极限。此时需再次调用read或write函数,以输入或输出剩余的字节。