在上一篇博客里,大家也许会对htons()感到疑惑吧,其实就是字节序的转换,所以这篇博客我们就来详细的解释一下什么是字节序。
也就是让大家对 bind函数有更加深刻而性感的认识
计算机是小端字节序,网络中是大端字节序。
内存中的多字节数据相对于内存地址有大端和小端之分。
计算机有两种储存数据的方式 : 大端字节序 和 小端字节序。
"大端字节序":
高位字节在前,低位字节在后,这也就和我们人类读写数字的方式是相同的。"小端字节序":
低位字节在前,高位字节在后,小端字节序和大端字节序是相反的。那么博主就给大家举一个例子吧。
比如说:
这就是大端字节序和小端字节序的存放了。
如果大家还是不能理解那么博主只能动用自己的表情包了!!!
看完这个图肯定还是会有同学有疑惑?为什么需要字节序?那为什么字节序还要区分大小呢??
Q:大家是不是会疑惑???明明大端字节序已经才是我们正常生活中的啊??为啥要在搞一个小端字节序出来,这不是自找麻烦吗???
A:要详细解释这个就要牵扯到计算机原理了,简单来说就是为了效率(因为计算都是从低位开始的),计算机电路是优先处理低位字节的。而我们图中已经说的很明白了,我们的字节序是从高位到低位来排列的,如果正常读取
(计算机处理字节序的时候,不知道什么是高位字节,什么是低位字节。它只知道按顺序读取字节,先读第一个字节,再读第二个字节)
会出现错误,而在将数据反过来读取效率又太低,所以就出现了小端字节序。
希望大家明白,除了计算机的内部是小端字节序外,其他的基本都是大端字节序.
对于字节序的处理,大家只要记住只有读取的时候,才必须区分字节序,其他情况都不用考虑
所以大家现在明白了吧,在我们输入了Ip和端口号之后,计算基会给我们处理成小端字节序,所以此时我们就要将小端字节序转换成大端字节序了。也就会用到我们的 htons()函数
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数将
主机字节序(小端字节序)转换成网络字节序(大端字节序),或者将网络字节序转换成主机字节序
#include <arpa/inet.h>
//主机字节序(小端字节序)转换成网络字节序(大端字节序)
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
//网络字节序(大端字节序)转换成主机字节序(小段字节序)
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
h表示"host",n表示network,l表示32位长整数,s表示16位短整数。
相信大家对字节序的概念已经有很性感的认识了。
所以我们再看一看 bind()函数那一整块代码
struct sockaddr_in servaddr; //为了让bind()绑定IP和端口号而定义的
bzero(&serv_addr, sizeof(serv_addr)); //将网络地址清空
memset(&serv_addr,0,sizeof(serv_addr));//z这个方法也可以
servaddr.sin_family = AF_INET;//与socket()的第一个参数 int domain一样的协议
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //绑定IP
servaddr.sin_port = htons(6666); //绑定端口号
bind(serv_addr, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
是不是看到了熟悉的 htons() 和 htonl()了???
可是这个我们还是不知道啊 INADDR_ANY??
这个其实就与我们 bind()的第二个参数有关了不知道大家忘记没有,第二个参数有两个结构体哟,如果记不住的话博主在把之前的图拿出来给大家看一看
看到了吧??存放32位的IP地址是不是有那么一点儿感觉了??哈哈没错其实就是 htonl()转换后的32位放到哪个结构体成员里面的。
INADDR_ANY:
相信大家再看到bind()函数应该很舒服了吧。
struct sockaddr_in servaddr; //为了让bind()绑定IP和端口号而定义的
bzero(&serv_addr, sizeof(serv_addr)); //将网络地址清空
memset(&serv_addr,0,sizeof(serv_addr));//z这个方法也可以
servaddr.sin_family = AF_INET;//与socket()的第一个参数 int domain一样的协议
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //绑定IP
servaddr.sin_port = htons(6666); //绑定端口号
bind(serv_addr, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
//第三个参数就是第二个参数的大小
至于为什么要强制转化我在上一篇博客已经说了,就不再赘述了。
也许会有同学问,如果我想要绑定固定的IP地址呢???我不想用 INADDR_ANY。那么就要用到我们另外的函数了----->IP地址转换函数
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
下面是书上对 IP地址转换函数的应用。
那里面的 argv[1]就是你自己输入的 IP地址
大家是不是又看到了陌生的东西了吧??
htons(atoi(argv[2]))??? htons()我倒是知道哦,但是 atoi()又是啥呀?? 其实很容易的,你看看前面是 serv_addr.sin.port所以是端口号,你在输入端口号和IP时,其实输入的是字符串,而atoi()函数的作用就是把字符串转换成整型数的一个函数。
//小技巧相比于上文介绍的字节序转换函数:
如果要使用特定的IP的对比
#include <arpa/inet.h>
//主机字节序(小端字节序)转换成网络字节序(大端字节序)
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
//网络字节序(大端字节序)转换成主机字节序(小段字节序)
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
h表示"host",n表示network,l表示32位长整数,s表示16位短整数。
//还有一种转换方式:
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
支持IPv4和IPv6
可重入函数
其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr。
因此函数接口是void *addrptr。
//二者的区别:
"192.168.1.24" -->unsigned int --> htonl() --> 网络字节序
"192.168.1.24" ------------------> 网络字节序 inet_pton();
网络字节序 ------------------> 点分十进制字符串 inet_ntop();
也就是 inet_pton();可以直接将IP字符串转换成网络字节序。而在使用htonl();之前还需要将IP字符串转换成无符号整数之后才行(转换方法atoi()上面有所提及)。
如果直接用 INADDR_ANY 这个宏(他本身就是无符号整数)所以直接用htonl即可。
好啦,bind()函数讲到这里相信大家一定不会再感到陌生了。下面再看一看bind()函数吧,最后一次加强印象了。
struct sockaddr_in servaddr; //为了让bind()绑定IP和端口号而定义的
bzero(&serv_addr, sizeof(serv_addr)); //将网络地址清空
memset(&serv_addr,0,sizeof(serv_addr));//这个方法也可以 二选一
servaddr.sin_family = AF_INET;//与socket()的第一个参数 int domain一样的协议
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //绑定IP
servaddr.sin_port = htons(6666); //绑定端口号
bind(serv_addr, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
谢谢观看