后台核心编程(五):网络编程-地址族与数据序列


 

1 分配给套接字的IP地址与端口号

IP是 Internet Protocol(网络协议)的简写,是为收发⽹络数据而分配给计算机的值。端口号并⾮赋予计算机的值,而是为了区分程序中创建的套接字而分配给套接字的端口号。

1.1 网络地址

为使计算机连接到网络并收发数据,必须为其分配IP地址。IP地址分为两类:

  • IPV4(Internet Protocol version 4)4 字节地址族
  • IPV6(Internet Protocol version 6)6 字节地址族

两者之间的主要差别是IP地址所用的字节数,目前通用的IPV4,IPV6的普及还需要时间。

1.2 网络地址分类与主机地址边界

IPV4 标准的4字节IP地址分为网络地址和主机地址,且分为A、B、C、D、E等类型

也就是说,分为以下几类:

A: 0.0.0.0 - 127.255.255,其中段0和127不可用

B: 128.0.0.0 - 191.255.255.255

C: 192.0.0.0 - 223.255.255.255

D: 224.0.0.0 - 239.255.255.255

E: 240.0.0.0 - 255.255.255.255,其中段255不可用

 

1.3 用于区分套接字的端口号

IP地址⽤于区分计算机,只要有IP地址就能向⽬标主机传输数据,但是只有这些还不够,我们需要把信息传输给具体的应⽤程序。

所以计算机⼀般有 NIC(⽹络接口卡)数据传输设备。通过 NIC 接受的数据内有端口号,操作系统参考端口号把信息传给相应的应⽤程序。
端口号由 16 位构成,可分配的端口号范围是 0~ 65535 。但是 0~1023 是知名端口,⼀般分配给特定的应⽤程序,所以应当分配给此范围之外的值。
 

2 地址信息的表示

应⽤程序中使⽤的IP地址和端口号以结构体的形式给出了定义。本节围绕结构体讨论⽬标地址的表⽰⽅法。

2.1 表示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]; //不使⽤
};

该结构体中提到的另⼀个结构体 in_addr 定义如下,它⽤来存放 32 位IP地址

struct in_addr
{
	in_addr_t s_addr; //32位IPV4地址
}

相关的数据类型:

2.2 结构体 sockaddr_in 的成员分析

  • 成员 sin_family

每种协议使用的地址族不同,比如,IPV4使用4字节的地址族,IPV6使用16字节的地址族。

AF_LOACL 只是为了说明具有多种地址族而添加的。

  • 成员 sin_port:该成员保存16位端口号,重点在于,它以网络字节序保存。
  • 成员 sin_addr:该成员保存32位 IP 地址信息,且也已网络字节序保存。
  • 成员 sin_zero:无特殊含义。只是为结构体 sockaddr_in 结构体变量地址值将一如下方式传递给 bind 函数。

如:

if (bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
	error_handling("bind() error");

此处 bind 第⼆个参数期望得到的是 sock_addr 结构体变量的地址值,包括地址族、端口号、IP地址等。

struct sockaddr
{
	sa_family_t sin_family; //地址族
	char sa_data[14]; //地址信息
}

此结构体 sa_data 保存的地址信息中需要包含IP地址和端口号,剩余部分应该填充 0 ,但是这样对于包含地址的信息⾮常⿇烦,所以出现了 sockaddr_in 结构体,然后强制转换成 sockaddr 类型,则⽣成符合 bind 条件的参数。

 

3 网络字节序与地址转换

不同的 CPU 中,4 字节整数值1在内存空间保存⽅式是不同的。

有些 CPU 这样保存:

00000000 00000000 1 00000000 00000001

有些 CPU 这样保存:

00000001 00000000 00000000 00000000

两种⼀种是顺序保存,⼀种是倒序保存 。

3.1 字节序(Order)与网络字节序

CPU 保存数据的⽅式有两种,这意味着 CPU 解析数据的⽅式也有 2 种:

  • ⼤端序(Big Endian):⾼位字节存放到低位地址
  • 小端序(Little Endian):⾼位字节存放到⾼位地址

因为这种原因,所以在通过⽹络传输数据时必须约定统⼀的⽅式,这种约定被称为⽹络字节序,⾮常简单,统⼀为⼤端序。即,先把数据数组转化成⼤端序格式再进⾏⽹络传输。

3.2 字节序转换

unsigned short ntons(unsigned short);  //转换为unsigned short类型的主机字节序
unsigned short htohs(unsigned short);  //转换为unsigned short类型的网络字节序
unsigned long ntonl(unsigned long);    //转换为unsigned long类型的主机字节序
unsigned long htohl(unsigned long);	   //转换为unsigned long类型的网络字节序

示例:

#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
	unsigned short host_port = 0x1234;
	unsigned short net_port;
	unsigned long host_addr = 0x12345678;
	unsigned long net_addr;
	net_port = htons(host_port); //转换为⽹络字节序
	net_addr = htonl(host_addr);
	printf("Host ordered port: %#x \n", host_port);
	printf("Network ordered port: %#x \n", net_port);
	printf("Host ordered address: %#lx \n", host_addr);
	printf("Network ordered address: %#lx \n", net_addr);
	return 0;
}

