
第三章内容比较多所以单开了一章。
地址族与数据序列
3.1 分配给套接字的IP地址和端口号
是(网络协议)的简写,是为了收发网络数据而分配给计算机的值。
端口号简单来说就是分配给套接字的序号。下面会详细讲。
网络地址(Internet Address)
地址主要分为两类:
IPv4(Internet Protocol version 4) 4字节地址族
IPv6(Internet Protocol version 6) 16字节地址族
和主要的区别是地址使用的字节数,目前主要使用的地址族还是。
标准的字节地址分为网络地址和主机地址。且分为等类型。一般不使用已被预约的类地址。

网络地址的分类与主机地址边界
只通过地址的第一个字节即可判断网络地址占用的字节数:
类地址的首字节范围:
类地址的首字节范围:
类地址的首字节范围:
还有下述表示方式。
类地址的首位以开始
类地址的前位以开始
类地址的前位以开始
用于区分套接字的端口号
用以区分计算机,只要有地址就能向目标主机传输数据。但是不同的数据(比如视频数据和网页数据)接收需要不同的套接字,我们就需要端口号来区分不同的套接字。
计算机中一般配有(,网络接口卡)数据传输设备。通过可以传递和分配端口号,操作系统会参考该端口号把数据传给相应端口的套接字。
端口号一般由位构成,可分配的端口号范围是。但是知名端口(),一般分给特定应用程序,应该要分配此范围之外的值。另外这里还有个特殊情况,和可以重复使用一个端口,但是一个端口最多只能有一个和套接字。
总结:数据传输目标地址必须包括IP地址和端口号数据才能被传输到最终的应用程序套接字。
3.2 地址信息的表示
表示IPv4地址的结构体
该结构体会作为地址信息传输给函数。
struct sockaddr_in{ sa_family_t sin_family; //地址族(Address Family) uint16_t sin_port; //16位TCP/UDP端口号 struct in_addr sin_addr; //32位IP地址 char sin_zero[8]; //不使用};该结构体提到的另外一个结构体定义如下,它用来存放32位IP地址。
struct in_addr{ In_addr_t s_addr; //32位IPv4地址};这里面的一些数据类型可以参考一下POSIX(Portable Operating System Interface,可移植操作系统接口)。它是为系列操作系统设立的标准。
| 数据类型 | 数据类型说明 | 声明的头文件 |
|---|---|---|
| int8_t uint8_t int16_t uint16_t int32_t uint32_t | signed 8-bit int unsigned 8-bit int (unsigned char) signed 16-bit int unsigned 16-bit int (unsigned short) signed 32-bit int unsigned 32-bit int (unsigned long) | sys/types.h |
| sa_family_t socklen_t | 地址族(address family) 长度(length of struct) | sys/socket.h |
| in_addr_t in_port_t | IP地址,声明为uint32_t 端口号、声明为uint16_t | netinet/in.h |
额外定义这写数据类型的原因同样是为了保证扩展性。
结构体sockaddr_in的成员分析
成员family,也就是地址族。
| 地址族(Address Family) | 含义 |
|---|---|
| AF_INET | IPv4网络通信协议中使用的地址族 |
| AF_INET6 | IPv6网络通信协议中使用的地址族 |
| AF_LOCAL | 本地通信中采用得的UNIX协议的地址族 |
成员sin_port以网络字节序保存位端口号。
成员sin_addr以网络字节序保存位地址信息,数据类型为前面的,其实也就是。
成员sin_zero无特殊含义,只是为使结构体的大小与结构体保持一致。
从之前的代码也可以看出,结构体变量的地址将以如下的方式传递给函数。
struct sockaddr_in serv_addr;....if(bind(serv_sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) == -1) error_handling("bind() error");....不过实际上函数的第二个参数期望得到结构体的变量地址,包括地址族,端口号,地址等。
struct sockaddr{ sa_family_t sin_family; //地址族(Adress Family) char sa_data[14]; //地址信息};此结构体成员中保存的地址需要保存地址和端口号,剩余部分应该填充。因而我们引入了来生成符合函数要求的字节流,最后转化为型的结构体,再传递给函数即可。
3.3 网络字节序与地址变换
字节序(Order)与网络字节序
不同向内存中保存数据的方式有两种:
大端序():高位字节存放到低位地址。
小端序():高位字节存放到高位地址。
拿整数举个例子:
| 端序 | 0x20号 | 0x21号 | 0x22号 | 0x23号 |
|---|---|---|---|---|
| 大端序 | 0x12 | 0x34 | 0x56 | 0x78 |
| 小端序 | 0x78 | 0x56 | 0x34 | 0x12 |
因此,代表数据保存方式的主机字节序(Host By Order)在不同之间也各不相同。
为了统一在传输过程中的字节序保证数据正确传输,我们在用网络传输数据的时候统一使用大端序,即网络字节序(Network Byte Order)。
字节序转换(Endian Conversions)
以下为一些帮助转换字节序的函数:
unsigned short htons(unsigned short);unsigned short ntohs(unsigned short);unsigned long htonl(unsigned long);unsigned long ntohl(unsigned long);我们可以通过函数名了解它们的功能:
的代表主机()字节序,的代表网络()字节序。
代表(个字节),指的是(里面类型占用个字节)。
3.4 网络地址的初始化与分配
将字符串信息转换为网络字节序的整数型
中保存地址信息的成员为位整数型。但我们的地址一般是用点分十进制表示法(Dotted Decimal Notation),比如说。不过我们有将地址字符串转化为大端序整数的函数:
#include <arpa/inet.h>
in_addr_t inet_addr(const char * string);//成功时返回32位大端序整数型值,失败是返回INADDR_NONE。inet_aton函数与inet_addr函数在功能上完全相同,也将字符串形式地址转化为位网络字节序整数并返回。只不过该函数还利用了in_addr结构体,且使用频率更高。
#include <arpa/inet.h>
//string:需要转换的IP地址信息字符串地址,addr:将保存转换信息结果的in_addr结构体变量地址值int inet_aton(const char * string, struct in_addr * addr);//成功时返回1(true),失败时返回0(false)。若要调用inet_addr函数,需将转换后的IP地址信息代人sockaddr_in结构体中声明的in_addr结构体变量。而inet_aton函数会自动把结果填入该结构体变量,不需要此过程。
然后还有一个相反的inet_aton函数,它可以把网络字节序整数型地址转换成我们熟悉的字符串。
#include <arpa/inet.h>
char * inet_ntoa(struct in_addr adr);//成功时返回转换的字符串地址值,失败时返回-1。这里要注意一点,由于这里的返回值是指针,也就是系统自动分配了内存来存储这个字符串,当我们下次调用这个函数的时候就会覆盖掉上一个字符串,如果需要保存字符串就要在第二次调用前复制这个字符串。
网络地址初始化
套接字创建过程中常见的网络地址信息初始化方法。
struct sockaddr_in addr;char * serv_ip = "211.217.168.13"; //声明IP地址字符串char * serv_port = "9190"; //声明端口号字符串memset(&addr, 0, sizeof(addr)); //结构体变量addr的所有成员初始化为0addr.sin_family = AF_INET; //指定地址族addr.sin_addr.s_addr = inet_addr(serv_ip); //基于字符串的IP地址初始化addr.sin_port = htons(atoi(serv_port)); //基于字符串的端口号初始化客户端地址信息初始化
上述网络地址的初始化主要针对服务器端,给套接字分配地址和端口号的原因主要是要传输数据,而对于客户端来说则是要连接到服务器相应的网络地址。所以服务器端的准备工作是通过函数完成的,而客户端主要是通过函数完成的。因此,函数调用前需准备的地址值类型也不同。服务器端声明结构体变量,将其初始化为赋予服务器端和套接字的端口号,然后调用函数。而客户端则声明结构体,并初始化为要与之连接的服务器端套接字的和端口号,然后调用函数。
INADDR_ANY
每次创建服务器端套接字都要输入地址会有些繁琐,此时可如下初始化地址信息:
struct sockaddr_in addr;char * serv_addr = "9190";memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_addr.s_addr = htonl(INADDR_ANY);add.sin_port = htons(atoi(serv_addr));这里利用常数INADDR_ANY自东分配服务器端的地址,可自动获取运行服务器端的计算机地址。若同一计算机中已分配多个地址(多宿主()计算机,一般路由器属于这一类),则只要端口号一致,就可以从不同接收数据。
这里还要提到一点,是回送地址(loopback address),指的是计算机自身地址,所以在第一章的示例中目标服务器的地址就是这个,如果是在不同计算机就需要输入正确的服务器地址。
向套接字分配网络地址
#include <sys/socket.h>
//sockfd:对应的文件描述符,myaddr:存有地址信息的结构体变量地址,addrlen:第二个结构体变量的长度int bind(int sockfd, struct sockaddr * myaddr, socklen_t addrlen);//成功时返回0,失败时返回-1。下面给出服务器端常见套接字初始化过程:
int serv_sock;struct sockaddr_in serv_addr;char * serv_port = "9190";
/* 创建服务器端套接字(监听套接字) */serv_sock = socket(PF_INET, SOCK_STREAM, 0);
/* 地址信息初始化 */memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(atoi(serv_port));
/* 分配地址信息 */bind(serv_sock, (struct sockaddr * )&serv_addr, sizeof(serv_addr));....3.5 基于Windows的实现
诸如等函数均可以在下使用,故不赘述。唯一不同的是不存在函数。
WSAStringToAddress & WSAAddressToString
此外还有两个特殊的转换函数,不过它们在功能上与和完全相同,但优点在于支持多种协议,在和中均适用。
#include <winsock2.h>
INT WSAStringToAddress( LPTSTR AddressString, INT AddressFamily, LPWSAPROTOCOL_INFO lpProtocolInfo, LPSOCKADDR lpAddress, LPINT lpAddressLength);//成功时返回0,失败时返回SOCKET_ERROR。//AddressString 含有IP和端口号的字符串地址值。//AddressFamily 第一个参数中地址所属的地址族信息。//IpProtocolInfo 设置协议提供者(Provider) ,默认为NULL。//IpAddress 保存地址信息的结构体变量地址值。//IpAddressLength 第四个参数中传递的结构体长度所在的变量地址值。与在功能上正好相反,它将结构体中的地址信息转换成字符串形式。
#include <winsock2.h>
INT WSAAddressToString( LPSOCKADDR lpsaAddress, DWORD dwAddressLength, LPWSAPROTOCOL_INFO lpProtocolInfo, LPSTR lpszAddressString, LPDWORD lpdwAddressStringLength);//成功时返回0,失败时返回SOCKET_ERROR。//IpsaAddress 需要转换的地址信息结构体变量地址值//dwAddressLength 第一个参数中结构体的长度。//IpProtocolInfo 设置协议提供者(Provider),默认为NULL。//IpszAddressString 保存转换结果的字符串地址值。//IpdwAddressStringLength 第四个参数中存有地址信息的字符串长度。中出现的和的本质一样,只是用了声明。
typedef struct sockaddr_in SOCKADDR_IN;