目录
1、TCP/IP以及OSI七层模型
1.1、数据链路层
IEEE802.3
MAC
CSMA/CD 过程
交换机的转发过程:
理解冲突域和广播域
VLAN原理(Virtual Local Area Network)
1.2 网络层
VLAN间路由:
DNS
网络中LAN、WAN、WLAN、VLAN和VPN的区别
QinQ原理及技术(QinQ白皮书)
2、TCP/IP编程
2.1 服务器端
sockaddr_in
htonl,htons,ntohl,ntohs的详解
int bind (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len)
2.2 客户端
C++内存管理
C++多态
数据链路层接受到数据帧,解包包括去头、去尾和校验;封装则是刚刚相反;
其他层则只封装头部!
如图
IEEE 802.3 通常指以太网。一种网络协议。描述物理层和数据链路层的MAC子层的实现方法,在多种物理媒体上以多种速率采用CSMA/CD访问方式,对于快速以太网该标准说明的实现方法有所扩展。
IEEE802.3定义了利用CSMA/CD(带冲突检测的载波侦听多路访问)技术而构建10Mbps,100Mbps,1Gbps,甚至10Gbps的局域网协议集,同时还选取了所采用的传输介质,当前主要是非屏蔽双绞线和光纤。
以太网交换机是一种数据链路层设备,通过设备的MAC地址进行数据帧的转发。目前情况下,以太局域网一般采用星型拓扑,中心点为集线器或交换机。
MAC(Media Access Control,介质访问控制)地址:48位,一般用12位16进制采用点分隔的方式表示,比如:0001.0323.31DD,MAC地址由硬件制造商预烧到以太网适配器中,所以也叫硬件地址。
以太网环境中,广播是特殊格式的数据帧,它的发送目标是以太网网络中的所有设备,广播的MAC地址为:FFFF.FFFF.FFFF。组播是特殊的广播,组播中,只将流量发送到某些特定设备上,组播MAC地址的前缀是:01-00-5E
- 为了侦听网络上的载波信号,局域网设备需要倾听以太网网络;
- 如果局域网设备没有在网络中检测到载波信号,那么它将开始发送信号。局域网设备将倾听网络上的载波信号,并且将其与输出进行匹配。
- 如果输入和输出之间存在差异,那么就表示其他局域网设备已经发送信号,这就是发生碰撞的情况;
- 为了警告导致碰撞的其他局域网设备,这台局域网设备将发出拥塞信号;
- 为了能够再次开始发送信号的过程,局域网设备将随机等待一段时间,这被称为退避算法。如果接下来的尝试又发生多次碰撞的情况,那么退避算法将延长等待时间。
交换机转发是根据数据帧的目的MAC地址,查MAC表(桥接表)(注意是MAC表而不是路由表) ,得到出端口号,进而将该数据帧从指定端口转发出去。在高端交换机上,一该表一般保存在CAM(Content Addressable Memory)内存中,CAM内存与一般内存不同,它是直接以内容为索引进行查找,而且查找一次CAM的时间是固定的,这样防止软件算法查找时间不固定导致的各类问题。
转发过程为:
- 未知MAC扩散(查询时如果没有查到相应的表现,则要进行扩散处理,交换机将该帧向除源端口之外的其余端口发送出去。)
- 源MAC学习(主机1要发数据帧到主机4,交换机在接受到该帧后,解析帧获取帧的源MAC地址,将对应的MAC和交换机端口更新到MAC表 )
- 已知MAC转发(MAC表中已经存在了对应的表项,则根据查到的出端口将帧转发出去)
- MAC表项计时老化(当某条MAC表项没有被用到的时间超过一定的时间后,将该表项进行老化,从MAC表中删除)
这些流程的核心是MAC表:
目的MAC地址
出端口号
0001-0EA3-A1AA
端口1
0001-0EA3-A1BB
端口2
0001-0EA3-A1CC
端口3
0001-0EA3-A1DD
端口4
看VLAN之前先了解两个概念:冲突域和广播域
冲突域:
【定义】在同一个冲突域中的每一个节点都能收到所有被发送的帧。简单的说就是同一时间内只能有一台设备发送信息的范围。
【分层】基于OSI的第一层物理层
【设备】第二层设备能隔离冲突域,比如Switch。交换机能缩小冲突域的范围,交换接的每一个端口就是一个冲突域。
广播域:
【定义】网络中能接收任一设备发出的广播帧的所有设备的集合。简单的说如果站点发出一个广播信号,所有能接收收到这个信号的设备范围称为一个广播域。
【分层】基于OSI的第二层数据链路层
【设备】第三层设备才能隔离广播域,比如Router。路由器能隔离广播域,其每一个端口就是一个广播域。
下面通过三个例子来说明:(暂不考虑VLAN情况)
例子一:一个Switch(交换器)直连三台PC和一台hub,而hub下直连有2台PC。
图中已经给出了答案,可是,这个4个冲突域1个广播域是怎么算出来呢?
根据前面介绍的关于广播域的定义中我们知道 ,只有第三层设备才能隔离广播域。上图中并没有router等第三层设备,所以,这里的广播域没有被隔离。也就是说上图中的网络只有1个广播域。
冲突域的计算,前面有说Switch能缩小冲突域,一个Switch端口其实就是一个冲突域,上图中有3台pc和1台hub直连到Switch上,所以,这里的冲突域为4个。
第一个例子比较简单,下面我们在网络中有router第三层设备的例子
一台router下直接连接三台hub,hub下都各自连有三台pc:
第三层设备router能隔离广播域,上图中router的三个端口分别直连了三个hub,因此得出有三个广播域。
但是,那3个冲突域是怎么来的呢?
其实,router他不但能隔离广播域,默认也是可以缩小冲突域的。所以上图中的router用3个端口将网络既分开成了3个广播域,又缩小成了3个冲突域。
第二个例子给了我们一个提醒,那就是路由器默认也是可以隔离冲突域的。
好了,下面我再看最后一个例子,这里都用上了常用的网络设备hub、Switch和router。
一台router下连两台交换机和一台hub,两台交换机下分辨连有三台 PC,而hub下连有4台PC:
上图所示网络,算出3个广播域不难,因为router有3个端口直连了2台交换机和1台集线器嘛。可是,冲突域不是7个吗?怎么是9个呢?两台交换机共使用了6个端口,外加路由器下还直连了一个集线器,这也是一个冲突域。于是,我可以得出6+1=7,7个冲突域啊。究竟是哪里算少了?对了,就是路由器到两台交换机之间也还是存在冲突域的。这一点也特别需要注意。
最后记录一下例子中提到的需要注意的地方了:
1、第二层设备只能隔离冲突域,第三层设备才能隔离广播域;
2、路由器不但能隔离广播域,默认也是可以隔离冲突域的;
3、路由器下直连交换,则路由器到交换机之间也是存在冲突域的。
总结:
1、第二层设备只能隔离冲突域,第三层设备才能隔离广播域
2、路由器不但能隔离广播域,默认也是可以隔离冲突域的
3、路由器下直连交换,则路由器到交换机之间也是存在冲突域的
4、router不仅能能够分割广播域,也能缩小冲突域
5、交换机的每一个端口是一个冲突域
6、集线器下连的所有端口是一个冲突域(上一个设备是路由器)
参考:非常详细的VLAN解析
VLAN概念:
VLAN(Virtual LAN),翻译成中文是“虚拟局域网”。LAN可以是由少数几台家用计算机构成的网络,也可以是数以百计的计算机构成的企业网络。VLAN所指的LAN特指使用路由器分割的网络——也就是广播域。
VLAN性质:
- 相同VLAN内主机可以任意通信
- 二层交换
- 不同VLAN内主机二层流量完全隔离(只能通过路由或者三层交换机将报文从一个VLAN转发到另一个VLAN)
- 阻断广播包,减小广播域
- 提供了网络安全性
- 相同VLAN跨交换机通信
- 实现虚拟工作组
- 减少用户移动带来的管理工作量
为什么VLAN属于数据链路层?
解释 :
分割广播域时,一般都必须使用到路由器。使用路由器后,可以以路由器上的网络接口(LAN Interface)为单位分割广播域。
但是,通常情况下路由器上不会有太多的网络接口,其数目多在1~4个左右。随着宽带连接的普及,宽带路由器(或者叫IP共享器)变得较为常见,但是需要注意的是,它们上面虽然带着多个(一般为4个左右)连接LAN一侧的网络接口,但那实际上是路由器内置的交换机,并不能分割广播域。
况且使用路由器分割广播域的话,所能分割的个数完全取决于路由器的网络接口个数,使得用户无法自由地根据实际需要分割广播域。
与路由器相比,二层交换机一般带有多个网络接口。因此如果能使用它分割广播域,那么无疑运用上的灵活性会大大提高。
用于在二层交换机上分割广播域的技术,就是VLAN。通过利用VLAN,我们可以自由设计广播域的构成,提高网络设计的自由度。
汇聚链接
那么如果同一个VLAN下的主机需要跨越多个交换机呢?如下图:
汇聚链接(Trunk Link)指的是能够转发多个不同VLAN的通信的端口。
汇聚链路上流通的数据帧,都被附加了用于识别分属于哪个VLAN的特殊信息。
现在再让我们回过头来考虑一下刚才那个网络如果采用汇聚链路又会如何呢?用户只需要简单地将交换机间互联的端口设定为汇聚链接就可以了。这时使用的网线还是普通的UTP线,而不是什么其他的特殊布线。图例中是交换机间互联,因此需要用交叉线来连接。
接下来,让我们具体看看汇聚链接是如何实现跨越交换机间的VLAN的。
A发送的数据帧从交换机1经过汇聚链路到达交换机2时,在数据帧上附加了表示属于红色VLAN的标记。
交换机2收到数据帧后,经过检查VLAN标识发现这个数据帧是属于红色VLAN的,因此去除标记后根据需要将复原的数据帧只转发给其他属于红色VLAN的端口。这时的转送,是指经过确认目标MAC地址并与MAC地址列表比对后只转发给目标MAC地址所连的端口。只有当数据帧是一个广播帧、多播帧或是目标不明的帧时,它才会被转发到所有属于红色VLAN的端口。
蓝色VLAN发送数据帧时的情形也与此相同。
通过汇聚链路时附加的VLAN识别信息,有可能支持标准的“IEEE 802.1Q”协议,也可能是Cisco产品独有的“ISL(Inter Switch Link)”。如果交换机支持这些规格,那么用户就能够高效率地构筑横跨多台交换机的VLAN。
另外,汇聚链路上流通着多个VLAN的数据,自然负载较重。因此,在设定汇聚链接时,有一个前提就是必须支持100Mbps以上的传输速度。
另外,默认条件下,汇聚链接会转发交换机上存在的所有VLAN的数据。换一个角度看,可以认为汇聚链接(端口)同时属于交换机上所有的VLAN。由于实际应用中很可能并不需要转发所有VLAN的数据,因此为了减轻交换机的负载、也为了减少对带宽的浪费,我们可以通过用户设定限制能够经由汇聚链路互联的VLAN。
汇聚链接的具体操作:
VLAN属于OSI模型的数据链路层,物理层发出的原始帧只有MAC头,VLAN将相应信息(4个字节)插入原始帧中:
- Type字段:为0x8100,表示接下来字段为IEEE802.1Q标记。
- PRI(Priority):为了实现第2层的QOS,802.1p优先级字段,其长度是3比特。
- CFI(Canonical Format Identifier,规范格式标记符):用于以太网和令牌环之间的兼容,其长度为1比特。
- VLAN ID:用于区分链路上的不同VLAN,其长度为12比特。
总结:由上述可知:
- VLAN属于二层数据链路层,
- 同一个VLAN下的主机可能来自不同交换机的LAN端口,
- 不同VLAN间无法通信,想要VLAN间通信必须借助第三层网络层(路由)
VLAN又有
- 基于IP的VLAN
- 基于MAC的VLAN
- 基于 用户的VLAN
现在的宽带路由器=路由+交换机
VLAN间路由:
上面详细讲述了VLAN的概念,VLAN属于数据链路层。那么为什么VLAN之间必须借助路由或三层交换机才能相互通信呢;(ps:路由不等于三层交换机)
在LAN内的通信,必须在数据帧头中指定通信目标的MAC地址。而为了获取MAC地址,TCP/IP协议下使用的是ARP。ARP解析MAC地址的方法,则是通过广播。也就是说,如果广播报文无法到达,那么就无从解析MAC地址,亦即无法直接通信。
假设有ABCD主机,MAC地址、IP地址、VLAN情况如下:
端口
MAC地址
VLAN
IP 1
A
1
192.168.1.1 2
B
1
192.168.1.2 3
C
2
192.168.2.1 4
D
2
192.168.2.2 5
-
-
6
R
汇聚
如果A主机要访问另一个VLAN下的C主机,其步骤如下(如上右图):
- A要访问C主机的数据帧包括C的IP,检查C的IP发现C不在同一个VLAN下
- 那么就要先使用ARP协议获取汇聚链接的MAC,即路由器的MAC地址,所以发出的数据帧1为(R的MAC地址+C的IP地址)
- 交换机端口1收到数据帧就检测与端口1处于同一VLAN的表象(汇聚链接6同属于VLAN1、VLAN2),那么数据帧1送到了汇聚节点即端口6
- 由于端口6是汇聚节点,因此会在数据帧中插入VLAN1的识别信息,形成数据帧2,并送到路由器端口。
- 路由器端口下有负责红色VLAN的子端口和蓝色VLAN的子端口,所以数据帧2进入路由器负责红色VLAN的子端口
- 负责红色VLAN子端口根据数据帧2的IP信息,找到目标在蓝色VLAN下,因此将数据帧 2的MAC地址(R)改写成地址C的MAC地址,形成数据帧3
- 交换机收到③的数据帧后,根据VLAN标识信息从MAC地址列表中检索属于蓝色VLAN的表项。由于通信目标——计算机C连接在端口3上、且端口3为普通的访问链接,因此交换机会将数据帧去除VLAN识别信息后(数据帧④)转发给端口3,最终计算机C才能成功地收到这个数据帧。
所以VLAN间路由其实就是用汇聚节点的MAC代替目标点的MAC地址(应为目标端和源端不在一个广播域,无法通过ARP得到MAC地址),然后通过路由找到目标的MAC地址,进行转发数据包。
VLAN 三层交换机(本质上是带有路由功能的(二层)交换机):
。。。
详见:https://blog.csdn.net/zjkc050818/article/details/80663886 第六节VLAN间通信
域名系统DNS(Domain Name System)是因特网使用的命名系统,用来把便于人们使用的机器名字转换成为IP地址。域名系统其实就是名字系统。为什么不叫“名字”而叫“域名”呢?这是因为在这种因特网的命名系统中使用了许多的“域(domain)”,因此就出现了“域名”这个名词。“域名系统”明确地指明这种系统是应用在因特网中。
我们都知道,IP地址是由32位的二进制数字组成的。用户与因特网上某台主机通信时,显然不愿意使用很难记忆的长达32位的二进制主机地址。即使是点分十进制IP地址也并不太容易记忆。相反,大家愿意使用比较容易记忆的主机名字。但是,机器在处理IP数据报时,并不是使用域名而是使用IP地址。这是因为IP地址长度固定,而域名的长度不固定,机器处理起来比较困难。
详细参考:DNS域名解析
域名到IP地址的解析过程的要点如下:
- 当某一个应用需要把主机名解析为IP地址时,该应用进程就调用解析程序,并称为DNS的一个客户,把待解析的域名放在DNS请求报文中,以UDP用户数据报方式发给本地域名服务器。
- 本地域名服务器在查找域名后,把对应的IP地址放在回答报文中返回。应用程序获得目的主机的IP地址后即可进行通信。
- 若本地域名服务器不能回答该请求,则此域名服务器就暂时称为DNS的另一个客户,并向其他域名服务器发出查询请求。
一、局域网(Local Area Network,LAN)
是指在某一区域内由多台计算机互联成的计算机组。局域网是封闭型的可以实现文件管理、打印机共享、电子邮件等功能。
二、广域网 (Wide Area Network,WAN)
是一种跨越大的、地域性的计算机网络的集合。通常跨越省、市,甚至一个国家。
三、路由器的 WAN 口和 LAN 口
现在的宽带路由器实际上是路由 + 交换机的一体结构,我们可以把它当成是两台设备。
WAN:接外部 IP 地址用,通常指的是出口,转发来自内部 LAN 接口的 IP 数据包。
LAN:接内部 IP 地址用,LAN 内部是交换机。我们可以不连接 WAN 口,把路由器当做普通交换机来使用。
四、无线局域网(Wireless LAN, WLAN)
WLAN 利用电磁波在空气中发送和接受数据,而无需线缆介质。传输速率达到 11Mbps,距离至 20km 以上。
WIFI 是实现无线组网的一种协议,WIFI 是 WLAN 的一个标准。WIFI 网络工作在 2.4G 或 5G 的频段。另外 3G/4G 也属于无线上网,但协议都不一样!
五、虚拟局域网(Virtual Local Area Network,VLAN)
是指网络中的站点不拘泥于所处的物理位置,根据需要划分不同的逻辑子网。
例如位于不同楼层的用户加入不同的虚拟局域网:1 楼划分为 10.221.1.0 网段,2 楼划分为 10.221.2.0 网段等。
六、虚拟专用网络(Virtual Private Network,VPN)
虚拟专用网络功能是:在公用网络上建立专用网络,进行加密通讯。VPN 网关通过对数据包的加密和数据包目标地址的转换实现远程访问。
企业网络中要远程访问,传统的方法是租用 DDN(数字数据网)专线或帧中继,导致高昂的费用。内网中架设一台 VPN 服务器,被广泛企业采用。
当我们联上美国的 VPN 服务器,所有数据传输都经过这台美国服务器,相当于你人在美国一样。
QinQ原理及技术(QinQ白皮书)
这里先回顾一下带参数运行的主函数是怎样的:
int main(int argc,char *argv[])
例如:
./tcpip_clients "hello" "world"//终端运行
//输出如下:
argc=3
argv[0]=./tcpip_clients
argv[1]=hello
argv[2]=world
第一个数值为 运行的命令,第二个之后为 输入的参数
- 服务器端:
- 调用socket函数创建套接字.
- 调用blind函数分配IP地址和端口号.
- 调用listen函数将套接字转换为可接受状态.
- 调用accept函数受理连接请求.如果在没有连接的情况下调用该函数,不会有返回,直到有连接请求为止.
- write函数用于传输数据.
结构体需要头文件#include
struct sockaddr_in
{
short sin_family;//Address family一般来说AF_INET(地址族)PF_INET(协议族)
unsigned short sin_port;/*Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)*/
struct in_addr sin_addr;/*IP address in network byte order(Internet address)*/
unsigned char sin_zero[8];/*Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐*/
};
int socket(int domain, int type, int protocol);//初始化套接字函数
- domain:协议域,又称协议族;例如:AF 表示ADDRESS FAMILY 地址族 PF 表示PROTOCOL FAMILY 协议族 这里为了规范用PF_INET表示tcpip协议(其实一般AF_INET == PF_INET)
- type:指定Socket类型。例如:流式Socket(SOCK_STREAM)是一种面向连接的Socket,针对于面向连接的TCP服务应用。数据报式Socket(SOCK_DGRAM)是一种无连接的Socket,对应于无连接的UDP服务应用。
- protocol:指定协议。例如:参数为0的时候,是自动选择模式
因为本地主机存储有大端小端之分,而网络统一定义为大端网络字节序,所以需要进行转换.
/**** * sockfd:标识一未捆绑套接口的描述字。 * my_addr:赋予套接口的地址。sockaddr结构定义如下: * struct sockaddr{ * u_short sa_family; * char sa_data[14]; * }; * addrlen:name名字的长度。 * 返回值:成功返回0,失败返回-1. ****/
- 参数列表中,sockfd 表示已经建立的socket编号(描述符);
- my_addr 是一个指向sockaddr结构体类型的指针;
- 参数addrlen表示my_addr结构的长度,可以用sizeof操作符获得。
服务器端函数:
serve_hello.c
#include
#include
#include
#include
#include
#include
int main(int argc,char *argv[])
{
int serv_sock;//服务器端的套接字
int clnt_sock;
struct sockaddr_in serv_addr;
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size;
char message[]="Hello world!";
//错误处理
if(argc!=2)
{
printf("Usage:%s\n",argv[0]);
exit(1);
}
serv_sock=socket(PF_INET,SOCK_STREAM,0);
if(serv_sock==-1) printf("socker_error()");
//初始化结构体
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);//32位的主机字节序转化为网络字节序 host to online long
serv_addr.sin_port=htons(atoi(argv[1]));//16位的主机字节序转化为网络字节序 host to online short
//分配IP和端口号
if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)
printf("bind() error");
if(listen(serv_sock,5)==-1)//第一个参数用于标识一个已捆绑未连接套接口的描述字,第二个参数表示等待连接队列的最大长度。
printf("listen error");
clnt_addr_size=sizeof(clnt_addr);
clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_addr,&clnt_addr_size);//accept函数受理连接请求;如果没有请求,那么不会返回,直到有连接
if(clnt_sock==-1)
printf("accept() error");
write(clnt_sock,message,sizeof(message));
//关闭
close(clnt_sock);//注意关闭的输入参数是描述字
close(serv_sock);
return 0;
}
客户端步骤:
- 创建套接字,初始套接字不区分服务端和客户端(如果调用connect使套接字成为客户端套接字,调佣bind,listen函数就成为服务器端套接字)
- 调用connect函数向服务器发送连接请求
//服务其发送字符串,客户端的接受程序如下
#include
#include //atoi 函数
#include
#include //包含sockaddr_in结构体
#include
#include
#include
int main(int argc,char *argv[])
{
int sock;
struct sockaddr_in serv_addr;
char message[30];
int str_len;
if(argc!=3)
{
printf("Usage:%s ",argv[0]);
}
sock=socket(PF_INET,SOCK_STREAM,0);
if(sock==-1)
printf("error socket()\n");
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=inet_addr(argv[1]);//将字符串转换为32位二进制网络字节序的IPV4地址
serv_addr.sin_port=htons(atoi(argv[2]));//atoi函数,把字符串转换为整形,再将16位整形字节转化为网络字节序
if(connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)
printf("error connect()\n");
str_len=read(sock,message,sizeof(message));//使用sizeof而不用strlen(),是因为message初始为空
if(str_len==-1)
printf("error read()\n");
printf("Message from server: %s\n",message);
close(sock);
return 0;
}
最后运行服务器端和客户端:
服务器端:
./tcpip_server 9190
客户端:
./tcpip_clients 127.0.0.1 9190 //本机 Ip 和端口号,如果使用其他计算机作为服务器,输入该计算机的IP
最终客户端输出: