2_地址和字节相关函数

IPv4和IPv6的地址ASCII表示

IPV4:

定义的最大长度

#define INET_ADDRSTRLEN   16

ddd.ddd.ddd.ddd’\0’
例如111.111.111.111

43个ASCII码表示的十进制数加上3个冒号。
最大4
3+3+1=16字节。

IPv6:

#define INET6_ADDRSTRLEN 46

内嵌IPv4地址法,为了兼容IPv4和IPv6
HHHH.HHHH.HHHH.HHHH.HHHH.HHHH.ddd.ddd.ddd.ddd

64个ASCII码表示的16进制数加上一族IPv4地址,加上6个冒号。
最大6
4+16+6=46字节

函数总结

在这里插入图片描述

字节排序函数

(1)大端和小端

对于一个16位整数,他有两个字节。内存通常一个单元存储一个字节,因此存储这个数有两种存储方式。对于16位整数

				10101010       01010101
			    最高有效位	   最低有效位
				MSB		            LSB

小端:低序字节存储在起始地
在这里插入图片描述

大端:高序字节存储在起始地址
在这里插入图片描述
不同主机采用不同的排序方式。给定系统所用的字节序称为主机字节序。

(2)如何查看本机字节序

#include<stdio.h>
int main(){
	union{
		short s;
		char c[2];
	}un;
	un.s=0x0102;			//高字节序0x01,低字节序0x02.
	if(un.c[0]==1&&un.c[1]==2)
		printf("大端");
	else if(un.c[0]==2&&un.c[1]==1)
		printf("小端");
	else
		printf("未知字节序");
	return 0;
}

这里使用一个共用体union来测试字节序。对于字节数组来说,系统会创建一个从低内存到高内存的连续内存。对于本例来说,定义了一个short型变量0x0102。改数值的高字节序为0x01,低字节序为0x02。
它存储在共用体中,和一个两位的字节数组共用内存。对于char c[2],c[0]对应内存地址A,c[1]就是内存地址A+1。查看c[0]和c[1]中储存的数据,即可直到本机字节序。

(3)字节排序函数

主机字节序和网络字节序不同,因此需要用到排序函数来进行转换。使用这些函数时,我们不需要去直到字节序的真正值(到底是大端还是小端),只需要针对场景调用适当函数,来进行主机和网络之间的数值转换。一般在需要将IP地址填入套接字地质结构中时,要进行主机和网络的转换。

	#include<netinet/in.h>
	/*返回网络字节序的值*/
	uint16_t  htons(uint16_t  host16bitvalue);	//host  to  net
	uint32_t  htonl(uint32_t  host32bitvalue);
	/*返回主机字节序多的值*/
	uint16_t  ntons(uint16_t  net16bitvalue);	//net    to  host
	uint16_t  ntons(uint32_t  net32bitvalue);

命名规则:h代表host,n代表net,s代表short(用于16位),l代表long(用于32位)。

字节操纵函数

在网络通信中,我们处理的字节可能标准不同。有些字节(例如IP地址)可能包含值为0的字节(’\0’),却不是一般意义上的C字符串(C字符串总是以’\0’结尾,定义在string.h中)。字节操纵函数可以统一处理这些字节,用来处理套接字地址结构。

(1)bzero函数

b开头函数源于4.2BSD。

#include<strings.h>
void  bzero(void  *dest,size_t  nbytes);
void bcopy(const void *src,void *dest,szie_t nbytes);         	 //将指定数目的字节*src从源字节串移到目标串*dest
int    bcmp(const void *ptr1,const void *ptr2,size_t nbytes);	//比较任意两个字节串,相同返回0,否则1
关键字const,表示所限定的指针所指向的内容不会被函数更改。保证使用这两个函数不会改变原字符串。
bzero函数将目标字节串中指定数目的字节置为0。一般在创建一个套接字地质结构之前,调用该函数来进行初始化为0的操作。
struct sockaddr_in	servaddr;
//……
bzero(&servaddr, sizeof(servaddr)); 	
servaddr.sin_family      = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port        = htons(13);	

(2)men_系函数

men(内存)开头的函数源与ANSI C标准。

#include<string.h>
void *memset(void *dest,int c,size_t len);				//将目标字节串指定数目的字节置为c
void *mencpy(void *dest,const void *src,size_t nbytes);		//类似copy
int   mencmp(const void *ptr1,const void *ptr2,size_t nbytes);	//类似bcmp

地址转换函数

地址转换函数的作用是:在ASCII字符串和网络字节序的二进制值(存放在套接字地址结构中的值)之间进行转换。

(1)用于IPv4的地址转换函数

主要是inet_aton函数和inet_ntoa函数。
a代表ASCII码点分十进制数,n代表net网际32位IPv4地址addr_in

(a)inet_aton函数

用于将点分十进制数串(例如192.168.0.1)转换为相应的32位网络字节序二进制IPv4地址。
aton的意思就是ASCII to net:
ASCII -----> addr_in

函数原型
#include<arpa/inet.h>
int inet_aton(const char *strptr, struct in_addr *addrptr);		
参数:

const char *strptr:ASCII码的ip地址。
struct in_addr *addrptr:32位IPv4地址结构

