三阶段复习

6.21 静态库与动态库

库有两种:静态库(.a、.lib)和动态库(.so、.dll)。所谓静态、动态是指链接。静态库在链接期把整个库文件都拷贝到可执行文件中,而动态库在链接期只是把索引文件拷贝到可执行文件中,在运行时才调用动态库。(通过索引文件找到动态库文件)

静态库与动态库优缺点

静态库:

优点:由于静态库是将整个库文件都拷贝到可执行文件中,所以调用速度相对快。

缺点:1.浪费内存空间(多个应用程序依赖同一个静态库时,多个程序运行时,会在内存中拷贝多份静态库)2.对程序的更新、部署、发布比较麻烦,静态库修改后,需要重新编译整个可执行文件,重新安装。

动态库:

优点:1.节省内存空间(多个应用程序依赖同一个动态库时,多个程序运行时,只会在内存中拷贝一份动态库)2.对程序的更新、部署、发布简单,动态库修改后,只需要重新编译动态库即可,不需要重新编译整个可执行文件。

缺点:调用速度比静态库慢,移植的时候需要同时移植可执行文件和动态库。

6.22 网络基本模型

七层网络模型

应用层

用来提供应用程序之间的信息通信的(例:淘宝、qq、微信等)

涉及到的协议(protocol):

HTTP

超文本传输协议,TCP端口 80

HTTPS

超文本传输安全协议,TCP端口 443,相比HTTP多了SSL加密方式,更安全

FTP

File Transfer Protocol,文件传输协议,TCP端口 23

DNS

Domain Name System,域名系统,TCP/UDP端口 53,将域名解析成IP地址

DHCP

Dynamic Host Configuration Protocol,动态主机配置协议,UDP端口 68,动态获取网络配置

Telnet

远程终端协议,TCP端口 23,远程控制Web服务器

SMTP

简单邮件传输协议,TCP端口 25,提供可靠且有效的电子邮件传输的协议

SSH

Secure Shell,安全外壳,是一种网络安全协议,通过加密和认证机制实现安全的访问和文件传输等业务

表示层

提供数据格式转换的,主要用来数据加解密、数据解压缩。图片/视频编解码。

几个注意点:

数据在发送之前要用一定的加密方法进行加密

想要使数据发送的快,就要对数据进行压缩,这样也会使网络更通畅

实时传输(直播)一般都需要视频编解码,不然延迟会高

会话层

session会话管理、服务器验证用户登录、断点续传。

session会话管理 例:如果我们在登录一个网站之后,浏览页面过程中要跳转网页,那么不需要重复登录,靠的就是session会话管理,它保证了用户在同一个网站中不管多少次的页面跳转,都不需要重新登录

断点续传 例:在下载文件中途有网络波动,在一段时间内网络恢复了,那么会接着下载

传输层

(操作系统级别的)

TCP

TCP,传输控制协议,提供一种面向连接的、可靠的、基于字节流的传输层通信协议,有流量控制和差错控制,使用TCP协议的应用比如邮件的接收和发送、文件传输、远端登录。需要数据稳定和完整性比较高的场景多使用TCP协议。

UDP

UDP,用户数据报协议,提供一种无连接的、高效率、低可靠性的数据传输服务,使用UDP协议的应用比如音视频聊天、在线游戏王者荣耀等、工业物联网数据传输等。需要数据时效性比较高的场景多使用UDP协议。

线程
端口

注:一个应用程序可以使用多个端口,但是一个端口只能被一个应用程序使用

socket(套接字)

是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

其中TCP/IP协议不是TCP和IP协议,而是代表一个协议族

数据传输一定需要的两个信息:

IP地址和端口号,IP地址能够保证数据到达目标设备,端口号保证数据能从设备到应用程序

网络层

网络层不止有协议,还有实际的设备,比如路由器

协议有IP、ARP、RARP、防火墙

网络层主要做的是寻址和路由选择

