早在19年5月就在某站上看到sylar的视频了,一直认为这是一个非常不错的视频。
由于本人一直是自学编程,基础不扎实,也没有任何人的督促,没能坚持下去。
每每想起倍感惋惜,遂提笔再续前缘。
为了能更好的看懂sylar,本套笔记会分两步走,每个系统都会分为两篇博客。
分别是【知识储备篇】和【代码分析篇】
(ps:纯粹做笔记的形式给自己记录下,欢迎大家评论,不足之处请多多赐教)
QQ交流群:957100923
Address模块-知识储备篇
一、IP地址的组成(我们只讲解IPV4了解概念扫盲即可,铺展开来讲篇幅太长)
局域网中只有同网段下的主机才能互相访问!!!
组成:IP地址 = 网络号 + 主机号
网络号:网络号相同表示属于同一个子网
主机号:表示子网中的某台主机
以下是一个IPV4的例子:
进制 | 8位 | 8位 | 8位 | 8位 |
---|---|---|---|---|
十进制 | 192 | 168 | 10 | 1 |
二进制 | 11000000 | 10101000 | 00001010 | 00000001 |
如表所示,由于没有指定网络号和主机号的分界线,所以网段并不明确
如果192.168 是网络号,10.1就是主机号
如果192.168.10是网络号,1就是主机号
所以我们貌似缺少一个东西来指定这个分界线,否则将无法确定网络号!!!
二、子网掩码
作用:用来标识子网,必须与IP同时存在。
1.子网掩码中必须由连续的1和连续的0组成
2.连续的1表示网络地址,连续的0表示主机地址
例如:
IP:192.168.1.1
子网掩码:255.255.255.0 ,
那ip对应的的网络号和主机号是多少?
分析:子网掩码换算成二进制:11111111.11111111.11111111.00000000
结论:子网掩码中连续的1表示网络号,连续的0表示主机号,对应到IP上即:192.168.1表示网络号,1表示主机号
进制 | 8位 | 8位 | 8位 | 8位 |
---|---|---|---|---|
IP十进制 | 192 | 168 | 1 | 1 |
子网掩码十进制 | 255 | 255 | 255 | 0 |
IP二进制 | 11000000 | 10101000 | 00000001 | 00000001 |
子网掩码二进制 | 11111111 | 11111111 | 11111111 | 00000000 |
与运算的结果为 | 11000000 | 10101000 | 00000001 | 00000000 |
结果十进制 | 192 | 168 | 1 | 0 |
大家要是这部分知识一直搞不清楚的话,不妨拿起纸和笔,不要吝啬你的草稿纸。
抛开十进制,用二进制全部理解一遍,再转到十进制。
其实我这边建议:如果有人和我一样,属于不太聪明类型的,可以不用去思考十进制。
只要把二进制表示模式下搞清楚就可以了,十进制只是一种表现形式而已。
三、~0u 什么意思?
#include <iostream>
int main(int argc,char** argv) {
std::cout << "~0 = " << ~0<< std::endl;
std::cout << "~0u = " << ~0u<< std::endl;
return 0;
}
输出结果:
//默认int类型
//0的正码为00000000,因为首位为0所以三码一致,
//按位取反为11111111(此时为按位取反后的补码),
//将补码转换为反码为11111110,
//反码再转换为正码输出(首位不变)为10000001也就是-1
~0 = -1
//无符号整型
~0u = 4294967295 <=> 11111111 11111111 11111111 11111111
四、(1<<31)-1 什么意思?
c++中int占四个字节,也就是32位,其中第一位(最高位)为符号为。
(1<<31)表示1左移31位,使得符号位为1,其他位为0。此时为负数
再将左移后的结果减一就使得符号位为0,其他位都为1,【即为int表示的有符号数的最大整数】。
五、网络常用结构体
1.ifaddrs
struct ifaddrs {
struct ifaddrs *ifa_next; // 指向列表中下一个结构的指针。该字段在列表的最后一个结构中为 NULL
char *ifa_name; // 接口名称
unsigned int ifa_flags; // 提供有关接口的一些信息的标志
struct sockaddr *ifa_addr; // 接口地址
struct sockaddr *ifa_netmask; // 接口的网络掩码
union {
struct sockaddr *ifu_broadaddr; // 接口广播地址
struct sockaddr *ifu_dstaddr; // 点对点目的地址
} ifa_ifu;
#define ifa_broadaddr ifa_ifu.ifu_broadaddr
#define ifa_dstaddr ifa_ifu.ifu_dstaddr
void *ifa_data; // 特定地址族数据的缓冲区
};
2.sockaddr
struct sockaddr {
ushort sa_family; // 网络协议
char sa_data[14];
};
3.sockaddr_in
struct sockaddr_in {
short sin_family; // 网络协议
unsigned short sin_port; // 端口
struct in_addr sin_addr; // ip
char sin_zero[8];
};
struct in_addr {
unsigned long s_addr; // 使用 inet_aton() 加载
};
4.sockaddr_in6
struct sockaddr_in6 {
sa_family_t sin6_family; /* 网络协议 */
in_port_t sin6_port; /* 端口 */
uint32_t sin6_flowinfo; /* IPv6 流信息 */
struct in6_addr sin6_addr; /* ip */
uint32_t sin6_scope_id;
};
struct in6_addr {
unsigned char s6_addr[16]; /* IPv6 address */
};
补充:
uint16 :占2个字节
uint32 :占4个字节
我们发现结构 sockaddr 和 sockaddr_in 字节数完全相同,都是16个字节,所以可以直接强转。
但是结构 sockaddr_in6 有28个字节,为什么在使用的时候也是直接将地址强制转化成(sockaddr*)类型呢?
这几个结构在作为参数时基本上都是以指针的形式传入的
我们拿函数bind()为例,这个函数一共接收三个参数:
第一个为监听的文件描述符。
第二个参数是sockaddr*类型。
第三个参数是传入指针原结的内存大小。
所以有了后两个信息,无所谓原结构怎么变化,因为他们的头都是一样的,也就是uint16 sa_family,那么我们也能根据这个头做处理。
int bind(int socket_fd, sockaddr* p_addr, int add_size);
5.addrinfo
struct addrinfo {
int ai_flags; // 地址信息标志
int ai_family; // 地址族(AF_INET, AF_INET6, AF_UNSPEC)
int ai_socktype; // 套接字类型(SOCK_STREAM, SOCK_DGRAM)
int ai_protocol; // 协议号(IPPROTO_TCP, IPPROTO_UDP),或0表示任意协议
socklen_t ai_addrlen; // 地址长度
struct sockaddr *ai_addr; // 网络地址结构指针
char *ai_canonname; // 规范名字(主机名或服务名)
struct addrinfo *ai_next; // 指向下一个addrinfo结构的指针
};
通过调用getaddrinfo()函数可以填充并返回一个或多个addrinfo结构,其中包含了特定主机名和服务名对应的可用地址信息。
六、网络常用函数
1.getifaddrs
#include <sys/types.h>
#include <ifaddrs.h>
int getifaddrs(struct ifaddrs **ifap);
/**
* func:函数存储对ifaddrs结构的链表的引用;
* return:成功返回0,失败返回-1;
*/
【注意】: 返回的数据是动态分配的,需要释放;
2.freeifaddrs
#include <sys/types.h>
#include <ifaddrs.h>
void freeifaddrs(struct ifaddrs *ifa);
/**
* func:释放对ifaddrs结构的链表的引用;
*/
3.getnameinfo
#include <sys/socket.h>
#include <netdb.h>
int getnameinfo(const struct sockaddr *sa, socklen_t salen,
char *host, size_t hostlen,
char *serv, size_t servlen, int flags);
/**
* func:将套接字地址转换为相应的主机和服务并返回;
* @param sa:套接字地址结构,保存输入的IP地址和端口号;
* @param salen:sa的长度;
* @param host:调用者分配缓冲区;
* @param hostlen:host长度;
* @param serv:调用者分配缓冲区;
* @param servlen:serv长度
* @param flags:
NI_NAMEREQD:若无法确定主机名,则返回一个错误;
NI_DGRAM:基于数据报(UDP)而不是基于流(TCP)的;
NI_NOFQDN:只返回本地主机的完全限定域名的主机名部分;
NI_NUMERICHOST:返回主机名的数字形式;
NI_NUMERICSERV:返回服务地址的数字形式;
* return:成功返回0,【节点和服务名称将使用以空字符结尾的字符串填充】;
失败设置errno:
EAI_AGAIN: 无法解析该名称, 稍后再试;
EAI_BADFLAGS: flags参数的值无效;
EAI_FAIL: 不可恢复的错误;
EAI_FAMILY: 无法识别地址族,或指定地址族的地址长度无效
EAI_MEMORY: 溢出;
EAI_NONAME: 名称不能解析所提供的参数
EAI_OVERFLOW: 溢出;
EAI_SYSTEM: 系统错误;
*/
【注意】:调用者可以通过提供一个NULL host(或serv)参数或一个零hostlen(或servlen)参数来指定不需要主机名(或不需要服
务名)。 但是,必须请求至少一个主机名或服务名。
七、获取本机网络信息案例
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
struct ifaddrs *ifaddr, *ifa;
int family, s;
char host[NI_MAXHOST];
if (getifaddrs(&ifaddr) == -1) {
perror("getifaddrs");
exit(EXIT_FAILURE);
}
/* 遍历链表,维护头指针,以便稍后可以释放链表 */
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL)
continue;
family = ifa->ifa_addr->sa_family;
/* 显示接口名称和族 */
printf("%s address family: %d%s\n",
ifa->ifa_name, family,
(family == AF_PACKET) ? " (AF_PACKET)" :
(family == AF_INET) ? " (AF_INET)" :
(family == AF_INET6) ? " (AF_INET6)" : "");
/* For an AF_INET* interface address, display the address */
if (family == AF_INET || family == AF_INET6) {
s = getnameinfo(ifa->ifa_addr,
(family == AF_INET) ? sizeof(struct sockaddr_in) :
sizeof(struct sockaddr_in6),
host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
if (s != 0) {
printf("getnameinfo() failed: %s\n", gai_strerror(s));
exit(EXIT_FAILURE);
}
printf("\taddress: <%s>\n", host);
}
}
freeifaddrs(ifaddr);
exit(EXIT_SUCCESS);
}
八、什么是大小端?
关于大端小端名词的由来,有一个有趣的故事,来自于Jonathan Swift的《格利佛游记》:Lilliput和Blefuscu这两个强国在过去的36个月中一直在苦战。战争的原因:大家都知道,吃鸡蛋的时候,原始的方法是打破鸡蛋较大的一端,可是那时的皇帝的祖父由于小时候吃鸡蛋,按这种方法把手指弄破了,因此他的父亲,就下令,命令所有的子民吃鸡蛋的时候,必须先打破鸡蛋较小的一端,违令者重罚。然后老百姓对此法令极为反感,期间发生了多次叛乱,其中一个皇帝因此送命,另一个丢了王位,产生叛乱的原因就是另一个国家Blefuscu的国王大臣煽动起来的,叛乱平息后,就逃到这个帝国避难。据估计,先后几次有11000余人情愿死也不肯去打破鸡蛋较小的端吃鸡蛋。
这个其实讽刺当时英国和法国之间持续的冲突。Danny Cohen一位网络协议的开创者,第一次使用这两个术语指代字节顺序,后来就被大家广泛接受。
大端存储,是将数据的低位字节放到高地址处,高位字节放到低地址处。
小端存储,是将数据的低位字节放到低地址处,高位字节放到高地址处。
假设有一个十六制数:0x12345678
内存地址 | 小端模式存放内容 | 大端模式存放内容 |
---|---|---|
0x4000 | 0x78 | 0x12 |
0x4001 | 0x56 | 0x34 |
0x4002 | 0x34 | 0x56 |
0x4003 | 0x12 | 0x78 |
小端模式 :强制转换数据不需要调整字节内容。
大端模式 :符号位的判定固定为第一个字节,容易判断正负。
九、大小端模式的判断(linux 平台下)
在头文件 <endian.h> 中,
可以使用宏定义 BYTE_ORDER 来判断大小端:
如果值为 LITTLE_ENDIAN ,则为小端。
如果值为 BIG_ENDIAN ,则为大端。
十、大小端模式的转换
头文件<byteswap.h>
- bswap_16():将一个 16 位整数从大端字节序转换为小端字节序或从小端字节序转换为大端字节序。
- bswap_32():将一个 32 位整数从大端字节序转换为小端字节序或从小端字节序转换为大端字节序。
- bswap_64():将一个 64 位整数从大端字节序转换为小端字节序或从小端字节序转换为大端字节序。
十一、enable_if是什么
enable_if<condition,T>::type;
int a;
//当a==1时,condition为true,那么type为int,所以会声明一个int* a;
typename std::enable_if<a == 1,int>::type *a;
//a !=1时,condition为true,那么type为double,所以会声明一个double* a;
typename std::enable_if<!(a == 1),double>::type *a;
在上面的例子中:
当a==1时,第一个的condition为true,所以会声明一个int* a。
但是如果a != 1,那么第二个的condition成立,会声明一个double* a。
今天就写到这吧,博客中不明白的可以搜索相关知识进行深入研究,这里点明了要掌握的基础内容。
【最后求关注、点赞、转发】
QQ交流群:957100923