《TCP IP网络编程》第三章 地址族与数据序列

第三章 地址族与数据序列

本章讲给套接字分配IP地址和端口号的方法。

3.1 分配给套接字的IP地址和端口号

IP是网络协议(Internet Protocol)的简写,是为收发网络数据而分配给计算机的值。

端口号并非赋予计算机的值,而是为区分程序中创建的套接字而分配给套接字的序号。

网络地址

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

IP地址分为两类:IPv4和IPv6。主要区别是用来表示IP地址所用的字节数不同,目前通用的地址族是IPv4。

IPv4标准的4字节IP地址 == 网络地址 + 主机地址。且分为ABCDE等类型。一般不会使用已被预约了的E类地址。

  • A类地址:网络ID占1字节,主机ID占3字节
  • B类地址:网络ID占2字节,主机ID占2字节
  • C类地址:网络ID占3字节,主机ID占1字节
  • D类地址:网络ID占4字节,主机ID占0字节(这是多播IP地址)

并非一开始就浏览整个4字节的IP地址,而是先浏览网络地址,将数据传到那个网络;网络接到数据后,浏览传输数据的主机地址,将数据传递给那个目标主机。

向相应的网络传输数据,实际上是向构成网络的路由器或交换机传递数据,由接收数据的路由器根据数据中主机地址向目标主机传递数据。

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

首字节范围:

  • A类地址:0~127,首位以0开始
  • B类地址:128~191,前2位以10开始
  • C类地址:192~223,前3位以110开始
用于区分套接字的端口号

计算机中一般配有NIC(网络接口卡)数据传输设备。通过NIC向计算机内部传输数据时会用到IP。

操作系统负责把传递到内部的数据适当分配给套接字,这时会利用端口号。

通过NIC接收的数据内有端口号,操作系统正是参考此端口号把数据传输给相应端口的套接字。

端口号,就是在同一个操作系统内为区分不同套接字而设置的。
端口号由16位构成,可分配的端口号范围0~65535。但0-1023是知名端口,一般分配给特定应用程序。
虽然端口号不能重复,但TCP套接字和UDP套接字不会共用端口号,所以允许重复。

数据传输目标地址,同时包含 IP地址和端口号,这样,数据就会被传输到最终的目的应用程序(应用程序套接字)。

3.2 地址信息的表示

表示IPv4地址的结构体
struct sockaddr_in{
	sa_family_t 	sin_family;	//地址族
	uint16_t 		sin_port;	//16位TCP/UDP端口号
	struct in_addr	sin_addr;	//32位的IP地址
	char			sin_zero[8];//不使用
}

其中:
struct in_addr{
	in_addr_t		s_addr;		//32位IPv4地址
}
POSIX中定义的数据类型说明声明的头文件
int8_tsigned 8-bit intsys/types.h
uint8_tunsigned 8-bit int(unsigned char)sys/types.h
int16_tsigned 16-bit intsys/types.h
uint16_tunsigned 16-bit int(unsigned short)sys/types.h
int32_tsigned 32-bit intsys/types.h
uint32_tunsigned 32-bit int(unsigned long)sys/types.h
sa_family_t地址族(address family)sys/socket.h
socklen_t长度(length of struct)sys/socket.h
in_addr_tIP地址,声明为uint32_tnetinet/in.h
in_port_t端口号,声明为uint16_tnetinet/in.h
结构体sockaddr_in的成员分析

成员sin_family:

地址族含义
AF_INETIPv4网络协议中使用的地址族(常用)
AF_INET6IPv6网络协议中使用的地址族
AF_LOCAL本地通信中采用的UNIX协议的地址族

成员sin_port:
保存16位端口号(2字节),以网络字节序保存。

成员sin_addr:
保存32位IP地址信息(4字节),也以网络字节序保存。其中的in_addr声明为unit32_t,只需当做32位的证书型即可。

成员sin_zero:
没啥意思。只是为了使结构体sockaddr_in的大小与sockaddr结构体保持一致而插入的成员。必须填充为0。

sockaddr_in结构体变量地址值传递给bind函数:

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

bind函数的第二个参数,期望得到sockaddr结构体变量地址值,包括地址族、端口号、IP地址等。

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

bind函数要求sa_data保存的地址信息中包含IP地址和端口号,剩余部分填充为0。太麻烦了,借助sockaddr_in这个结构体辅助构造,先填好sockaddr_in,然后转换成sockaddr型的结构体变量,再传递给bind函数。