可以通过ipconfig查看自己的ip地址

数据链路层

交换机、网卡(MAC地址)

设备有几个网卡,就有几个ip地址

交换机只能进行单纯的转发,大家要连到同一台交换机上,而路由器除了支持数据转发外还支持路由寻址

物理层

光纤、网线

把数字信号转换为电信号或光信号进行比特流传输

TCP/IP事实标准网络模型

包含应用层、传输层、网络层、物理层

C/S架构与B/S架构对比

C/S(Client/Server)

客户端和服务端可以使用任意协议(常用TCP、UDP)连接
需要指定客户端,每个应用程序都有指定的客户端 

B/S(Browser/Server)

客户端不受限制
浏览器和服务端必须使用 http 或 https 协议

6.23 实战训练:C/S架构

基于UDP

服务端代码

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<iostream>
using namespace std;
#include<WinSock2.h>

#pragma comment(lib,"Ws2_32.lib")

int main()
{
	//加载库
	WORD wVersion = MAKEWORD(2, 2);/*创建版本号 2.2 版本*/
	WSAData data;
	int err = WSAStartup(wVersion,&data);
	if (0 != err)
	{
		printf("WSAStartup failed with error: %d\n", err);
		return 1;
	}
	if (2 != LOBYTE(data.wVersion) || 2 != HIBYTE(data.wVersion))/*判断加载的版本号是否正确*/
	{
		printf("Could not find a usable version of Winsock.dll\n");
		WSACleanup();
		return 1;
	}
	else
		printf("The Winsock 2.2 dll was found okay\n");
	//创建套接字
	SOCKET sock = socket(AF_INET/*IPV4版本*/, SOCK_DGRAM/*UDP报文段*/, IPPROTO_UDP/*协议类型为UDP*/);
	if (INVALID_SOCKET == sock)
	{
		printf("sock failed with error: %d\n",WSAGetLastError());
		WSACleanup();
		return 1;
	}
	else
		printf("sock success\n");

	//绑定ip和端口号
	/*这里相当于初始化结构体*/
	struct sockaddr_in addrServer;
	addrServer.sin_family = AF_INET;/*IPV4类型*/
	addrServer.sin_addr.S_un.S_addr = INADDR_ANY;/*任意网卡*/
	addrServer.sin_port = htons(12345);/*htons 转换成网络字节序*/
	/*这里是绑定*/
	err = bind(sock,(sockaddr*)&addrServer,sizeof(addrServer));
	if (SOCKET_ERROR == err)
	{
		printf("bind failed with error: %d\n", WSAGetLastError());
		//关闭套接字
		closesocket(sock);
		//卸载库
		WSACleanup();
		return 1;
	}
	else
		printf("bind success\n");

	//收发数据
	int recvNum = 0;
	char recvBuf[1024] = "";
	int sendNum = 0;
	char sendBuf[1024] = "";

	struct sockaddr_in addrClient;
	int addrClientSize = sizeof(addrClient);
	while (true)
	{
		//接收消息
		recvNum = recvfrom(sock, recvBuf, sizeof(recvBuf), 0, (sockaddr*)&addrClient, &addrClientSize);
		if (recvNum > 0)
		{
			//打印收到的数据
			char ip[100] = "";
			cout << "ip:" << inet_ntoa(addrClient.sin_addr) << ":" << recvBuf << endl;
			/*inet_addr 将点分十进制ip地址转换为ulong类型,inet_ntoa 将ulong类型转换为点分十进制*/
		}
		else if (recvNum == 0)
		{
			cout << "connect closed,please try again..." << endl;
		}
		else
			break;

		//发送消息
		gets_s(sendBuf);//读取数据
		/*sendto 给谁发信息就绑定谁的地址信息*/
		sendNum = sendto(sock,sendBuf, sizeof(sendBuf), 0, (sockaddr*)&addrClient, addrClientSize);
		if (SOCKET_ERROR == sendNum)
		{
			cout << "send error" << endl;
			break;
		}
	}

	//关闭套接字
	closesocket(sock);
	//卸载库
	WSACleanup();

	return 0;
}

