IPv4的地址结构为sockaddr_in,IPv6为sockaddr_in6,链路协议sockaddr_dl,Unix域为sockaddr_un,存储为sockaddr_storage。
**套接字地址结构总是以引用形式来传递!
struct in_addr
{
in_addr_t s_addr; // 32比特的IPv4地址,网络字节序
// 需要函数将点分十进制的地址转化为该值
};
struct sockaddr_in
{
uint8_t sin_len; // 结构长度
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]; // 没有使用
};
**IPv4地址和TCP,UDP端口号在套接字地址结构中总是以【网络字节序】来存储。
struct in6_addr {
uint8_t s6_addr[16]; /*128-bit IPv6 addr*/
};
#define SIN6_LEN /*FOR TEST*/
struct sockaddr_in6 {
uint8_t
sin6_len; /*len of this
struct(28)*/
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id; /*set of interface for a scope*/
};
**v4的地址族为AF_INET,v6的地址族为AF_INET6。
由于之前没有void *指针类型,所以在声明传递指针的数据类型时需要用一个通用的套接字地址结构,之后对相关函数的调用都要将特定的结构指针强制类型转换为通用结构指针。
比如:
struct sockaddr_in serv;
bind(sockfd, (struct sockaddr *) &serv, sizeof(serv));
意思是,既作为函数的参数,又作为函数的一部分结果。(通过引用形式传递)
(1)从进程到内核传递套接字地址结构的函数:bind, connect, sendto,两个参数一个某个套接字的指针,另一个该结构的整数大小
(2)从内核到进程的函数:accept, recvfrom, getsockname, getpeername,两个参数分别指向某套接字的指针,指向表示该结构大小的整数变量的指针。
小端:低字节在起始地址
大端:高字节在起始地址
**网络协议使用大端字节序来传送。
我们把某个给定系统所用的字节序称为主机字节序。
union结构:变量成员公用同一内存,其长度为联合中最大的变量长度。
判断主机字节序:
#include <iostream>
using namespace std;
int main()
{
union {
short s;
char bytes[2];
}un;
short sSample = 0x0102;
un.s = sSample;
if (sizeof(un.s) == 2)
{
if (un.bytes[0] == 1 && un.bytes[1] == 2)
{
cout << "big-endian" << endl;
}
else if (un.bytes[0] == 2 && un.bytes[1] == 1)
{
cout << "little-endian" << endl;
}
else
{
cout << "unknow" << endl;
}
}
else
{
cout << "size of short is not 2, but " << sizeof(short) << endl;
}
return 0;
}
在进行网络编程时,我们不需要关心机器字节序和网络字节序到底是little-endian还是big-endian,我们只需要知道数据在当前机器上的进程处理时,需要使用本机字节序,当数据在网络上传递时,需要使用网络字节序。通过下面这四个函数,可以方便的进行本机与网络字节序之间的转换:
#include <netinet/in.h>
uint16_t htons(uint16_t host_16_bit_value);
uint32_t htonl(uint32 _t host_32_bit_value);
//both return: value in network byte order
uint16_t ntohs(uint16_t net_16_bit_value);
uint32_t ntohl(uint32_t net_32_bit_value);
//both return: value in host byte order
h代表host, n代表network;
s代表short,l代表long。
(s视为一个16位的值比如端口号,l视为一个32位的值比如IPv4地址)
Unix下有两组字节操作函数,一组以b(byte)开头,是socket库提供的自己操作函数,一种以mem(memory)开头,由ANSI C提供。
#include <strings.h> // 注意这里不是<string.h>,多了一个s
void bzero(void* dest, size_t nbyte);
void bcopy(const void* src, void* dest, size_t nbyte);
int bcmp(const void* ptr1,void* ptr2, size_t nbyte);
我们通常使用bzero而不是memset,因为bzero只有两个参数!
这几个函数都是用于点分十进制IP和网络字节序二进制IP相互转换。
#include <arpa/inet.h>
/* * 注意: * 1 下面一对函数,相互转换,a代表字符串,n代表网络字节序 * 2 没有长度参数,因为点分十进制额字符串长度比较固定,程序可以分析 * 3 inet_ntoa接收的参数是值,而不是指针,比较少见,而且返回的值是在静态内容中, * 且运行修改,因为不是const * 4 inet_aton 返回1代表成功转换,0代表失败,可以理解为true和false, * 与一般的0标识成功有点不同 */
int inet_aton(const char* strptr, struct in_addr* addrptr);
char* inet_ntoa(struct in_addr inaddr);
/* * 注意: * 此方法过时,因为文档不全,更主要的是对于“255.255.255.255”转换得到结果与错误码相同,有缺陷,所以不建议使用。虽然使用起来,比inet_aton方便,但是隐患较多,所以最好不要使用。 */
in_addr_t inet_addr(const char* strptr);
这一对函数与inet_aton/inet_ntoa类似,但是支持Ipv6,添加了一个family参数接受AF_INET对应IPV4,AF_INET6对应Ipv6。”p”和”n”分别代表presentation和numeric。inet_pton/inet_ntop的函数接口更为一致。我们应在程序中使用它们。
#include <arpa/inet.h>
int inet_pton(int family, const char *strptr, struct in_addr *addrptr);
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len); // error, return NULL
// addrptr是数值格式指针,strptr是表达格式地址,如果成功返回strptr,如果失败返回NULL
//**inet_ntop的strptr参数不可以是空指针,调用者应该为目标存储单元分配内存并指定其大小
字节流套接字上调用read和write获得的字节数可能比请求的数量少(因为内核中用于套接字的缓冲区可能满了),此时需要我们多次调用read和write函数。
因此,我们使用readn,writen来代替,从而避免让调用者来处理不足的字节计数值。
readline函数每次读取一行。
慢速版本:每读一个字节调用一次read。
快速版本:提供缓冲,用my_read代替read,my_read每次最多读MAXLINE个字符,然后每次返回一个字符。