TCP/IP 网络编程---地址族与数据序列

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

(一) IP 地址

定义

IP地址是分配给网络上每个设备的唯一标识符,用于在网络中定位和通信。

类型

  • IPv4 (Internet Protocol version 4):4字节地址族
  • IPv6 (Internet Protocol version 6):16字节地址族

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

网络地址是为了区分网络而设置的一部分 IP 地址。

假如要向 M 网络中的某个主机传输数据,并非一开始就浏览所有 IP 地址,进而找到目标主机;而是首先浏览网络地址,找到 M 网络,先把数据传输到 M 网络,M 网络接收到数据后,浏览传数据的主机地址并将数据传给目标计算机

主机地址边界(IPv4)

  • A类地址的首字节范围:00000000(0)到01111111(127)
  • B类地址的首字节范围:10000000(128)到10111111(191)
  • C类地址的首字节范围:11000000(192)到11011111(223)

(二)用于区分套接字的端口号

IP 用于区分计算机,只要有 IP 地址就能向目标主机传输数据,但仅凭这些无法传输给最终的应用程序,即仅凭 IP 地址无法区分传输到计算机的网络数据发给哪个应用程序

计算机中一般配有 NIC (Network Interface Card,网络接口卡)数据传输设备。通过 NIC 向计算机内部传输数据时会用到 IP。操作系统负责把传递到内部的数据适当分配给套接字,此时就需要利用端口号(port)。通过 NIC 接收的数据内有端口号,操作系统正是参考此端口把数据传输给相应端口的套接字:

地址信息的表示

应用程序中使用的 IP 地址和端口号以结构体的形式给出了定义。

(一)表示 IPv4 地址的结构体

c 复制代码
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];	        // 不使用
};

struct in_addr
{
	In_addr_t		s_addr;			// 32位 IPv4 地址
};

(二)结构体成员分析

  • sin_family:

    • 类型sa_family_t
    • 说明 :表示地址族。对于 IPv4 地址,通常设置为 AF_INET。这告诉系统该结构体包含的是 IPv4 地址信息。
  • sin_port:

    • 类型uint16_t
    • 说明 :表示16位的端口号。端口号用于区分同一台计算机上的不同服务。端口号的值是网络字节序(大端序),因此在赋值时需要使用 htons() 函数来转换主机字节序到网络字节序。
  • sin_addr:

    • 类型struct in_addr
    • 说明 :表示32位的 IPv4 地址。struct in_addr 中的字段是 s_addr,它存储了 IP 地址的实际值,通常也是网络字节序。
  • sin_zero:

    • 类型char[8]
    • 说明 :这是一个填充字段,大小为8字节。它用于确保 struct sockaddr_instruct sockaddr 结构体对齐,保持结构体的大小一致。这个字段在实际使用中不需要设置或使用。
c 复制代码
struct sockaddr_in serv_addr;

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

sockaddr_in 结构体变量的地址值将以如上方式传递给 bind 函数,可以看到第二个参数期望得到 sockaddr 结构体变量地址值,包括地址族、端口号、IP 地址等。sockaddr 结构体如下:

c 复制代码
struct sockaddr
{
    sa_family_t sin_family;    //地址族
    char sa_data[14];          // 地址信息
};

此结构体成员 sa_data 保存的地址信息中需要包含 IP 地址和端口号,剩余部分应填充0。这对于包含地址信息来讲非常麻烦,所以有了新的结构体 sockaddr_in

网络字节序与地址变换

(一)字节序

不同 CPU 中,4字节整数型值1在内存空间中保存的方式是不同的。4字节整数型值1可用二进制表示如下:

00000000 00000000 00000000 00000001

有些 CPU 以这种顺序保存到内存,另外一些 CPU 则以倒序保存:

00000001 00000000 00000000 00000000

CPU 向内存保存数据的方式有两种,所以 CPU 解析数据的方式也分为两种:

  • 大端序(Big Endian):高位字节存放到低位地址。