客户端代码

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<iostream>
using namespace std;
#include<WinSock2.h>

#pragma comment(lib,"Ws2_32.lib")

int main()
{
	//加载库
	WORD wVersion = MAKEWORD(2, 2);/*创建版本号 2.2 版本*/
	WSAData data;
	int err = WSAStartup(wVersion, &data);
	if (0 != err)
	{
		printf("WSAStartup failed with error: %d\n", err);
		return 1;
	}
	if (2 != LOBYTE(data.wVersion) || 2 != HIBYTE(data.wVersion))/*判断加载的版本号是否正确*/
	{
		printf("Could not find a usable version of Winsock.dll\n");
		WSACleanup();
		return 1;
	}
	else
		printf("The Winsock 2.2 dll was found okay\n");
	//创建套接字
	SOCKET sock = socket(AF_INET/*IPV4版本*/, SOCK_DGRAM/*UDP报文段*/, IPPROTO_UDP/*协议类型为UDP*/);
	if (INVALID_SOCKET == sock)
	{
		printf("sock failed with error: %d\n", WSAGetLastError());
		WSACleanup();
		return 1;
	}
	else
		printf("sock success\n");


	//接收端的地址信息
	struct sockaddr_in addrServer;
	addrServer.sin_family = AF_INET;
	addrServer.sin_port = htons(12345);  //htons转换成网络字节序
	addrServer.sin_addr.S_un.S_addr = inet_addr("192.168.246.1");

	//收发数据
	int recvNum = 0;
	char recvBuf[1024] = "";
	int sendNum = 0;
	char sendBuf[1024] = "";

	while (true)
	{
		//发送消息
		gets_s(sendBuf);//读取数据
		sendNum = sendto(sock, sendBuf, sizeof(sendBuf), 0, (sockaddr*)&addrServer, sizeof(addrServer));
		if (SOCKET_ERROR == sendNum)
		{
			cout << "send error" << endl;
			break;
		}

		//接收消息
		recvNum = recvfrom(sock, recvBuf, sizeof(recvBuf), 0, NULL/*传出参数,后续用不上addrServer了,所以可以传NULL,后面的NULL同理*/, NULL);
		if (recvNum > 0)
		{
			//打印收到的数据
			cout << "server say:" << recvBuf << endl;
		}
		else if (recvNum == 0)
		{
			cout << "connect closed,please try again..." << endl;
		}
		else
			break;
	}

	//关闭套接字
	closesocket(sock);
	//卸载库
	WSACleanup();

	return 0;
}

运行结果

可见,服务端和客户端可以实现一来一回收发数据。

基于TCP

服务端代码

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<iostream>
#include<WinSock2.h>
using namespace std;

#pragma comment(lib,"Ws2_32.lib")

