一.套接字类型
1.面向连接的套接字(SOCK_STREAM)
特点:
- 传输过程中数据不会消失
- 按顺序传输数据
- 传输的数据不存在数据边界
2.面向消息的套接字(SOC_DGRAM)
特点:
- 强调快速传输而非传输顺序
- 传输的数据可能丢失也可能损毁
- 传输的数据有数据边界
- 限制每次传输的数据大小
二.地址信息的表示
1.结构体 sockaddr_in 的成员分析
struct sockaddr_in {
sa_family_t sin_family; // 地址族(一般为AF_INET)
in_port_t sin_port; // 端口号
struct in_addr sin_addr; // IPv4地址
char sin_zero[8]; // 用于对齐的填充字段
};
- sin_family :每种协议族适用的地址族均不同,用来存储协议地址族,区分协议
- sin_port:保存16位端口号,并且是以网络字节序保存
- sin_addr:保存32位IP地址信息,并且也以网络字节序保存
- sin_zero:无特殊含义。
三.网络字节序和地址变换
1.字节序和网络字节序
00000000 00000000 00000000 00000001 //小端序格式
00000001 00000000 00000000 00000000 //大端序格式
cpu向内存保存数据的方式有2种,这意味着cpu解析数据的方式也有两种。
在网络传输数据时约定统一方式,也就是网络字节序,统一为大端序。(即先把数据数组转换为大端序格式再进行网络传输)。
2.字节序转换
在网络编程中,字节序转换是指将数据在不同字节序(大端字节序和小端字节序)之间进行转换的过程。在进行网络通信时,为了确保数据在发送和接收的过程中能够正确解析,需要进行字节序的转换。
常用的字节序转换函数有以下两个:
htons和ntohs:用于16位无符号整数的字节序转换。
- htons(host to network short)用于将主机字节序转换为网络字节序。
- ntohs(network to host short)用于将网络字节序转换为主机字节序。
htonl和ntohl:用于32位无符号整数的字节序转换。
htonl(host to network long)用于将主机字节序转换为网络字节序。
ntohl(network to host long)用于将网络字节序转换为主机字节序。
这些函数可以帮助我们在不同字节序之间进行转换,确保数据在网络传输过程中的正确性和可靠性。
例如,如果要将一个16位整数从主机字节序转换为网络字节序,可以使用htons函数进行转换:
uint16_t hostValue = 0x1234;
uint16_t networkValue = htons(hostValue);
//类似地,如果要将一个32位整数从主机字节序转换为网络字节序,可以使用htonl函数进行转换:
uint32_t hostValue = 0x12345678;
uint32_t networkValue = htonl(hostValue);
//在接收数据时,可以使用相应的ntohs和ntohl函数将网络字节序转换为主机字节序。
3.网络地址的初始化与分配
(1)
inet_addr
函数
用于将IPv4地址的点分十进制表示形式转换为32位无符号整数。它位于<arpa/inet.h>
头文件中,并且返回类型是in_addr_t
。在使用inet_addr
函数时需要检查返回值是否等于INADDR_NONE
,以判断是否转换成功。如果返回值等于INADDR_NONE(-1)
,则表示转换失败,可能是因为输入的IP地址格式不正确。
#include <iostream>
#include <arpa/inet.h>
int main() {
const char* ipStr = "192.168.0.1";
// 将点分十进制形式的IPv4地址转换成32位无符号整数
in_addr_t ipAddr = inet_addr(ipStr);
if (ipAddr == INADDR_NONE) {
std::cout << "Invalid IP address" << std::endl;
} else {
std::cout << "IP address in network byte order: " << ipAddr << std::endl;
}
return 0;
}
in_addr_t inet_addr(const char *cp);
inet_addr函数转换网络主机地址(如192.168.1.10)为网络字节序二进制值,如果参数char *cp无效,函数返回-1(INADDR_NONE),这个函数在处理地址为255.255.255.255时也返回-1,255.255.255.255是一个有效的地址,不过inet_addr无法处理;
(2)inet_pton
函数
一个用于将IPv4和IPv6地址字符串转换为网络字节序的二进制形式的函数。若成功则返回值为1,否则返回值为0.
#include <iostream>
#include <arpa/inet.h>
int main() {
const char* ipv4Address = "192.168.0.1";
struct in_addr ipv4Binary;
if (inet_pton(AF_INET, ipv4Address, &ipv4Binary) > 0) {
std::cout << "IPv4 address in binary form: 0x" << std::hex << ntohl(ipv4Binary.s_addr) << std::endl;
} else {
std::cout << "Invalid IPv4 address" << std::endl;
}
const char* ipv6Address = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
struct in6_addr ipv6Binary;
if (inet_pton(AF_INET6, ipv6Address, &ipv6Binary) > 0) {
std::cout << "IPv6 address in binary form: ";
for (int i = 0; i < 16; ++i) {
std::cout << std::hex << static_cast<int>(ipv6Binary.s6_addr[i]);
}
std::cout << std::endl;
} else {
std::cout << "Invalid IPv6 address" << std::endl;
}
return 0;
}
(3)inet_ntop 函数
一个用于将二进制形式的IPv4或IPv6地址转换回点分十进制表示的函数,该函数不涉及字节序的转换。返回值:若成功则为指向结构的指针,若出错则为NULL。
#include <iostream>
#include <arpa/inet.h>
int main() {
// IPv4地址转换
struct in_addr ipv4Addr;
inet_pton(AF_INET, "192.168.0.1", &(ipv4Addr.s_addr));
char ipv4Str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(ipv4Addr.s_addr), ipv4Str, INET_ADDRSTRLEN);
std::cout << "IPv4 address: " << ipv4Str << std::endl;
// IPv6地址转换
struct in6_addr ipv6Addr;
inet_pton(AF_INET6, "2001:0db8:85a3:0000:0000:8a2e:0370:7334", &(ipv6Addr.s6_addr));
char ipv6Str[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &(ipv6Addr.s6_addr), ipv6Str, INET6_ADDRSTRLEN);
std::cout << "IPv6 address: " << ipv6Str << std::endl;
return 0;
}
(4)网络地址初始化
-
客户端(服务器初始化只需要将IP地址参数设置为 INADDR_ANY,自动获取服务端的IP地址)
#include <iostream>
#include <arpa/inet.h> // 包含网络地址相关的头文件
int main() {
// 初始化网络地址结构
struct sockaddr_in serverAddress;
serverAddress.sin_family = AF_INET; // 设置地址族为IPv4
serverAddress.sin_port = htons(8080); // 设置端口号(使用htons函数进行字节序转换)
serverAddress.sin_addr.s_addr = inet_addr("192.168.0.1"); // 设置IP地址(使用inet_addr函数将字符串IP转换为二进制形式)
// 打印初始化的网络地址信息
std::cout << "IP地址: " << inet_ntoa(serverAddress.sin_addr) << std::endl; // 使用inet_ntoa函数将二进制IP转换为字符串形式
std::cout << "端口号: " << ntohs(serverAddress.sin_port) << std::endl; // 使用ntohs函数将端口号从网络字节序转换为主机字节序
return 0;
}
-
向套接字分配网络地址
bind()
函数是在网络编程中常用的函数之一,它用于将一个套接字(socket)与特定的IP地址和端口号绑定起来。以下是关于bind()
函数的一些详细说明:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数解释:
sockfd:表示要绑定的套接字描述符(socket file descriptor)。
addr:指向sockaddr结构体的指针,其中包含了要绑定的IP地址和端口号信息。
addrlen:表示addr结构体的长度。
返回值:
若绑定成功,则返回0。
若出现错误,则返回-1,并设置对应的错误码(可以使用errno全局变量查看具体错误信息)。
简单示例:
#include <iostream>
#include <arpa/inet.h>
#include <sys/socket.h>
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字
if (sockfd == -1) {
std::cerr << "Failed to create socket." << std::endl;
return 1;
}
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET; // 使用IPv4地址族
serverAddr.sin_port = htons(8080); // 绑定端口号为8080
serverAddr.sin_addr.s_addr = INADDR_ANY; // 绑定所有可用的IP地址
if (bind(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
std::cerr << "Failed to bind socket." << std::endl;
return 1;
}
std::cout << "Socket bound successfully." << std::endl;
return 0;
}
四.概念总结
1.IP地址族IPV4和IPV6有何区别,在何种背景下诞生了IPV6?
-
地址长度:IPv4使用32位地址,而IPv6使用128位地址。这意味着IPv6提供了更大的地址空间,可以分配更多的唯一地址。
-
地址表示方法:IPv4使用点分十进制表示法,例如192.168.0.1;而IPv6使用冒号分隔的十六进制表示法,例如2001:0db8:85a3:0000:0000:8a2e:0370:7334。IPv6还引入了简写规则,允许将连续的零段缩写为“::”。
-
可用地址数量:由于IPv4地址长度有限,只有约42亿个可用地址,导致IPv4地址耗尽问题。而IPv6的地址空间极其庞大,可以提供约340十万亿亿亿亿(3.4×10^38)个唯一的地址,能够满足未来互联网的需求。
IPv6的诞生主要是为了解决IPv4地址耗尽问题。随着全球互联网的普及和设备的增加,需要更多的唯一IP地址来连接各种设备,例如智能手机、平板电脑、物联网设备等。IPv6的推出为互联网提供了更大的地址空间,以应对当前和未来的互联网发展需求。
2.通过IPV4网络ID,主机ID和路由器的关系说明向公司局域网中的计算机传输数据的过程
在IPv4网络中,每个主机都有一个唯一的IP地址。这个IP地址由两部分组成:网络ID和主机ID。
-
网络ID(Network ID):网络ID标识了所属的网络。它是在全球范围内由互联网号码分配局(IANA)和互联网注册局(IR)分配的,用于标识不同的网络段。
-
主机ID(Host ID):主机ID标识了网络中的具体主机或设备。它是由网络管理员在特定网络范围内自行分配的。
- 当计算机通过IPv4网络传输数据时,以下是数据传输的一般过程:
-
发送端计算机首先将数据分割成数据包。每个数据包除了携带实际数据外,还包含源IP地址和目标IP地址等必要信息。
-
发送端计算机根据目标IP地址判断数据包应该发送到哪个网络。它会检查自己的子网掩码来确定目标IP地址与本地网络ID的关系。
-
如果目标IP地址与发送端计算机所在网络ID相同,那么目标主机就在同一局域网上。发送端计算机直接将数据包发送给目标主机。
-
如果目标IP地址与发送端计算机所在网络ID不同,那么目标主机就位于不同的网络上。发送端计算机需要将数据包发送给默认网关或路由器。
-
默认网关或路由器是连接不同网络之间的桥梁。发送端计算机将数据包发送给默认网关,然后由默认网关根据目标IP地址和路由表中的信息,将数据包转发到正确的目标网络上。
-
当数据包到达目标网络后,目标主机根据自己的IP地址判断是否为自己的数据包,并进行相应的处理。
这样,通过IPv4网络ID、主机ID和路由器的关系,可以实现向公司局域网中的计算机传输数据。数据经过源主机、路由器和目标主机之间的交互,最终到达目标主机上。
3.套接字地址分为IP地址和端口号。为什么需要IP地址和端口号?或者说,通过IP可以区分哪些对象?通过端口号可以区分哪些对象?
套接字地址是用于在网络通信中标识和定位网络中的应用程序或服务的。它由IP地址和端口号两部分组成。
-
IP地址:IP地址是用于在网络中唯一标识设备(如计算机、路由器等)的地址。通过IP地址,可以区分不同的主机或网络设备。IP地址是网络层的概念,用于在全球范围内寻址和路由数据包。
-
端口号:端口号是用于在主机内部标识具体的应用程序或服务的标识符。每个主机都有65535个不同的端口号(从0到65535),其中一些被约定为特定的服务端口(例如,HTTP使用端口80)。端口号是传输层的概念,用于在唯一的IP地址上将数据包交付给正确的应用程序。
为什么需要IP地址和端口号呢?
-
IP地址:通过IP地址,可以唯一地标识网络中的各个主机或设备。它允许网络中的数据包正确路由到目标设备,确保数据能够准确地发送和接收。
-
端口号:在一个主机上可能同时运行多个应用程序或服务,端口号允许操作系统将传入的数据包正确地传递给目标应用程序。通过端口号,可以区分同一IP地址上的不同应用程序或服务。
通过IP地址可以区分不同的主机或网络设备,而通过端口号可以区分同一主机上的不同应用程序或服务。结合IP地址和端口号,可以实现在网络中准确地定位并交付数据到目标设备的特定应用程序或服务。
4.说明IP地址的分类方法
IP地址的分类方法是一种早期的划分方式,用于将IPv4地址空间按照网络规模和需求进行分组。根据IP地址的前缀位数不同,IPv4地址可分为以下五类:A类、B类、C类、D类和E类。
-
A类地址:
- 范围:1.0.0.0 到 126.0.0.0
- 特点:A类地址以第一个字节的最高位为0开头,后面三个字节为主机部分。这意味着A类地址有较大的网络号部分和相对较小的主机号部分。
- 用途:A类地址常用于大型网络,可以容纳较多的主机。
-
B类地址:
- 范围:128.0.0.0 到 191.255.0.0
- 特点:B类地址以前两个字节的最高两位为10开头,后面两个字节为主机部分。这意味着B类地址有中等大小的网络号部分和主机号部分。
- 用途:B类地址通常用于中等规模的网络,能够支持较多的主机。
-
C类地址:
- 范围:192.0.0.0 到 223.255.255.0
- 特点:C类地址以前三个字节的最高三位为110开头,最后一个字节为主机部分。这意味着C类地址有较小的网络号部分和较大的主机号部分。
- 用途:C类地址常用于小型局域网,能够提供较多的网络。
-
D类地址:
- 范围:224.0.0.0 到 239.255.255.255
- 特点:D类地址以前四个字节的最高四位为1110开头,用于多播通信。
- 用途:D类地址用于将数据包发送到多个目标设备。
-
E类地址:
- 范围:240.0.0.0 到 255.255.255.255
- 特点:E类地址以前五个字节的最高五位为11110开头,保留作为实验和研究使用,不用于一般的通信。
需要注意的是,IP地址分类的方法在现代互联网中已经不再广泛使用,取而代之的是更加灵活的无类别域间路由选择(CIDR)方法。CIDR允许更加精确地划分和分配IP地址,以满足更灵活的网络需求。
5.请解释大端序、小端序、网络字节序,并说明为何需要网络字节序?
大端序(Big-endian)和小端序(Little-endian)是两种不同的字节序排列方式,用于表示多字节数据在内存中的存储顺序。
-
大端序:高位字节存储在低地址,低位字节存储在高地址。也就是说,在一个多字节整数中,最高有效字节(MSB)位于起始地址处,而最低有效字节(LSB)位于结束地址处。
-
小端序:低位字节存储在低地址,高位字节存储在高地址。也就是说,在一个多字节整数中,最低有效字节(LSB)位于起始地址处,而最高有效字节(MSB)位于结束地址处。
网络字节序(Network Byte Order)是一种规范化的字节序,用于在网络上传输数据。网络字节序采用的是大端序方式。无论主机使用大端序还是小端序进行数据存储,发送到网络上的数据都必须按照网络字节序进行封装,接收方则需要将接收到的网络字节序数据转换为本地字节序才能正确解析。
为什么需要网络字节序?
-
跨平台通信:不同计算机和操作系统可能采用不同的字节序方式。通过统一采用网络字节序,可以保证不同平台之间的通信能够正确解析数据。
-
数据传输可靠性:网络通信中,发送方和接收方可能位于不同的地点,并通过多个路由器转发数据。如果在传输过程中使用不同的字节序方式,可能导致接收方解析出错,产生数据混乱或错误的结果。通过使用网络字节序,可以确保数据在传输过程中保持一致性。
-
协议标准化:为了使不同设备之间能够共享和理解相同的数据格式,协议通常会明确规定使用网络字节序进行数据交换。例如,在TCP/IP协议栈中,各层协议都采用网络字节序。
总结起来,网络字节序作为一种统一的字节序方式,保证了跨平台通信和数据传输的可靠性,同时也为协议标准化提供了一致性。