地址族与数据序列

本文介绍了网络编程中的关键概念,包括IP地址和端口号的表示方法、网络字节序及其转换函数,以及如何将字符串形式的IP地址转换为网络字节序的整数型。

地址族与数据序列

分配给套接字的IP地址与端口号

  • IP

    为了接发网络数据而分配给计算机的值

    IP地址分为IPv6和IPv4地址,我们主要以IPv4为例。

  • 端口号

    为了区分程序中创建的套接字而分配给套接字的序号

一、地址信息的表示

回顾我们之前讲的bind函数

#include<sys/socket.h>
int bind(int sockfd,struct sockaddr* myaddr,socklen_t addrlen);
//bind函数的作用在于将套接字和IP+端口号绑定

sockfd: 套接字标识符

myaddr: 存放IP地址与端口号的数据结构

socklen_t: 计算前一个数据结构的大小

而我们使用过程当中,并不会去创建一个sockaddr类型结构体变量,而是创建一个sockaddr_in对象,然后进行强转,如下所示:

    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    ......
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_addr.sin_port=htons(atoi(argv[1]));
    //调用bind()函数分配IP地址以及端口号
if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)
        error_handling("bind() error");

为什么不能直接声明sockaddr对象而去声明sockaddr_in对象,我们稍后分析,我们先看看sockaddr_in的结构体:

struct sockaddr_in
{
    sa_family_t sin_family;     //地址族
    uint16_t sin_port;          //16位TCP/IP端口号
    struct in_addr sin_addr;    //32位IP地址
    char      sin_zero[0]       //不使用
}

在该结构体当中内嵌一个struct in_addr结构体:
struct in_addr
{
    in_addr_t  s_addr;   //32位IPv4地址
}
  • 地址族sin_family

AF_INET: IPv4网络协议使用的地址族

AF_INET6:IPv6网络协议使用的地址族

AF_LOCAL: 本地通信中采用的UNIX协议的地址族

再看看sockaddr结构体信息

struct sockaddr
{
    sa_family_t sin_family   //地址族(Address Family)
    char sa_data[14];        //地址信息
}

sa_data数组当中保存了地址信息中需要包含IP地址和端口号剩余部分填充0,而对于用户来讲将两个不同信息填入同一个数组非常麻烦,因此就有了sockaddr_in结构体方便我们将信息填入。

二、网络字节序与地址变换

CPU 保存数据的方式有两种:大端序与小端序, 这意味着在CPU当中对数据的解析方式有两种,当我们用大端序计算机给小端序计算机发送数据,我们需要进行转换,但转换的前提是需要知道发送方采用何种方式解析数据。然而这种方式非常愚蠢而且费力。因此我们需要在网络传输过程当中将数据转换成一种和大小端序无关的统一解析方式,这样接收方可以采用统一的方式解析数据,如何将要发送的数据进行转化以及将接收的数据进行解析是本节主要的内容。

2.1 转换函数

unsigned short htons(unsigned short);//short类型的本地数据转换成网络字节序,用于发送数据
unsigned short ntohs(unsigned short);//将网络字节序转换成short类型的本地数据,用于接收数据
unsigned long htonl(unsigned long);  //long类型的本地数据转换成网络字节序,用于发送数据
unsigned long ntohl(unsigned long);  //将网络字节序转换成long类型的本地数据,用于接收数据

记忆方法:

s 、 l: 指的是数据类型short、long

h: host, 本地数据

n: net, 网络数据

to:指的是转换方向

2.2 使用方式

#include<stdio.h>
#include<arpa/inet.h>

int  main(int argc, char *argv[])
{
    unsigned short host_port=0x1234;
    unsigned short net_port;
    unsigned long host_addr=0x12345678;
    unsigned long net_addr;
    net_port=htons(host_port);
    net_addr=htonl(host_addr);

    printf("Host ordered port: %#x \n",host_port);
    printf("Network ordered port:%#x\n",net_port);
    printf("Host ordered address :%#lx \n",host_addr);
    printf("Network ordered address:%#lx\n",net_addr);
    return 0;
}

运行结果:

Host ordered port: 0x1234 
Network ordered port:0x3412
Host ordered address :0x12345678 
Network ordered address:0x78563412

无论是在大端序计算机还是小端序计算机,运行结果都是一样。

注意:除了向sockaddr_in 结构体变量填充的数据需要我们进行以上转换操作,其他无需考虑。