int main()
{
	//加载库
	WORD wVersion = MAKEWORD(2, 2);
	WSAData data;
	int err = WSAStartup(wVersion, &data);
	if (err != 0)
	{
		cout << "WSAStartup error" << endl;
		return 1;
	}
	if (2 != LOBYTE(data.wVersion) || 2 != HIBYTE(data.wVersion))
	{
		cout << "WSAStartup fail" << endl;
		WSACleanup();
		return 1;
	}
	else
	{
		cout << "WSAStartup success" << endl;
	}
	//创建套接字
	SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//TCP协议
	if (INVALID_SOCKET == sock)
	{
		cout << "sock error" << endl;
	}
	else
	{
		cout << "sock success" << endl;
	}

	//绑定IP
	struct sockaddr_in addrServer;
	addrServer.sin_family = AF_INET;
	addrServer.sin_port = htons(12345);
	addrServer.sin_addr.S_un.S_addr = INADDR_ANY;//绑定任意网卡
	err = bind(sock, (sockaddr*)&addrServer, sizeof(addrServer));
	if (SOCKET_ERROR == err)
	{
		cout << "bind error" << endl;
	}

	//监听
	err = listen(sock, 10);
	if (err == SOCKET_ERROR)
	{
		cout << "listen error" << endl;
	}
	else
	{
		cout << "listen success" << endl;
	}

	struct sockaddr_in addrClient;
	int addrClientSize = sizeof(addrClient);

	//接受连接
	SOCKET m_sock = accept(sock, (sockaddr*)&addrClient, &addrClientSize);
	if (INVALID_SOCKET == m_sock)
	{
		cout << "accept fail" << endl;
	}
	else
	{
		cout << "accept success" << endl;
	}

	//收发数据
	int sendNum = 0;
	char sendBuf[1024] = "";
	int recvNum = 0;
	char recvBuf[1024] = "";

	
	while (1)
	{
		//接收数据
		recvNum = recv(m_sock, recvBuf, sizeof(recvBuf), 0);
		if (recvNum > 0)
		{
			//打印出客户端的IP地址
			char ip[100] = "";
			cout << "ip:" << inet_ntoa(addrClient.sin_addr) << ":" << recvBuf << endl;
		}
		else if (recvNum == 0)
		{
			cout << "connect closed" << endl;
		}
		else
		{
			break;
		}
		//发送数据
		gets_s(sendBuf);
		sendNum = send(m_sock, sendBuf, sizeof(sendBuf), 0);
		if (SOCKET_ERROR == sendNum)
		{
			cout << "send error" << WSAGetLastError() << endl;
		}
	}

	closesocket(m_sock);

	WSACleanup();
}

客户端代码

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<iostream>
#include<WinSock2.h>
using namespace std;

#pragma comment(lib,"Ws2_32.lib")

int main()
{
	//加载库
	WORD wVersion = MAKEWORD(2, 2);
	WSAData data;
	int err = WSAStartup(wVersion, &data);
	if (0 != err)
	{
		cout << "WSAStartup error" << endl;
	}
	if (2 != LOBYTE(data.wVersion) || 2 != HIBYTE(data.wVersion))
	{
		cout << "WSAStartup fail" << endl;
	}
	else
	{
		cout << "WSAStartup success" << endl;
	}
	//创建套接字
	SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//TCP协议
	if (INVALID_SOCKET == sock)
	{
		cout << "socket error" << WSAGetLastError() << endl;
	}
	else
	{
		cout << "socket success" << endl;
	}
	//连接
	struct sockaddr_in addrServer;
	addrServer.sin_family = AF_INET;
	addrServer.sin_port = htons(12345);
	addrServer.sin_addr.S_un.S_addr = inet_addr("192.168.248.1");
	err = connect(sock,(sockaddr*)&addrServer,sizeof(addrServer));
	if (SOCKET_ERROR == err)
	{
		cout << "connect error" << endl;
	}
	else
	{
		cout << "connect success" << endl;
	}

	//收发数据
	int sendNum = 0;
	char sendBuf[1024] = "";
	int recvNum = 0;
	char recvBuf[1024] = "";
	while (1)
	{
		//发送数据
		gets_s(sendBuf);
		sendNum = send(sock, sendBuf, sizeof(sendBuf), 0);
		if (SOCKET_ERROR == sendNum)
		{
			cout << "send error:" << WSAGetLastError() << endl;
			break;
		}

		//接收数据
		recvNum = recv(sock, recvBuf, sizeof(recvBuf), 0);
		if (recvNum > 0)
		{
			cout << "server say:" << recvBuf << endl;
		}
		else if (recvNum == 0)
		{
			cout << "connect closed" << endl;
		}
		else
		{
			break;
		}
	}

	closesocket(sock);

	WSACleanup();

	return 0;
}

运行结果

 可见,服务端和客户端可以实现一来一回收发数据。

常见报错原因