3.3 网络字节序与地址变换

大端序:高位字节存放到低位地址
小端序:高位字节存放到高位地址

将4字节的int型数0x12345678保存在0x20开始的地址中,
小端序:
低位地址 0x20 0x21 0x22 0x23 高位地址
高位字节 0x12 0x34 0x56 0x78 低位字节

大端序:
低位地址 0x20 0x21 0x22 0x23 高位地址
低位字节 0x78 0x56 0x34 0x12 高位字节

0x20是低位地址,0x23是高位地址;
0x12是高位字节,0x78是低位字节

每种CPU的数据保存方式均不同,所以,代表CPU数据保存方式的主机字节序在不同CPU中也各不相同。

在通过网络传输数据时约定统一方式,这种约定称为网络字节序——统一为大端序。就是,先把数据数组转化成大端序格式再进行网络传输。

字节序转换
unsigned short htons(unsigned short);short型数据从主机字节序转化为网络字节序
unsigned short ntohs(unsigned short);short型数据从网络字节序转化为主机字节序
unsigned long htonl(unsigned long);
unsigned long ntohs(unsigned long);

h:主机(host)字节序
n:网络(network)字节序
s:short
l:long

通常,以s作为后缀的函数中,s代表2字节short,用于端口号转换;以l作为后缀的函数中,l代表4字节long,用于IP地址转换。

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

(1)inet_addr

inet_addr函数可以将字符串形式的IP地址转换成32位整数型数据,此函数在转换类型的同时进行字节序转换,就是能将其转换成网络字节序

#include<arpa/inet.h>

in_addr_t inet_addr(const char* string);
成功时返回32位大端序整数型值,失败时返回INADDR_NONE

应用示范:

char * addr = "1.2.3.4";
unsigned long conv_addr = inet_addr(addr);

if(conv_addr == INADDR_NONE)
	printf("Error occureded \n");
else
	printf("Network ordered integer addr: %#lx \n\n", conv_addr);

最后输出:
Network ordered integer addr: 0x4030201
(2)inet_aton

inet_aton函数和inet_addr函数在功能上完全相同:

#include<arpa/inet.h>

int inet_aton(const char* string, struct in_addr * addr);
				string 	含有需要转换的IP地址信息的字符串地址值
				addr	将保存转换结果的in_addr结构体变量的地址值
成功时返回1(true),失败时返回0(false)

应用示范:

char * addr = "127.232.124.79";
struct sockaddr_in addr_inet;

if(!inet_aton(addr, &addr_inet.sin_addr)){
	error_handling("Conversion error");
}else{
	printf("Network ordered integer addr: %#x \n", addr_inet.sin_addr.s_addr);
}
(3)inet_ntoa

inet_aton功能相反,将网络字节序整数型IP地址转换为我们熟悉的字符串形式

#include<arpa/inet.h>

char* inet_ntoa(struct in_addr adr);
成功时返回字符串地址值,失败时返回-1

调用完这个函数后,最好立即将字符串信息复制到其他内存空间,放置下次调用该函数时覆盖了这次的结果:

struct sockaddr_in addr;

char * str_ptr;
char str_arr[20];
addr.sin_addr.s_addr = htonl(0x1020304);

str_ptr = inet_ntoa(addr.sin_addr);
strcpy(str_arr,str_ptr);
printf("Dotted_Decimal notation: %s \n",str_prt);	此时输出结果是1.2.3.4,后面再次调用inet_ntoa函数后,会变成新的值
printf("Dotted_Decimal notation: %s \n",str_arr);	此时输出结果是1.2.3.4,后面再次调用inet_ntoa函数后,仍保存这个值
网络地址初始化

主要是针对服务器端:

struct sockaddr_in addr;	
char* serv_ip = "211.217.168.13";  //声明IP地址字符串
char* serv_port = "9190"; //声明端口号字符串
memset(&addr, 0, sizeof(addr)); //结构体变量addr的所有成员初始化为0,这样sockaddr_in结构体的成员sin_zero初始化为0
addr.sin_family = AF_INET;	//指定地址族
addr.sin_addr.s_addr = inet_addr(serv_ip); //基于字符串的IP地址初始化
addr.sin_port = htons(atoi(serv_port)); //基于字符串的端口号初始化。atoi函数将字符串类型的端口值转换成整数型
客户端地址信息初始化

