[TCP/IP网络编程] 地址族与数据序列
分配给套接字的IP地址与端口号
IP是网络协议的简写,IP协议规定了数据包的格式,地址怎么编码、怎么寻址、怎么路由转发。为了收发网络数据我们需要按照IP协议来给计算机分配一个值(IP地址)。端口号是为了区分程序中创建的套接字而分配给套接字的编号(从操作系统的视角来看,端口号是分配给进程的,端口最终被某个进程占用,进程通过套接字持有)。
IP地址
IP地址分为两类:
- IPv4 : 4字节地址族
- IPv6: 16字节地址族
IPv4标准的4字节IP地址分为网络地址和主机地址。分为A、B、C、D、E等类型。当我们向一个IP地址传数据时,首先向相应的网络地址传输数据(构成网络的路由器或交换机),之后由路由器或交换机根据主机地址向目标主机传数据。
地址信息的表示
应用程序中使用的IP地址和端口号以结构体的形式给出定义。
表示IPv4地址的结构体
此结构体将作为地址信息传递给bind函数
1
2
3
4
5
6
7
8
9
10
struct sockaddr_in {
sa_family_t sin_family; //地址族
uint16_t sin_port; //16位端口号
struct in_addr sin_addr; //32位IP地址
char sin_zero[0] //结构体填充
}
struct in_addr {
In_addr_t s_addr; //32位IP地址
}
bind函数的使用:
1
2
3
4
struct sockaddr_in serv_addr;
if(bind(serv_sock, (strcut sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
error_handling("bind() error");
这里的第二个参数期望得到sockaddr结构体变量的地址值,包括地址族、端口号、IP地址等。
1
2
3
4
struct sockaddr {
sa_family_t sin_family; // 地址族
char sa_data[14]; // 地址信息
}
其中sa_data保存的地址信息中需要包含IP地址和端口号,剩余应该填充0,但是直接填写非常麻烦,所以我们需要一个新的结构体sockaddr_in,我们填写sockaddr_in后将其转换位sockaddr型的结构体变量,再传递给bind函数使用。
网络字节序与地址变换
不同CPU中数据在内存中的保存方式是不同的,分为大端序和小端序两种。因此,在通过网络传输数据时需要按照约定统一方式,这种约定称为网络字节序,统一为大端序。
也就说,我们要把数据数组转换成大端序格式后再进行网络传输。因此所有的计算机接收到的数据应该识别该数据是网络字节序格式,小端序系统传输数据时应该转换为大端序排列方式。
字节序转换
1
2
3
4
unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);
其中h代表主机(host)字节序,n代表网络字节序,s和l分别表示数据类型
网络地址的初始化与分配
将字符串信息转换为网络字节序的整数型
sockaddr_in中保存的地址信息为32位整型,因此,为了分配IP地址,需要将其转换位32位整数型数据。
1
2
3
4
5
6
7
#include <arpa/inet.h>
in_addr_t inet_addr(const char* string); //成功返回32位大端序整数型,失败返回INADDR_NONE
int inet_aton(const chr* string, strcut in_addr* addr); //成功返回1,失败返回-1
char* inet_ntoa(strcut in_addr adr); //整数型IP转为字符串,成功返回字符串地址值,失败返回-1
网络地址初始化
1
2
3
4
5
6
7
8
strcut 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_addr.s_addr = htonl(INADDR_ANY); //自动获取服务器端的IP地址
addr.sin_port = htons(atoi(serv_port)); //基于字符串端口号初始化
向套接字分配网络地址
1
2
3
4
5
6
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr* myaddr, socklen_t addrlen);
// sockfd 套接字文件描述符
// myaddr 存有地址信息的结构体变量地址值
// addrlen 第二个结构体变量的长度
下面展示了服务器端常见的套接字初始化过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 = htol(INADDR_ANY);
serv_addr.sin_port = htos(atoi(serv_port));
//分配地址信息
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));