套接字地址结构
(头文件<netinet/in.h>)
常用套接字地址结构有IPv4和IPv6两个版本,IPv4较为常见
IPv4的地址结构定义如下:
struct in_addr
{
in_addr_t s_addr;/*32-bit IPv4 address*/
/*network byte ordered*/
};
struct sockaddr_in
{
uint8_t sin_len;/*length if structure(16)*/
sa_family_t sin_family;/*AF_INET*/
in_port_t sin_port;/*16-bit TCP or UDP port number*/
/*network byte ordered*/
struct in_addr sin_addr;/*32-bit IPv4 address*/
/*network byte ordered*/
char sin_zero[8]; /*unused*/
};
IPv6的地址结构定义如下:
#include <netinet/in.h>
struct in6_addr{
uint8_t s6_addr[16]; // 128-bit IPv6 address, network byte ordered.
};
#define SIN6_LEN // required for compile-time tests
struct sockaddr_in6{
uint8_t sin6_len; // length of this struct (28)
sa_family_t sin6_family; // AF_INET6
in_port_t sin6_port; // transport layer port, network byte ordered
uint32_t sin6_flowinfo; // flow information, undefined
struct in6_addr sin6_addr; // IPv6 address, network byte ordered
uint32_t sin6_scope_id; // set of interfaces for a scope
};
其中POSIX规范里面并不要求有长度字段sin_len或sin6_len
当作为任何套接字函数的参数时,套接字地址结构总是以引用形式来传递,所以这就要求对应的套接字函数必须处理来自所支持的任何协议族的套接字地址结构。为解决如何声明所传递的指针的数据类型这一问题,在 ANSI C 定义之前(那时还没有“void *”这一通用的指针类型)所采取的办法是定义一个如下的通用套接字地址结构。
#include <sys/socket.h>
struct sockaddr{
uint8_t sa_len;
sa_family_t sa_family; // address family: AF_XXX value
char sa_data[14]; // protocol-specific address
};
于是套接字函数就被定义成如下形式:
于是套接字函数就被定义成如下形式:
int bind(int, struct sockaddr *, socklen_t);
这就要求在将指向特定于协议的套接字地址结构的指针传递给任何的套接字函数之前都要进行强制类型转换。比如:
struct sockaddr_in serv; // IPv4 socket address structure.
bind(sockfd, (struct sockaddr *)&serv, sizeof(serv));
从内核的角度看,使用指向通用套接字地址结构的指针另有原因:内核必须取调用者的指针,把它转换成“struct sockaddr *”类型,然后检查其中 sa_family 字段的值来确定该结构的真实类型。而从应用程序开发人员的角度来看,这些通用套接字地址结构的唯一用途就是对指向特定于协议的套接字地址机构的指针执行强制类型转换。要是“void *”指针类型可用就更简单了,因为无须显示进行类型转换。
目前又新出了一种新的通用套接字地址结构,具体用途暂没理清:
struct sockaddr_storage{
uint8_t ss_len; // length of this struct (implementation dependent)
sa_family_t ss_family; // address family: AF_xxx value
/* implementation-dependent elements to provide:
* a) alignment sufficient to fullfill the alignment requirements of all
* socket address types that the system supports.
* b) enough storage to hold any type of socket address that the system supports.
*/
};
字节序
头文件#include <arpa/inet.h>
内存中存储多个字节的方法包括两种,分别称为小端字节序和大端字节序
在linux系统中可以使用byteorder系统的字节序,即主机字节序。主机字节序根据系统的不同而不同
网络协议中的字节序称为网络字节序,网络字节序固定都是大端字节序
转换字节序的函数有四个,分别为
uint32_t htonl(uint32_t hostint32);
功能:将32位主机字节序数据转换为网络字节序数据
参数:hostint32,需要转换的32位主机字节序数据,uint32_t为32位无符号整型
返回值:返回网络字节序的值
uint16_t htons(uint16_t hostint16);
功能:将16位主机字节序数据转换成网络字节序数据
参数:hostint16,需要转换的16位主机字节序数据,uint16_t为16位无符号短整型
返回值:返回网络字节序的值
uint32_t ntohl(uint32_t netint32);
功能:将32位网络字节序数据转换为主机字节序数据
参数:netint32,需要转换的32位网络字节序数据,uint32_t为32位无符号整型
返回值:返回主机字节序的值
uint16_t ntohs(uint16_t netint16);
功能:将16位网络字节序数据转换成主机字节序数据
参数:netint16,需要转换的16位网络字节序数据,uint16_t为16位无符号短整型
返回值:返回主机字节序的值
其中几个字母的意思如下:n:network h:host s:short l:long
这里的short表示16位的无符号整型,long表示32位的无符号整型
字节操纵函数
在使用套接字地址结构时,一般都需要首先把整个结构的每个字节都初始化为0
为实现这块需求,可以使用两种函数
一类是b开头的函数bzero函数,一类是memset函数
除了置0的需求外,一般还有复制字符串和字符串比较的需求,string.h也提供了这两种需求的两种实现
#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);
#include <strings.h>
void *memset(void dest,int c,size_t len);
void *memcpy(void *dest,const void *src,size_t nbytes);
int memcmp(const void *ptr1,const void *ptr2,size_t nbytes);
值得注意的是,当把memcpy的dest和src是同一个变量时,会出错,而bcopy不会
地址转换函数
为IPv4地址转换而设计的函数包括 inet_aton、inet_addr、inet_ntoa三个
inet_addr()函数
功能:inet_addr()函数用于将点分十进制IP地址转换成网络字节序IP地址;
原型:in_addr_t inet_addr(const char *cp);
返回值:如果正确执行将返回一个无符号长整数型数。如果传入的字符串不是一个合法的IP地址,将返回INADDR_NONE;注意,INADDR_NONE的转为点分十进制IP地址,正好为255.255.255.255,因此,写程序时要确保转换的地址不包含255.255.255.255
头文件:arpa/inet.h (Linux)
inet_aton()函数
功能:inet_aton()函数用于将点分十进制IP地址转换成网络字节序IP地址;
原型:int inet_aton(const char *string, struct in_addr *addr);
返回值:如果这个函数成功,函数返回1,如果输入地址不正确则会返回0;
头文件:sys/socket.h (Linux)
inet_ntoa()函数
功能inet_ntoa()函数用于网络字节序IP转化点分十进制IP;
原型:char *inet_ntoa (struct in_addr);
返回值:若无错误发生,inet_ntoa()返回一个字符指针。否则的话,返回NULL。
头文件:arpa/inet.h (Linux)
随着IPv6出现,为同时支持IPv6和IPv4,出现了inet_pton和inet_ntop函数
#include <arpe/inet.h>
int inet_pton(int family, const char *strptr, void *addrptr); //将点分十进制的ip地址转化为用于网络传输的数值格式
返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1
const char * inet_ntop(int family, const void *addrptr, char *strptr, size_t len); //将数值格式转化为点分十进制的ip地址格式
返回值:若成功则为指向结构的指针,若出错则为NULL
套接字读写函数
头文件 <unistd.h>
在网络传输中,使用socket函数的得到的int整数称为套接字描述符,该描述符和文件描述符是一个意思。实际上,系统从网络中读写数据和从本地文件读写数据所用的函数是一致的,分别为:
ssize_t write (int fd, const void * buf, size_t count);
函数说明:write()会把参数buf所指的内存写入count个字节到参数fd所指的文件内。
返回值:如果顺利write()会返回实际写入的字节数(len)。当有错误发生时则返回-1,错误代码存入errno中。
2、read()
函数定义:ssize_t read(int fd, void * buf, size_t count);
函数说明:read()会把参数fd所指的文件传送count 个字节到buf 指针所指的内存中。
返回值:返回值为实际读取到的字节数, 如果返回0, 表示已到达文件尾或是无可读取的数据,函数不会有作用。
当出错时,则返回-1
另外,以下情况返回值小于count:
(1)读常规文件时,在读到count个字节之前已到达文件末尾。例如,距文件末尾还有50个字节而请求读100个字节,则read返回50,下次read将返回0。
(2)对于网络套接字接口,返回值可能小于count,但这不是错误,这是由于用于套接字的缓冲区可能已经达到极限
read和write函数返回值小于0时,表示发生错误。其中一种错误即使发生也应该继续进行读写操作,该错误就是EINTR
基本判别代码如下:
if(errno == EINTR)
{
....
}