返回值:

字符串有效返回1,否则返回0

作用:

将strptr指向的点分十进制数串ip地址,转换为struct in_addr类型的地址(用于套接字地质结构中)。

(b)inet_ntoa函数

该函数的作用是ntoa,net to ASCII
in_addr ----> ASCII

函数原型:
#include<arpa/inet.h>
char *inet_ntoa(struct in_addr inaddr);
参数:

struct in_addr结构的32位IPv4地址。

返回值:

指向ASCII码的点分十进制数串的指针。

(c)inet_addr函数(已被废弃)

ASCII -----> in_addr_t

函数原型:
#include<arpa/inet.h>
in_addr_t inet_addr(const char *strptr);
参数:

const char *strptr:ASCII码的IP地址。

返回值:

in_addr_t:uint32整数,不是in_addr结构。

作用:

作用相同。该函数存在的问题是:不能处理255.255.255.255。因为该数值在这个函数中代表处理出错

(2)适用于IPv4和IPv6

主要是inet_pton函数和inet_ntop函数。p代表表达(presentation),n代表数值(numeric)。这两个函数既可以用于IPv4,也可以用于IPv6,用参数int family来区分。

(a)inet_pton函数

函数原型:
#include<arpa/inet.h>
int inet_pton(int family,const char *strptr,void *addrptr);
参数:

int family:可以是AF_INET,也可以时AF_INET6
const char *strptr:ASCII码IP地址
void *addrptr:一个指针,指向IP地址转换后的二进制结果。

返回值:

如果对于指定的family,输入的字符串是无效的,则返回0;成功转换则返回1;若出错则返回-1.

(b)inet_ntop函数

函数原型
#include<arpa/inet.h>
const char *inet_ntop(int family,const void *addrptr,char *strptr,size_t len);
参数:

int family:可以是AF_INET,也可以是AF_INET6
size_t len:len参数是最后转换后字符串需要的存储单元的大小。对于这个大小,不同的协议族有不同的定义。在<netinet/in.h>中有如下定义:

#define INET_ADDRSTRLEN   16	
#define INET6_ADDRSTRLEN 46	
返回值:

如果len太小,不足以容纳最后的格式效果,则返回一个空指针,否则返回一个指向转换结果的指针。

包裹函数:sock_ntop函数(书中用的)

(1)包裹函数的意义

系统函数的缺陷是,必须与协议绑定。
例如对于inet_ntop函数,实现numeric to presentation

const char *inet_ntop(int family,const void *addrptr,char *strptr,size_t len);

对于IPv4:

struct sockaddr_in addr;
inet_ntop(AF_INET,&addr.sin_addr,str,sizeof(str));

对于IPv6:

struct sockaddr_in6 addr6;
inet_ntop(AF_INET6,&addr6.sin_addr,str,sizeof(str));

为了获得addrptr这个参数,必须与固定的地址族绑定。
为了解除绑定,使得IPv4和IPv6的移植性更好,考虑使用包裹函数来包裹这些需要特定地址族的函数。
在函数中加入额外的switch模块,用case区分。

(2)sock_ntop包裹inet_ntop

对于套接字地址结构,我们有可用的通用套接字地址结构,因此可以用通用套接字地址结构作为输入参数,并在函数内部再调用特定的方法(switch)来得到二进制IP地址。

函数原型:

#include<unp.h>
char *sock_ntop(const struct sockaddr *sockaddr,socklen_t addrlen);

参数:

sockaddr指向一个长度为addrlen的套接字地址结构。

返回值:

本函数的返回值是一个指向sockaddr的静态缓冲区的一个指针。

说明:

本函数使用sockaddr自己的静态缓冲区来保存结果。对于IPv4和IPv6来说,转换结果是ASCII形式地址加上分号,分号后加5位位端口号和’\0’。IPv6需要额外两边的方括号。因此
IPv4为ddd.ddd.ddd.ddd:ppppp。所需最大缓存空间为:16+6=22字节
IPv6为[HHHH.HHHH.HHHH.HHHH.HHHH.HHHH.ddd.ddd.ddd.ddd:ppppp]。最大:46+6+2=54字节

(3)源码

char * sock_ntop(const struct sockaddr *sa, socklen_t salen){
    	char	portstr[8];
    	static char str[128];		
		switch (sa->sa_family) {
			case AF_INET: {
				struct sockaddr_in  *sin = (struct sockaddr_in *) sa;

				if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)
					return(NULL);
				if (ntohs(sin->sin_port) != 0) {
					snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin->sin_port));
					strcat(str, portstr);
				}
				return(str);
			}
			case AF_INET6: {
				struct sockaddr_in6  *sin6 = (struct sockaddr_in6 *) sa;

				str[0] = '[';
				if (inet_ntop(AF_INET6, &sin6->sin6_addr, str + 1, sizeof(str) - 1) == NULL)
					return(NULL);
				if (ntohs(sin6->sin6_port) != 0) {
					snprintf(portstr, sizeof(portstr), "]:%d", ntohs(sin6->sin6_port));
					strcat(str, portstr);
					return(str);
				}
				return (str + 1);
			}
	//……还有其他省略
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值