在大端序中,整数 0x12345678 的高位字节(0x12)存储在最低地址,低位字节(0x78)存储在最高地址。

地址
0x00 0x12
0x01 0x34
0x02 0x56
0x03 0x78
  • 小端序(Little Endian):高位字节存放到高位地址。

在小端序中,整数 0x12345678 的高位字节(0x12)存储在最高地址,低位字节(0x78)存储在最低地址。

地址
0x00 0x78
0x01 0x56
0x02 0x34
0x03 0x12

(二)网络字节序

由于大端序和小端序的存在,字节序不同的计算机之间传输数据过程中可能会出现问题,所以,在通过网络传输数据时约定统一方式,这种约定称为网络字节序(Network Byte Order)------------统一为大端序

(三)字节序转换

字节序转换函数:

c 复制代码
unsigned short htons(unsigned short);

unsigned short ntohs(unsigned short);

unsigned long htonl(unsigned long);

unsigned long ntohl(unsigned long);
  • htons 中的 h 代表主机(host)字节序。
  • htons 中的 n 代表网络(network)字节序。
  • s 指的是 short
  • l 指的是 long

所以 htons 解释为 "把 short 型数据从主机字节序转化为网络字节序"。

网络地址的初始化与分配

(一)将字符串信息转换为网络字节序的整数型

对于 IP 地址的表示,较为熟悉的是点分十进制表示法(Dotted Decimal Notation)例如 201.211.214.36,而非整数型数据表示法。幸运的是,有函数会帮助我们将字符串形式的 IP 地址转换成32位整数型数据,这些函数在转换类型的同时进行网络字节序转换。

(1) inet_addr 函数

c 复制代码
#include <arpa/inet.h>
// 成功时返回32位大端序整数型值,失败时返回 INAADDR_NONE
in_addr_t inet_addr(const char* string);

Linux 平台调用过程

c 复制代码
#include <stdio.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
	char *addr1="127.212.124.78";

	// 错误的 IP 地址,验证函数的错误检测能力
	char *addr2="127.212.124.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 occureded \n");
	else
		printf("Network ordered integer addr: %#lx \n\n", conv_addr);
	return 0;
}

(2) inet_aton 函数

c 复制代码
#include <arpa/inet.h> 
// 成功时返回1(true),失败时返回0(false) 
int inet_aton(const char* string, struct in_addr* addr);
  • string :待转换的字符串地址值。
  • addr :转换结果的结构体变量的地址值。

Linux 平台调用过程

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
void error_handling(char *message);

int main(int argc, char *argv[])
{
	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);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

有一个与 inet_aton 函数正好相反的函数 inet_ntoa这个函数可以把网络字节序整数型 IP 地址转化为我们熟悉的字符串形式

c 复制代码
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
	struct sockaddr_in addr1, addr2;
	char *str_ptr;
	char str_arr[20];
   
	addr1.sin_addr.s_addr=htonl(0x1020304);
	addr2.sin_addr.s_addr=htonl(0x1010101);
	
	str_ptr=inet_ntoa(addr1.sin_addr);
	strcpy(str_arr, str_ptr);
	printf("Dotted-Decimal notation1: %s \n", str_ptr);
	
	inet_ntoa(addr2.sin_addr);
	printf("Dotted-Decimal notation2: %s \n", str_ptr);
	printf("Dotted-Decimal notation3: %s \n", str_arr);
	return 0;
}

/*
root@com:/home/swyoon/tcpip# gcc inet_ntoa.c -o ntoa
root@com:/home/swyoon/tcpip# ./ntoa
Dotted-Decimal notation1: 1.2.3.4 
Dotted-Decimal notation2: 1.1.1.1 
Dotted-Decimal notation3: 1.2.3.4 
*/

(3)inet_addrinet_ntoa 函数在 Windows 平台的调用过程

c 复制代码
#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);
}

(二)网络地址初始化