三、网络地址的初始化与分配

3.1 将字符串信息转换为网络字节序的整数型

sockaddr_in 中保存的地址信息是32位整型,为了分配IP地址,我们需要将点十分制表示法例如:“1.2.3.4”、"222.204.3.156"等等,我们需要将类似上述的IP地址转换成32位整数型,例如:0x1234567。

3.2 转换函数

1. inet_addr函数

函数原型

#include<arpa/inet.h>
in_addr_t inet_addr(const char *string);

使用

#include<stdio.h>
#include<arpa/inet.h>

int  main(int argc, char *argv[])
{
    char *addr1="1.2.3.4";
    char *addr2="1.2.3.256";
    unsigned long conv_addr=inet_addr(addr1);
    if(conv_addr==INADDR_NONE)printf("Error occured!\n");
    else{
        printf("Network ordered integer addr:%#lx \n",conv_addr);
    }
    conv_addr=inet_addr(addr2);
    if(conv_addr==INADDR_NONE)printf("Error occured!\n");
    else{
        printf("Network ordered integer addr:%#lx \n\n",conv_addr);
    }

    return 0;
}

运行结果

Network ordered integer addr:0x4030201 
Error occured!

通过结果我们发现,该函数不仅仅能将IP地址字符串转换成32位整数型数据,同时还能检查无效的IP地址。

2. inet_aton函数

该函数与inet_addr函数作用一样,不过他在转换的同时能将结果直接填入in_addr结构体当中,比inet_addr省了一个步骤,因此在使用的过程当中我们更多的是使用inet_aton函数。

函数原型

#include<arpa/inet.h>
int inet_aton(const char *string,struct in_addr* addr);

使用

#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
void error_handling(char *message);
int  main(int argc, char *argv[])
{
  char *addr="127.232.124.79";
  struct sockaddr_in addr_inet;
  if(!inet_aton(addr,&addr_inet.sin_addr))
  {
      error_handling("Conversation error");
  }
  else{
      printf("Network ordered integer addr:%#x  \n",addr_inet.sin_addr.s_addr);
  }
  return 0;
}
void error_handling(char *message)
{
   fputs(message,stderr);
   fputc('\n',stderr);
   exit(1);
}

运行结果

Network ordered integer addr:0x4f7ce87f 

记住怎么用就行。

该函数存在对立函数inet_ntoa, 将in_addr结构体里面的32位IP整数转换成点十分制

函数原型

#include<arpa/inet.h>
char *inet_ntoa(struct in_addr adr);

使用

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<arpa/inet.h>
int  main(int argc, char *argv[])
{
 struct sockaddr_in addr1,addr2;
 char *str_ptr;
 char str_arr[20];
 addr1.sin_addr.s_addr=htonl(0x1020304);
 addr2.sin_addr.s_addr=htonl(0x1010101);
 str_ptr=inet_ntoa(addr1.sin_addr);
strcpy(str_arr,str_ptr);
printf("Dottede-Decimal notation1:%s\n",str_ptr);

inet_ntoa(addr2.sin_addr);
printf("Dottede-Decimal notation2:%s\n",str_ptr);
printf("Dottede-Decimal notation3:%s\n",str_arr);
return 0;
}

运行结果:

ottede-Decimal notation1:1.2.3.4
Dottede-Decimal notation2:1.1.1.1
Dottede-Decimal notation3:1.2.3.4

需要注意的是该函数返回值是一个指针,意味着在该函数内部申请了一块内存存放返回结果,在外部我们也应该申请一块内存来存放返回结果,否则当下次再次调用该函数的时候会覆盖之前的结果,如结果中所示,结果展示了三行数据,第一行是第一次调用该函数得到的返回结果,我们用str_arr存放结果,然后二次调用该函数,如第二行所示,结果被覆盖。

3.3 网络地址初始化

这里直接展示初始化方法

struct sockaddr_in addr;                 
char *serv_ip="222.221.103.154";            //声明IP地址字符串
char *serv_port="9091";                     //声明端口号字符串
memset(&addr,0,sizeof(addr));               //结构体变量addr的所用成员初始化为0
addr.sin_family=AF_INET;                    //指定地址族
addr.sin_addr.s_addr=inet_addr(serv_ip);    //基于字符串的IP地址初始化
addr.sin_port=htons(atoi(serv_port));       //基于字符串的端口号初始化
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值