无法解析的外部符号:没有添加依赖库

未定义标识符:没有头文件

6.24 ARP协议和DNS协议

数据包在传输过程中的变化

如图所示,发送方的原数据最开始是在应用层,然后在传输层加上UDP头,在网络层加上IP头,在物理层加上帧头、帧尾和CRC校验,然后接收方会逐层校验拆包,直到在应用层得到原数据。

单播、组播和广播

单播就是一对一的,广播就是看目标设备在不在范围内,组播是看目标设备是不是要发送的设备

ARP协议

地址解析协议,是根据IP地址获取MAC地址的一个TCP/IP协议,用在双端设备在通讯收发数据之前,如果不知道对端的MAC地址,就可以通过ARP协议来获取

ARP协议是跨数据链路层和网络层的,所以他的上层协议是IP协议

ARP协议实现的过程就是自己设备会发送一个ARP Request的广播,那么当前路由器所连接的所有设备都会收到这个广播,数据包中的目的MAC填0,所有收到这个广播的设备都会看自己的IP是不是这个广播的目IP,如果不是就会将这个包丢弃,如果是就会回复一个ARP Reply的单播,然后就可以开始通信了

 

ARP代理

当发送端广播ARP请求时,本地网络上不会有主机回应(因为IP地址是外网的),此时路由器将会回应该请求,则发送源误认为路由器就是目的主机,会将报文全部转发给它,再由路由器转发报文到外网,则该路由器就被称为ARP代理。

免费ARP

在主机开机配置时,会发送一个目的IP地址为自己IP地址的ARP请求报文,该报文称之为免费ARP,其作用如下:

1.让主机确认本地网络上是否有与自己IP地址相同的主机,若有,则会返回一个错误报文。
2.告诉整个广播域,目前某个IP所对应的MAC地址是什么——这一行为就像是在发宣传单,而宣传单是不需要回应的。若接收主机ARP缓存中本身就有发送源主机的IP-MAC对,则会更新,否则,会缓存发送源的IP-MAC对。

路由数据转发过程

原设备和目的设备不是连接同一个路由器上的,需要经过多个路由器转发才能连接上

DNS协议

域名解析协议,DNS服务器将好记的域名解析成IP地址

过程:将客户端要访问的域名传到本地DNS服务器,如果缓存里有就直接返回IP地址,如果没有就访问DNS根服务器,发现是.com域,那么本地DNS服务器再访问.com域服务器,发现是.163.com域,那么再次访问.163.com域服务器寻找IP地址,最后本地DNS服务器返回客户端一个IP地址。

6.25 子网划分

IP地址分类

子网掩码

又叫网络掩码,地址掩码,子网络遮罩。本机IP地址的二进制与子网掩码的二进制按位与可以得到某主机对外IP地址。

默认子网掩码

想判断自己与他人是否在一个子网内,就用自己的IP地址与子网掩码按位与,看与他人是否相同。

非默认子网掩码

非默认子网掩码中1的个数一定比默认子网多,并且1要连续,这些1是从原来主机号中借来的。

网关

广播地址

用于向网络中的所有设备进行广播。具有正常的网络号部分,而主机号部分全为1的IP地址称之为广播地址。

注:主机号全为0的地址为网关地址,主机号全为1的地址为广播地址。

有限广播地址:指的是32位全为1(即255.255.255.255)的IP地址,用于本网广播。

直接广播地址:正常的广播地址称为直接广播地址,发送直接广播地址数据,会将数据发送给该子网内所有主机。

子网划分常见问题

1.选定的子网掩码将创建多少个子网?

2^x个,其中x是子网掩码借用的主机位数。如:192.168.10.32/28,其中28是网络号的意思。我们知道C类IP默认子网掩码为255.255.255.0,C类IP的网络号应该是24位,故其借用了主机位4位来充当网络位,所以创建的子网个数就是2 ^ 4 = 16。

2.每个子网可包含多少台主机?