c 复制代码
struct sockaddr_in addr;
char* serv_ip = "211.217.168.13";    // 声明IP地址字符串
char* serv_port= "9190";             // 声明端口号字符串
memset(&addr, 0, sizeof(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 地址会有些繁琐,可如下初始化地址信息:

c 复制代码
struct sockaddr_in addr;
char* serv_port= "9190";             
memset(&addr, 0, sizeof(addr));     
addr.sin_family = AF_INET;           
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(atoi(serv_port));       

INADDR_ANY 可自动获取服务器端的计算机 IP 地址。当你使用 INADDR_ANY 绑定套接字时,套接字将被绑定到计算机的所有网络接口。这意味着服务器会接收来自任何网络接口的流量。

问答

① 通过 IP 可以区分哪些对象?通过端口号可以区分哪些对象?

  • IP 地址:用于唯一标识网络中的设备或网络接口,并可以区分不同的设备、子网和 ISP。

  • 端口号:用于在单一设备上区分不同的应用程序、服务实例和协议类型。它确保数据包能够正确地传递到对应的服务。

② 什么是知名端口?其范围是多少?知名端口中具有代表性的 HTTP 和 FTP 端口号各是多少?

  • 知名端口:是为特定协议和服务预定义的端口号,用于确保网络服务的标准化和一致性。

  • 范围:0 到 1023。

  • HTTP 端口号:80。

  • FTP 端口号:21。

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

  • 回送地址(Loopback Address) 是网络协议中用于测试和调试的一个特殊地址。它代表了本机自身,用于验证网络协议栈和网络接口的工作状况。

  • IPv4 回送地址

    • 表示127.0.0.1,以及 127.0.0.0/8 地址段中的所有地址。
    • 含义127.0.0.1 是回送地址的最常用形式。IPv4 的回送地址范围是 127.0.0.0127.255.255.255,其中 127.0.0.1 是最常见的地址。
  • IPv6 回送地址

    • 表示::1
    • 含义::1 是 IPv6 中的回送地址。IPv6 的回送地址是 ::1,并且整个 ::/128 地址段中的所有地址都可以用作回送。
  • 测试和调试:回送地址用于测试本地网络协议栈和应用程序,而无需实际的网络连接。通过向回送地址发送数据,可以检查网络协议栈是否正常工作,而不涉及外部网络。

  • 本地通信:它允许网络服务和应用程序在同一台计算机上相互通信,而不需要通过物理网络接口。

  • 向回送地址传输数据的结果

    • 当数据包发送到回送地址时,数据包不会被发送到外部网络。相反,数据包会被直接处理并返回给发送者。
    • 这意味着发送到回送地址的数据不会离开计算机,所有的数据包都在本地计算机上进行处理和响应。
相关推荐
奈葵32 分钟前
C语言字符函数和字符串函数
c语言·开发语言
OKkankan39 分钟前
单链表算法题(数据结构)
c语言·数据结构·数据库·c++·算法
芒 种1 小时前
深入理解指针
c语言
羊小猪~~1 小时前
C/C++语言基础--initializer_list表达式、tuple元组、pair对组简介
c语言·开发语言·c++·vscode·list·c++20·visual studio
t5y222 小时前
【C语言】Union
c语言·开发语言·算法
蝌蚪HTTP3 小时前
干货分享之Python爬虫与代理
爬虫·python·网络协议·tcp/ip·ip
TeYiToKu4 小时前
笔记整理—linux驱动开发部分(12)I2C总线与触摸屏设备
linux·c语言·arm开发·驱动开发·笔记·嵌入式硬件
hgdlip4 小时前
两部手机的IP地址:是否会相同?全面探讨
网络协议·tcp/ip·智能手机
昇腾CANN4 小时前
Ascend C算子性能优化实用技巧05——API使用优化
c语言·开发语言·性能优化
沐泽Mu4 小时前
嵌入式学习-C嘎嘎-Day03
c语言·开发语言·c++·学习