Linux 网络编程 —— 地址族与字节序
程序员文章站
2022-04-12 16:19:11
...
网络地址:用于唯一确定网络中的某一台计算机。包括IPv4(4个字节)和IPv6(16个字节)。
端口号:用于区分同一台主机上的不同套接字。
- 端口号由16位构成,可分配范围为0~65535, 其中0~1023为知名端口。
- 端口号不能重复,但TCP套接字和UDP套接字不会共用端口号,所以允许它们重复:即,如果某TCP套接字使用9190端口,则其他TCP套接字就无法使用该端口号,但UDP套接字可以使用。
地址结构
填写地址信息的线索:
- 使用哪一种地址族?如,IPv4
- IP地址是多少?如,211.204.214.76
- 端口号是多少?如,2048
sockaddr_in
地址结构(IPv4地址)
struct sockaddr_in {
sa_family_t sin_family; // 地址族
unit16_t sin_port; // 16位端口号
struct in_addr sin_addr; // 32位IP地址
char sin_zero[8]; // 不使用
};
struct in_addr {
in_addr_t s_addr; // 32位IPv4地址
};
-
地址族
AF_INET IPv4网络协议中使用的地址族 AF_INET6 IPv6网络协议中使用的地址族 AF_LOCAL 本地通信中采用UNIX协议的地址族 -
sin_port:以网络字节序保存16位端口号。
-
sin_addr:以网络字节序保存32位IP地址信息。
-
sin_zero:必需填充为0,无特殊含义。
sockaddr
地址结构
struct sockaddr {
sa_family_t sin_family; // 地址族
char sa_data[14]; // 地址信息
};
-
sa_data:保存的地址信息中需要包括IP地址和端口号,剩余部分应填充为0,这很难操作,继而有了
sockaddr_in
结构。 -
通常情况下,我们使用
sockaddr_in
类型的地址结构填充地址信息,然后再将其转为sockaddr
类型传给函数。如,struct sockaddr_in serv_addr; // 填充地址信息 ... bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); // ...
字节序
大端序:低位地址存放高位字节。(网络字节序)
小端序:低位地址存放低位字节。
字节序转换
#include <arpa/inet.h>
unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);
- 其中,
h
代表主机(host)字节序,n
代表网络(network)字节序。s
代表short
,l
代表long
。 - 例子:
unsigned short host_port = 0x1234; unsigned short net_port; unsigned long host_addr = 0x12345678; unsigned long net_adr; net_port = htons(host_port); net_addr = htonl(host_addr);
网络地址初始化
-
字符串地址转为网络字节序的整数型
将字符串形式的IP地址(点分十进制形式)转换成32位整数型数据,且在转换的同时进行网络字节序转换。#include <arpa/inet.h> in_addr_t inet_addr(const char *string); 成功时返回32位大端序整数型值,失败时返回INADDR_NONE。 int inet_aton(const char *string, struct in_addr *addr); 成功时返回1,失败时返回0.
例子:
char *addr = "1.2.3.4"; unsigned long conv_addr = inet_addr(addr); if(conv_addr == INADDR_NONE) { exit(1); } struct sockaddr_in addr_inet; if(!inet_aton(addr, &addr_inet.sin_addr)) { exit(1); }
-
网络字节序整数型IP地址转换为字符串形式
#include <arpa/inet.h> char* inet_ntoa(struct in_addr adr); 成功时返回转换的字符串地址,失败时返回-1.
调用完该函数后,应立即将字符串信息复制到其他内存空间,因为若再次调用该函数,则有可能覆盖之前保存的字符串信息。
例子:struct sockaddr_in addr; char *str_ptr; char str_arr[20]; addr.sin_addr.s_addr = htonl(0x1020304); str_ptr = inet_ntoa(addr.sin_addr); strcpy(str_arr, str_ptr); // 赶紧复制一份
-
网络地址初始化
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); // 服务器端IP地址常用方法,客户端则不这样 addr.sin_port = htons(atoi(serv_port));
-
memset(&addr, 0, sizeof(addr))
:将addr的所有字节均初始化为0,这么做是为了将sockaddr_in结构体中的成员sin_zero
初始化为0. - 利用常数
INADDR_ANY
分配服务器端IP地址,这样可自动获取运行服务器端的计算机IP地址,不必亲自输入;而且,若同一计算机中已分配多个IP地址,则只要端口号一致,就可以从不同IP地址接收数据。
-
向套接字分配网络地址
初始化完地址信息后,接下来就要把初始化的地址信息分配给套接字,bind()
函数负责这项操作。
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *addr, socklen_t addrlen);
成功时返回0,失败时返回-1.
-
sockfd
:要分配地址信息的套接字文件描述符。(要分配给哪一个套接字) -
addr
:存有地址信息的结构体的地址。(地址信息在哪里) -
addrlen
:该结构体的长度。
例子:
int serv_sock;
struct sockaddr_in serv_addr;
char *serv_port = "9190";
// 1. 创建服务器端套接字(监听套接字)
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
// 2. 地址信息初始化
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 服务器端IP地址常用方法,客户端则不这样
serv_addr.sin_port = htons(atoi(serv_port));
// 3. 分配地址信息
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
...