欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux 网络编程 —— 地址族与字节序

程序员文章站 2022-04-12 16:19:11
...

网络地址:用于唯一确定网络中的某一台计算机。包括IPv4(4个字节)和IPv6(16个字节)。

端口号:用于区分同一台主机上的不同套接字。

  • 端口号由16位构成,可分配范围为0~65535, 其中0~1023为知名端口。
  • 端口号不能重复,但TCP套接字和UDP套接字不会共用端口号,所以允许它们重复:即,如果某TCP套接字使用9190端口,则其他TCP套接字就无法使用该端口号,但UDP套接字可以使用。

地址结构

填写地址信息的线索:

  1. 使用哪一种地址族?如,IPv4
  2. IP地址是多少?如,211.204.214.76
  3. 端口号是多少?如,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代表shortl代表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));

...