输出:

Host ordered port: 0x1234
Network ordered port: 0x3412
Host ordered address: 0x12345678
Network ordered address: 0x78563412

 

4 网络地址的初始化与分配

4.1 将字符串信息转换为⽹络字节序的整数型

sockaddr_in 中需要的是 32 位整数型,但是我们只熟悉点分⼗进制表⽰法,那么改如何把类似于 201.211.214.36 转换为 4 字节的整数类型数据呢 ?幸运的是,有⼀个函数可以帮助我们完成它。

#include <arpa/inet.h>
in_addr_t inet_addr(const char *string);

该函数将 char* 类型的IP字符串,转换为32位整形 in_addr_t

具体⽰例:

#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
	char *addr1 = "1.2.3.4";
	char *addr2 = "1.2.3.256";
	unsigned long conv_addr = inet_addr(addr1);
	if (conv_addr == INADDR_NONE)
		printf("Error occured! \n");
	else
		printf("Network ordered integer addr: %#lx \n", conv_addr);
	conv_addr = inet_addr(addr2);
	if (conv_addr == INADDR_NONE)
		printf("Error occured! \n");
	else
		printf("Network ordered integer addr: %#lx \n", conv_addr);
	return 0;
}

输出:

Network ordered integer addr: 0x4030201
Error occured!

1个字节能表⽰的最⼤整数是255,所以代码中 addr2 是错误的IP地址。从运⾏结果看,inet_addr 不仅可以转换地址,还可以检测有效性。

inet_aton 函数与 inet_addr 函数在功能上完全相同,也是将字符串形式的IP地址转换成整数型的IP地址。只不过该函数⽤了 in_addr 结构体,且使⽤频率更⾼。

其使用方法为:

#include <arpa/inet.h>
int inet_aton(const char *string, struct in_addr *addr);
/*
成功时返回 1 ,失败时返回 0
string: 含有需要转换的IP地址信息的字符串地址值
addr: 将保存转换结果的 in_addr 结构体变量的地址值
*/

4.2 将网络字节序的整数型转换为字符串类型

#include <arpa/inet.h>
char *inet_ntoa(struct in_addr adr);

该函数将通过参数传⼊的整数型IP地址转换为字符串格式并返回。但要小⼼,返回值为 char*,返回字符串地址意味着字符串已经保存在内存空间,但是该函数未向程序员要求分配内存,而是再内部申请了内存保存了字符串。也就是说调⽤了该函数候要⽴即把信息复制到其他内存空间。因此,若再次调⽤ inet_ntoa 函数,则有可能覆盖之前保存的字符串信息。总之,再次调⽤ inet_ntoa 函数前返回的字符串地址是有效的。若需要⻓期保存,则应该将字符串复制到其他内存空间。

4.3 网络地址初始化

struct sockaddr_in addr;
char *serv_ip = "211.217,168.13"; //声明IP地址族
char *serv_port = "9190"; //声明端口号字符串
memset(&addr, 0, sizeof(addr)); //结构体变量 addr 的所有成员初始化为0
addr.sin_family = AF_INET; //制定地址族
addr.sin_addr.s_addr = inet_addr(serv_ip); //基于字符串的IP地址初始化
addr.sin_port = htons(atoi(serv_port)); //基于字符串的IP地址端口号初始化
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值