服务器端,声明sockaddr_in结构体变量,将其初始化为赋予服务器端IP和套接字的端口号,然后调用bind函数;

客户端,声明sockaddr_in结构体变量,并初始化为要与之连接的服务器端套接字IP和端口号,然后调用connect函数。

INADDR_ANY

服务器端优先采用INADDR_ANY这种方式。

char* serv_ip = "211.217.168.13";  //声明IP地址字符串
addr.sin_addr.s_addr = inet_addr(serv_ip); //基于字符串的IP地址初始化

可以缩为如下一句话:
addr.sin_addr.s_addr = htonl(INADDR_ANY); //可以自动获取运行服务器端的计算机IP地址

同一计算机中可以分配多个IP地址,实际IP地址的个数与计算机中安装的NIC的数量相等。

即使是服务器端套接字,也需要决定应接收哪个IP传来的(哪个NIC传来的)数据。

若只有1个NIC,则直接使用INADDR_ANY

第一章的hello_server.c和hello_client.c的运行过程

127.0.0.1是回送地址,指的是计算机自身IP地址。

向套接字分配网络地址

完成了sockaddr_in结构体的初始化后,接下来要把初始化的地址信息分配给套接字,bind函数负责此项操作。

#include<sys/socket.h>

int bind(int sockfd, struct sockaddr* myaddr, socklen_t addrlen);
				sockfd	要分配地址信息(IP地址和端口号)的套接字文件描述符
				myaddr	存有地址信息的结构体变量地址值
				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的实现

函数htons、htonl在Windows中的应用
#include<winsock2.h>

WSADATA wsaData;	库初始化
if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0){
	ErrorHandling("WSAStartup() error");
}

unsigned short host_port = 0x1234;
unsigned short net_port = htons(host_port);		将端口号从主机字节序变成网络字节序

unsigned long host_addr = 0x12345678;
unsigned long net_addr = htonl(host_addr);		将IP地址从主机字节序变成网络字节序

WSACleanup();		关闭库
函数inet_addr、inet_ntoa在Windows中的应用

Windows中不存在inet_aton函数。

#include<stdio.h>
#include<string.h>
#include<winsock2.h>
void ErrorHandling(char * message);

int main(int argc, char * argv[]){
	WSADATA wsaData;
	if(WSAStartup(MAKEWORD(2,2), &wsaData)!=0)
		ErrorHandling("WSAStartup() error!");
	
	{//inet_addr函数调用示例
		char* addr = "127.212.124.78";
		unsigned long conv_addr = inet_addr(addr);
		if(conv_addr == INADDR_NONE)
			printf("Error occured! \n");
		else
			printf("Network ordered integer addr: %#lx \n",conv_addr);		
	}

	{//inet_ntoa函数调用示例
		struct sockaddr_in addr;
		char * strPtr;
		char strArr[20];
		
		addr.sin_addr.s_addr = htonl(0x1020304);
		strPtr = inet_ntoa(addr.sin_addr);
		strcpy(strArr,strPtr);
		printf("Dotted-Decimal notation3 %s \n",strArr);		
	}

	WSACleanup();
	return 0;
}


void ErrorHandling(char * message){
	fputs(message,stderr);
	fputc('\n',stderr);
	exit(1);
}
Windows环境下向套接字分配网络地址
SOCKET servSock;
struct sockaddr_in servAddr;
char * servPort = "9190";

//创建服务器套接字
servSock = socket(PF_INET, SOCK_STREAM, 0);	

/*地址信息初始化*/
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
servAddr.sin_port = htons(atoi(servPort));

//分配地址信息
bind(servSock, (struct sockaddr*)&servAddr, sizeof(servAddr));
...
WSAStringToAddress& WSAAddressToString

这两个转换函数功能与inet_ntoainet_addr完全相同,但有点在于支持多种协议,在IPv4和IPv6中都可以用。但是,缺点是这俩函数依赖特定平台,只能在Windows上使用,不能在Linux上用,这样会降低兼容性。

3.6 习题

参考

(1)IP地址族IPv4和IPv6有何区别?在何种背景下诞生了IPv6?

IPV4是4字节地址族,IPV6是16字节地址族。IPV6的诞生是为了应对2010年前后IP地址耗尽的问题而提出的标准。

(2)通过IPV4网络ID、主机ID及路由器的关系说明向公司局域网中的计算机传输数据的过程。

