一.套接字类型
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; // 设置地址族为IPv4serverAddress.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); // 绑定端口号为8080serverAddr.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;
}