2^y-2台,其中y是没有被借用的主机位数。-2是因为主机位全为0的部分是这个子网的网关,全为1的部分是这个子网的广播地址。

3.有哪些子网?

想知道有哪些子网,首先要算出子网的步长(增量)。例:256-192=64,即子网掩码为192时,步长为64。从0开始不断增加,直到到达子网掩码值,中间的结果就是子网,即0,64,128,192。

子网划分练习题

7.2 直接广播与有限广播

将上面的代码服务端变成只接收不发送,客户端变成只发送不接收。

直接广播

客户端添加绑定网卡

	//3.绑定网卡
	struct sockaddr_in addrClient;
	addrClient.sin_family = AF_INET;
	addrClient.sin_addr.S_un.S_addr = inet_addr("192.168.248.1");
	addrClient.sin_port = htons(54321);
	err = bind(sock, (sockaddr*)&addrClient, sizeof(addrClient));
	if (SOCKET_ERROR == err)
	{
		printf("bind failed with error: %d\n", WSAGetLastError());
		//关闭套接字
		closesocket(sock);
		//卸载库
		WSACleanup();
		return 1;
	}
	else
		printf("bind success\n");

 同时,接收端地址变为广播地址

	//接收端的地址信息
	struct sockaddr_in addrServer;
	addrServer.sin_family = AF_INET;
	addrServer.sin_port = htons(12345);  //htons转换成网络字节序
	addrServer.sin_addr.S_un.S_addr = inet_addr("192.168.248.255");//这里变成广播

这样,就可以发送广播了 

 有限广播

有限广播的地址是255.255.255.255,但是我们直接添加

addrServer.sin_addr.S_un.S_addr = inet_addr("255.255.255.255");//有限广播

会报错,错误如下:

这是因为有限广播不能直接用,必须申请权限才能使用

	addrServer.sin_addr.S_un.S_addr = inet_addr("255.255.255.255");//有限广播
	//申请有限广播权限
	bool val = true;
	setsockopt(sock,SOL_SOCKET,SO_BROADCAST,(char*)&val,sizeof(val));

具体参见: setsockopt function (winsock2.h) - Win32 apps | Microsoft Learn

运行结果 

 直接广播与有限广播的区别

直接广播可指定任意广播域(限一个),即一个子网。需要计算直接广播地址(主机号全为1),不需要申请广播权限。

有限广播仅限于本网络,即自己在哪个子网,就只能在哪个子网内发送有限广播。不需要计算有限广播地址(255.255.255.255),需要申请广播权限。

7.17 阻塞与非阻塞

阻塞

当一个进程向另一个进程发送请求时,如果对方没有响应,那么当前进程会一直等待,直到对方返回结果或者超时。

非阻塞

当一个进程向另一个进程发送请求时,即使对方没有响应,当前进程也可以继续执行其他任务,不必等待对方的响应。

socket默认阻塞,以下是设置socket为非阻塞:

	//设置socket为非阻塞
	u_long val = 1;
	ioctlsocket(sock,FIONBIO,&val);

参考:

发送的阻塞和非阻塞

发送阻塞

当发送缓冲区空间不足够大的时候,等到发送缓冲区空间足够大再发送。

发送非阻塞

当发送缓冲区空间不足够大的时候,有多少空间就往里拷贝多少内容,剩下的数据应用程序自己处理。

发送缓冲区和接收缓冲区

每创建一个socket,操作系统就会给这个socket分配一个接收缓冲区和发送缓冲区。

以下是查看接收缓冲区和发送缓冲区大小的方法:

	int recvSize = 0;
	int sendSize = 0;
	int size = sizeof(int);
	getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&recvSize, &size);
	getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&sendSize, &size);
	cout << "recvSize:" << recvSize << ", sendSize:" << sendSize << endl;

输出结果:

单位为字节,可见发送缓存和接收缓存大小均为64K。

7.28 以太网帧结构

  • 25
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值