首先数据传输的第一个环节是向目标IP所属的网络传输数据。此时使用的是IP地址中的网络ID。传输的数据将被传到管理网络的路由器,接受数据的路由器将参照IP地址的主机号找自己保存的路由表,找到对应的主机发送数据。

(3)套接字地址分为IP地址和端口号。为什么需要IP地址和端口号?或者说,通过IP可以区分哪些对象?通过端口号可以区分哪些对象?

IP地址是为了区分网络上的主机。端口号是区分同一主机下的不同的SOCKET,以确保软件准确收发数据。

(4)说明IP地址的分类方法,据此说出下面这些IP地址的分类

  • A类地址首字节范围:0-127;网络ID占1字节,主机ID占3字节
  • B类地址首字节范围:128-191;网络ID2字节,主机ID2字节
  • C类地址首字节范围:192-223;网络ID3字节,主机ID1字节

下面是题目给的地址:

  • 214.121.212.102(C)
  • 120.101.122.89(A)
  • 129.78.102.211(B)

(5)计算机通过路由器或交换机连接到互联网。请说出路由器和交换机的作用。

路由器是帮助数据传输到目的地中介。不仅如此,还起到帮助连接本地网络的电脑和互联网的作用。 不太理解

(6)什么是知名端口?其范围是多少?知名端口中具有代表性的HTTP合同FTP端口号各是多少?

“知名端口(Well-known PROT)”是指预定分配给特定操作的端口。其范围是0~1023,其中最知名的端口是HTTP:80端口和TCP:21

(7)题目大概意思是:为什么bind中第二个参数是sockaddr,但是传入的是sockaddr_in。

bind函数第二个参数类型是sockaddr结构体,很难分配IP地址和端口号,它的IP地址和端口号的分配是通过sockaddr_in完成的。因为该结构体和sockaddr结构体的组成字节序和大小完全相同,所以可以强转。

(8)请解释大端序、小端序、网络字节序,并说明为何需要网络字节序。

小端序是把高位字节存储到高位地址上;大端序是把高位字节存储到低位地址上。因为保存栈的方式有差异,所以对网络传输数据的过程制定了标准,这就是“网路字节序”。而且,在网络字节序中,数据传输的标准是“大端序”。

(9)大端计算机希望将4字节整型数据12传到小端序计算机。请说出数据传输过程中发生的字节序变换过程。

因为网络字节序的顺序标准是“大端序”,所以大端序的计算机在网络传输中不需要先转换字节顺序,直接传输。但是接受数据的是小端序计算机,因此,要经过网络字节序转本地序的过程,再保存到存储设备上。

(10)怎么表示回送地址?其含义是什么?如果向回送地址传输数据将会发生什么情况?

回送地址表示计算机本身,为127.0.0.1。因此,如果将数据传送到IP地址127.0.0.1,数据不会传输到网络的其他设备上,而是直接返回。

为了在Windows安装ADB工具,你可以按照以下步骤进行操作: 1. 首先,下载ADB工具包并解压缩到你自定义的安装目录。你可以选择将其解压缩到任何你喜欢的位置。 2. 打开运行窗口,可以通过按下Win+R键来快速打开。在运行窗口中输入"sysdm.cpl"并按下回车键。 3. 在系统属性窗口中,选择"高级"选项卡,然后点击"环境变量"按钮。 4. 在环境变量窗口中,选择"系统变量"部分,并找到名为"Path"的变量。点击"编辑"按钮。 5. 在编辑环境变量窗口中,点击"新建"按钮,并将ADB工具的安装路径添加到新建的路径中。确保路径正确无误后,点击"确定"按钮。 6. 返回到桌面,打开命令提示符窗口。你可以通过按下Win+R键,然后输入"cmd"并按下回车键来快速打开命令提示符窗口。 7. 在命令提示符窗口中,输入"adb version"命令来验证ADB工具是否成功安装。如果显示版本信息,则表示安装成功。 这样,你就成功在Windows安装ADB工具。你可以使用ADB工具来执行各种操作,如枚举设备、进入/退出ADB终端、文件传输、运行命令、查看系统日志等。具体的操作方法可以参考ADB工具的官方文档或其他相关教程。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* [windows环境安装adb驱动](https://blog.youkuaiyun.com/zx54633089/article/details/128533343)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [Windows安装使用ADB简单易懂教程](https://blog.youkuaiyun.com/m0_37777700/article/details/129836351)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值