位置:首页 > C > TCP/IP网络编程:IP地址、端口号与sockaddr_in结构体详解

TCP/IP网络编程:IP地址、端口号与sockaddr_in结构体详解

时间:2026-08-27  |  作者:骑光打字机  |  阅读:0

目录

  1. 分配给套接字的 IP 地址与端口号
  2. (一) IP 地址
  3. 定义
  4. 类型
  5. 主机地址边界(IPv4)
  6. (二)用于区分套接字的端口号
(一) IP 地址 对应的技术说明图
(一) IP 地址概括(一) IP 地址的核心概念、关键要点与实践提示。
分配给套接字的 IP 地址与端口号 对应的技术说明图
分配给套接字的 IP 地址与端口号概括分配给套接字的 IP 地址与端口号的核心概念、关键要点与实践提示。

前言

在TCP/IP网络编程中,套接字通信依赖于准确的地址分配。IP地址作为网络设备的唯一标识,负责定位主机,而端口号则用于区分同一计算机内的不同应用程序。本文深入解析IPv4与IPv6的地址类型及边界,阐述操作系统如何通过端口号将数据分发至对应套接字。同时,详细剖析表示IPv4地址的sockaddr_in结构体,包括sin_family、sin_port等关键成员,帮助开发者理解底层数据封装机制,为构建稳定的网络应用奠定基础。

TCP/IP网络编程:IP地址、端口号与sockaddr_in结构体详解 的核心流程信息图
TCP/IP网络编程:IP地址、端口号与so用简体中文信息图概括TCP/IP网络编程:IP地址、端口号与so的核心流程、关键规则与实践要点。

分配给套接字的 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 地址的结构体

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_in 与 struct sockaddr 结构体对齐,保持结构体的大小一致。这个字段在实际使用中不需要设置或使用。
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 结构体如下:

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)存储在最高地址。

地址值
0x000x12
0x010x34
0x020x56
0x030x78
  • 小端序(Little Endian):高位字节存放到高位地址。

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

地址值
0x000x78
0x010x56
0x020x34
0x030x12

(二)网络字节序

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

(三)字节序转换

字节序转换函数:

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 型数据从主机字节序转化为网络字节序”。

网络地址的初始化与分配

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

在 TCP/IP 网络编程中,IP 地址的表示方式通常有两种:一种是大家较为熟悉的点分十进制表示法(Dotted Decimal Notation),例如 201.211.214.36;另一种则是整数型数据表示法。虽然人类更习惯阅读点分十进制格式,但在计算机底层通信时,使用的是整数型数据。幸运的是,系统提供了专门的函数来帮助我们将字符串形式的 IP 地址转换成32位整数型数据。这些函数在执行类型转换的同时,还会自动进行网络字节序的转换,从而简化了开发者的工作。

(1) inet_addr 函数

#include 
// 成功时返回32位大端序整数型值,失败时返回 INAADDR_NONE
in_addr_t inet_addr(const char* string);
Linux 平台调用过程
#include 
#include 

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 nn", conv_addr);
	return 0;
}

(2) inet_aton 函数

#include  
// 成功时返回1(true),失败时返回0(false) 
int inet_aton(const char* string, struct in_addr* addr);
  • string :待转换的字符串地址值。
  • addr :转换结果的结构体变量的地址值。
Linux 平台调用过程
#include 
#include 
#include 
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 地址转化为我们熟悉的字符串形式,使得程序能够将底层数据以可读的形式展示给用户或记录到日志中。这种双向转换机制确保了网络通信中数据表示的一致性和可读性。

#include 
#include 
#include 

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_addr 和 inet_ntoa 函数在 Windows 平台的调用过程

#include 
#include 
#include 
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);
}

(二)网络地址初始化

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 地址会有些繁琐,可如下初始化地址信息:

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。

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

回送地址通常表示为 127.0.0.1 或 localhost。其含义是指向当前主机本身,用于本机内部的网络通信测试。如果向回送地址传输数据,数据包不会离开主机,而是直接在操作系统内核中循环返回给发送进程。这种方式无需经过物理网卡和外部网络,因此速度极快且安全,常用于本地服务开发和调试,确保应用在没有外部网络依赖的情况下正常运行。

回送地址概述

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

IPv4 回送地址

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

IPv6 回送地址

  • 表示:::1
  • 含义:::1 是 IPv6 中的回送地址。IPv6 的回送地址是 ::1,并且整个 ::/128 地址段中的所有地址都可以用作回送。

回送地址的应用场景

测试和调试

回送地址用于测试本地网络协议栈和应用程序,而无需实际的网络连接。通过向回送地址发送数据,可以检查网络协议栈是否正常工作,而不涉及外部网络。

本地通信

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

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

  • 当数据包发送到回送地址时,数据包不会被发送到外部网络。相反,数据包会被直接处理并返回给发送者。
  • 这意味着发送到回送地址的数据不会离开计算机,所有的数据包都在本地计算机上进行处理和响应。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多