这块的函数又多又乱,今天写篇日志,以后慢慢补充
1. 网络地址介绍
1.1 ipv4
1.1.1 点、分十进制的ipv4
你对这个地址熟悉吗? 192.168.10.100,这可以当做一个字符串。被十进制数字、 “ . ”分开。IP地址的知识就不再多讲了,旧的分类方法把IP地址分为A类B类C类,这部分知识本文最末会提及。但是你见过300.401.299.299这样的网络地址吗?为什么这4个数字每个最大只有255呢?
1.1.2 32位无符号、整数型的ipv4
在电脑中,所有的数据都是二进制存储的,ipv4的地址,由4个数字组成,每个数字都占有二进制的8位,那么最大就是11111111(二进制) ,转化成十进制就是255 。所以ipv4 每个数字最大只能是255.
192.168. 10.100转化为二进制( 用你win10电脑上自带的计算器,左上角选择(程序员))就得到结果“
1100 0000 。1010 1000 。 00001010 。 0110 0100 把我用来分割的句号去掉就是32位短整数;
还有64位长整数,后面用到再说
1.1.3 字节序
这个词应该是前辈们直接翻译的man手册。
本机字节序:host byte order数据在你的电脑上的存放方式。
网络字节序: network byte order 。要跟别人的终端打交道,所有数据都得放到网上传输。网上的传输格式。
字节序有两种,大端字节序,小端字节序。不管哪种、都是二进制!!
先来弄明白内存中,有一块 叫做 “栈 ”,栈的增长方向是从下到上,就跟你摞砖头一样,
比方说,
你先定义了一个变量int a =10 ,
你又定义了int b =20,
b 和a的值就存放在栈上。你先定义了a ,a 就放0x0001这块内存,b 就在0x0002这块内存
(仅仅是举例而已,!这个例子别当真其实一个int就要占4个字节的,,相当于8位十六进制)
( 0x表示16进制,16进制的一位相当于二进制4位。)
假设现在有一个 十六进制的数字 0x1234 (转化为十进制是 4660)它需要上图中4个空抽屉才能存下他, 如果4放在 最低的抽屉,1放在最上面的抽屉,那就是 “小端”字节序。如果相反,那么就是大端字节序。
大端 : 高位数据,放在低位内存。
小端:高位数据,放在高位内存。低位数据,放在低位内存。
你的主机,是怎么存放很长的一个数据的呢?(x86一般都是小端字节序)参考另一个博主的文章
网络字节序可不能随心所欲,因为所有上网的人都用这一种格式。网络字节序采用 大端字节序格式。想象一下,别人给你发“我们去春游”,你的电脑肯定先收到 “我” ,最后一个收到“游” ,(网络传输是字节流)“我”先存起来肯定在低位内存,而这个短语当成一个整数 2603来看的话,最高位的 2 相当于最开头的 “ 我”, 是高位数据,放低位内存 (这个比喻恰当不?)
1.2 ipv6
IPv6的地址长度为128位,是IPv4地址长度的4倍。ipv6的地址不是被冒号切成4段,而是切成8段,每两个冒号之间相当于16位二进制。
于是IPv4点分十进制格式不再适用,采用十六进制表示。两个冒号之间16位二进制正好用4位十六进制表示。
IPv6有3种表示方法。(摘自百度百科)
1.2.1 冒分十六进制表示法
格式为 X: X: X: X:X:X:X:X,其中每个X表示地址中的16b,以十六进制表示,例如:
ABCD:EF01:2345:6789:ABCD:EF01:2345:6789
这种表示法中,每个X的前导0是可以省略的,例如:
2001:0DB8:0000:0023:0008:0800:200C:417A→ 2001:DB8:0:23:8:800:200C:417A
另外两种表示方法自学一下 不重要,因为你用不规范的输入当做inet_pton inet_ntop的参数的时候可能出错。
2. IP地址结构体分析
当我们说网络地址时,必须包括ip地址和一个16位的端口号,我们平时在浏览器输入网址,从来没输入过端口号,www.baidu.com : 80 没打过吧。是因为服务器上什么协议(服务)对应什么端口都是默认的。
2.1 sockaddr_in结构体
包含3个成员:
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
注意:port端口是 IP协议的上层---TCP /UDP协议里的内容,IP协议就只管IP地址。 sin_family一般就是AF_INET(个别函数里特殊的话一会儿再说),也不用转换成网络字节序,因为它不在网络上传播,只是本地电脑关心这个值。
sin_port和sin_addr必须转换成网络字节序,才能在网络上传播。注意IP地址结构体 in_addr 的成员:s_addr就是Ip 地址的网络字节序。
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
uint32_t 不就是无符号32位整数吗! 所以应该是ipv4 。然后在inet_aton的帮助手册中,你发现它还有另外一个名字in_addr_t
in <netinet/in.h> as:
typedef uint32_t in_addr_t;
struct in_addr {
in_addr_t s_addr;
};
2.2 sockaddr的结构体
现在一般不用它了,因为它的长度是固定的, 但是填充什么需要你决定,你要是填不满它规定的字节,你还得设计一些字符 “填充”进去 (padding) 太麻烦了所以废弃了。但是它也有写进代码的机会!!
函数bind 的第二个参数必须是它的指针,所以要用强制类型转换。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
我前一篇讲udp的代码里有句代码是:
int ret = bind(sfd, (struct sockaddr*)&servad, sizeof( servad));
2.3 总结一下!!!
网络地址结构体到底是什么!是in_addr结构体!!注意!!不是 in_addr_t
in_addr_t 是网络地址的 网络字节序!!
3.相关函数
三大类,就是网络字节序,本机字节序,字符串这3种格式来回转换。然后这3类函数,每类再细分2小种,你要的结果在返回值里,还是你要的结果在传出参数里。
3.1 字符串格式 “127.0.0.1 ”-- -- > 网络字节序格式
3.1.1 结果在返回值里
3.1.1.1 inet_addr
#include <arpa/inet.h>
in_addr_t inet_addr(const char * string);
将一个点、数字格式的ipv4地址,转为二进制的网络字节序格式
返回值:
成功:32位大端序整数型值,注意unsigned int ,不是long unsigned int
失败:函数可以检测你的输入格式是否正确,你输入的不正确的话,函数返回值是 INADDR_NONE (usually -1)
man手册上说的: Use of this function is problematic because -1 is a valid address (255.255.255.255). 别用这个函数。尽量去用 inet_aton(), inet_pton(3), or getaddrinfo(3), which provide a cleaner way to indicate error return.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
int main() {
//定义一个地址mytest
struct sockaddr_in mytest;
bzero(&mytest, sizeof(mytest));
mytest.sin_family =AF_INET;
//mytest.sin_port =htons(8888);
const char* str = "127.0.0.1";
in_addr_t result111 = inet_addr(str);
mytest.sin_addr.s_addr = result111;
printf("网络地址是%u\n", result111);
return 0;
}
结果
网络地址是16777343
用你的计算器 二进制是 0001。 0000 0000。 0000 0000 。0111 1111
哦? 1.0.0.127 ??对了,网络字节序是大端的,所以127放在最低位了
3.1.1.2 inet_network
in_addr_t inet_network(const char *cp);
将一个点、数字形式的ipv4的字符串,转化为 本机字节序格式,但是仍可以作为网络地址,进行网络传输 (???)我测试过了,用不了网络传输。。
struct sockaddr_in mytest;
mytest.sin_addr.s_addr = inet_network (“127.0.0.1”);//无法建立网络连接的
返回值:成功:转换成的网络地址。
失败: -1
#include <unistd.h>
#include <stdio.h>
int main(char agrc,char *argv[]){
int i,x;
printf("%s\n",argv[1]);
unsigned int ip = inet_network(argv[1]);
printf("%8x \n",ip);
printf("%x\n",ip);
return 0;
}
编译后,输入./a.out 192.168.0.1
结果
192.168.0.1
c0a80001
c0a80001
3.1.2 结果在参数里
3.1.2.1 inet_aton
#include <arpa/inet.h>
int inet_aton (const char *cp, struct in_addr *inp);
将一个本机(host ,本地的)的点、数字格式的ipv4地址,转为二进制的网络字节序格式
参数1 : 有const ,是传入参数。需转换的IP地址信息的字符串,例如“127.0.0.1””
参数2: 传出参数, in_addr结构体的指针里,现在存着你想要的in_addr的信息了。(看清楚是谁的指针!!!)
返回值:成功 1 失败 0
//inet_aton
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
int main() {
//定义一个地址mytest
struct sockaddr_in mytest;
bzero(&mytest, sizeof(mytest));
mytest.sin_family =AF_INET;
//mytest.sin_port =htons(8888);
const char* str = "127.0.0.1";
inet_aton( str, &(mytest.sin_addr));
printf("网络地址是%u\n", mytest.sin_addr.s_addr);
return 0;
}
结果跟上面那个一样 网络地址是16777343
3.1.2.2 inet_pton
#include <arpa/inet.h>
int inet_pton (int af, const char *src, void *dst);
将字符串转为网络地址结构体,ipv4 ipv6都实用 结果复制到dst指针指向的内容里。
参数1 : af 填写AF_INET时,参数2 应该是点分十进制的ipv4格式
af填写 AF_INET6 时,参数2应该是 冒号切好的,8个十六进制的ipv6 (这种写法最稳妥)。后面太复杂了不讨论ipv6 情况。。。
参数2 :格式对应好。
参数3: 你可以传任何指针进去,反正void* 是万用指针。。看一下最后面那个man手册子带例子,作者就是传了unsigned char buf[ sizeof( struct in6_addr) ] 字符数组指针buf进去。
返回值:
成功:返回1
失败:你写的参数2格式不对,返回0
你写的af参数不对,返回-1
例子:看我最后面复制的man 手册的例子。作者把地址复制到一个unsigned char 无符号字符数组里了。。里面每个元素都是1字节(二进制8位)的,正好(如果是ipv4)就是地址中的一个十进制数字。
在之前写的一篇代码中,老师也用到了这个函数,但是我现在学习了说明手册,我觉得可以将结果复制到网络地址结构体,而不是网络地址的字节序
在客户端要去连接服务器的时候,需要将字符串转换为网络字节序。
老师原代码 int s = inet_pton( AF_INET,"127.0.0.1",&seradd.sin_addr.s_addr);
我自己改了一个小地方,一样可以成功
int s = inet_pton( AF_INET,"127.0.0.1",&seradd.sin_addr);
看来这个函数第三个参数很灵活
3.2 网络字节序格式 -- -- 》 字符串格式
3.2.1 结果在返回值里
3.2.1.1 inet_ntoa 函数
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
char * inet_ntoa (struct in_addr in);
将一个网络字节序格式的网络主机地址 in, 转化为数字+点的字符串格式的ipv4地址。这里是服务器端代码,客户端不需要代码,直接开启一个新终端,nc 127.0.0.1 8888命令,然后服务器那边就
请求连接的客户网络地址是127.0.0.1
//inet_ntoa
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
int main() {
int sfd = socket (AF_INET, SOCK_STREAM, 0);
if (sfd <0){
printf("socket fun error/n");
return -1;
}
struct sockaddr_in myad;
bzero(&myad, sizeof(myad));
myad.sin_family =AF_INET;
myad.sin_port =htons(8888);
myad.sin_addr.s_addr = htonl (INADDR_ANY);
int ret = bind(sfd, (struct sockaddr *)&myad, sizeof(myad));
if (ret<0) {
printf("bind error/n");
return -1;
}
listen( sfd,128);
//把连接的客户端的地址打出来
struct sockaddr_in clientad;
bzero(&clientad, sizeof(clientad));
clientad.sin_family =AF_INET;
clientad.sin_port =htons(8888);
socklen_t clilen = sizeof(clientad);
//有客户连接啦
int newfd= accept(sfd,(struct sockaddr*)&clientad,&clilen);
char* str = {0};
str = inet_ntoa(clientad.sin_addr);
printf("请求连接的客户网络地址是%s\n",str);
return 0;
}
3.2.1.2 inet_ntop 函数
#include <arpa/inet.h>
const char * inet_ntop (int af, const void *src, char *dst, socklen_t size);
将网络地址结构体src ,转化为字符串,复制到字符串的指针 dst 中。dst必须不是NULL
看一下这篇文章最后的代码
char addstring[128];
memset(addstring, 0x00,128);
printf("服务器端,get connect,客户端地址是:\n");
printf("IP:%s,PORT: %d\n", inet_ntop(AF_INET, &dst.sin_addr.s_addr,addstring,sizeof(addstring)), ntohs(dst.sin_port));
函数说明里说,第二个参数只要是一个容器就行,老师这里的代码是不是多写了一点呢,我试着传递到sin_addr里面!! 结果一样!成功了。(端口就不打印了)
//服务器端,部分代码!完整代码去看我那篇文章
//socket编程基础
struct sockaddr_in dst;
bzero(&dst, sizeof(dst));
dst.sin_family =AF_INET;
dst.sin_port =htons(8888);
socklen_t dstleng = sizeof(dst);
int newfd = accept (sfd, (struct sockaddr* )&dst, &dstleng);
if (newfd <0) {
printf("服务端,accept error、\n");
return -1;
}
char addstring[128];
memset(addstring, 0x00,128);
printf("服务器端,现在打印客户端地址:\n");
// const char *inet_ntop(int af, const void *src,
// char *dst, socklen_t size);
printf("IP:%s\n", inet_ntop(AF_INET, &dst.sin_addr,addstring,sizeof(addstring)));
运行结果,连接后,服务器端这边
服务器端,现在打印客户端地址:
IP:127.0.0.1
3.2.2 结果在传出参数里
还没找到
3.3 字节序相互转化
3.3.1 本机字节序 -》》 网络字节序
uint32_t htonl (uint32_t hostlong); // 32位 IP 地址
uint16_t htons (uint16_t hostshort); // 16位 短整数,端口
3.3.2 网络字节序 --》》本机字节序
uint32_t ntohl (uint32_t netlong);
uint16_t ntohs (uint16_t netshort);
注意,自己写的代码里 端口不要设置1024以前的。。。那是保留端口,给系统用的。
3.4 废弃的3个函数
网络地址结构体,这个结构体里唯一成员(32位无符号整型网络地址)来回转换的3个函数
in_addr_t inet_lnaof (struct in_addr in);
将in 结构体里的一部分 ---本地网络地址返回,格式是本机字节序。
in_addr_t inet_netof (struct in_addr in);
将in 结构体里的一部分 ---网络号码返回,格式是本机字节序。
??? 难道这个in里面不就一个成员吗
struct in_addr inet_makeaddr (in_addr_t net, in_addr_t host);
为啥废弃了呢:在传统x86架构的电脑里(大家用的都是吧) ,主机字节序是小端Least Significant Byte first (little endian) ,最没地位的字节站最前面
网络字节序是大端(big endian) Most Significant Byte first 最重要的字节最前面
这三个函数是跟过去的A B C 三类IP地址相关的。
A类IP 地址:一个重要的字节当网络地址 ,剩下三个字节当本地的机器们的地址。最重要的1个二进制,值是 0
B类IP 地址: 2个重要的字节当网络地址 ,剩下2个字节当本地的机器们的地址。最重要的2个二进制的1 0
C类IP 地址: 3个字节当网络地址 ,1个字节当本地的机器们的地址。最重要的3个二进制位,值是 11 0
传统的这个模式太死板了,现在已经是CIDR (无类别间域路由))
4.man手册自带例子解析
const char * inet_ntop (int af, const void *src, char *dst, socklen_t size);
int inet_pton (int af, const char *src, void *dst);
在代码中,作者的 af参数并没有写固定死,而是传参数进去。
是将你的执行命令的第二个参数 argv[1] (第一个参数是argv[0] 命令名称) 转化为一个整数。
函数 int atoi (const char * str); 将一个字符串转化为整数。
当然,在inet_pton 和inet_ntop ()两个函数中,对于af参数的格式来说,本来就是 AF_INET 和AF_INET6两种。
result = A? B: C 如果A为真,那么result 等于B 否则等于C 但是加长的三目运算符见过吗
代码中有一句特别长的三目运算符,我看着比较晕
domain = (strcmp(argv[1], "i4") == 0) ? AF_INET :
(strcmp(argv[1], "i6") == 0) ? AF_INET6 : atoi(argv[1]);
查了一下,这是多条件的三目运算符
条件1 ?结果1
:条件2? 结果2
:条件3 ?结果3
:""
条件1为真,取结果1 ;条件2为真 ,取结果2
如果第二个参数是 i4 那么domain = AF_INET 如果 第二个参数你写i6 那么domain = AF_INET6 ,本示范代码最后原作者都写了i6 。
代码中首先使用inet_pton 函数,将你写的地址字符串转化为网络字节序,并且存储在unsigned char buf[sizeof(struct in6_addr)] 这个数组的首地址 (buf)中,注意这个buf的长度必须是 sizeof( struct in6_addr ) 是结构体的字节数!!
文中有一个宏 INET6_ADDRSTRLEN 表示46(字节),参考看这篇
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]){
unsigned char buf[sizeof(struct in6_addr)];
int domain, s;
char str[INET6_ADDRSTRLEN];
if (argc != 3) {
fprintf(stderr, "Usage: %s {i4|i6|<num>} string\n", argv[0]);
exit(EXIT_FAILURE);
}
domain = (strcmp(argv[1], "i4") == 0) ? AF_INET :
(strcmp(argv[1], "i6") == 0) ? AF_INET6 : atoi(argv[1]);
s = inet_pton(domain, argv[2], buf);
if (s <= 0) {
if (s == 0){
fprintf(stderr, "Not in presentation format");
}
else {
perror("inet_pton");
}
exit(EXIT_FAILURE);
}
if (inet_ntop(domain, buf, str, INET6_ADDRSTRLEN) == NULL) {
perror("inet_ntop");
exit(EXIT_FAILURE);
}
printf("%s\n", str);
exit(EXIT_SUCCESS);
}
手册自带的 、执行和结果
$ ./a.out i6 0:0:0:0:0:0:0:0
::
$ ./a.out i6 1:0:0:0:0:0:0:8
1::8
$ ./a.out i6 0:0:0:0:0:FFFF:204.152.189.116
::ffff:204.152.189.116