Python 网络编程 OSI 协议原理、网络通信、DNS 域名解析、socket 套接字、TCP 粘包等教程

本文主要是介绍Python 网络编程 OSI 协议原理、网络通信、DNS 域名解析、socket 套接字、TCP 粘包等教程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

01、互联网的本质介绍

互联网的本质就是一系列的网络协议

然而internet为何物?

其实两台计算机之间通信与两个人打电话之间通信的原理是一样的(中国有很多地区,不同的地区有不同的方言,为了全中国人都可以听懂,大家统一讲普通话)

结论:英语成为世界上所有人通信的统一标准,如果把计算机看成分布于世界各地的人,那么连接两台计算机之间的internet实际上就是一系列统一的标准,这些标准称之为互联网协议,互联网的本质就是一系列的协议,总称为‘互联网协议’(Internet Protocol Suite).

互联网协议的功能:定义计算机如何接入internet,以及接入internet的计算机通信的标准。

02、OSI 七层协议

开放式系统互联通信参考模型(英语:Open System Interconnection Reference Model,缩写为 OSI),简称为OSI模型(OSI model),一种概念模型,由国际标准化组织提出,一个试图使各种计算机在世界范围内互连为网络的标准框架。

互联网协议按照功能不同分为osi七层或tcp/ip五层或tcp/ip四层
在这里插入图片描述
每层运行常见物理设备
在这里插入图片描述
OSI七层协议数据传输的封包与解包过程
在这里插入图片描述
我们将应用层,表示层,会话层并作应用层,从tcp/ip五层协议的角度来阐述每层的由来与功能,搞清楚了每层的主要协议。

就理解了整个互联网通信的原理。

首先,用户感知到的只是最上面一层应用层,自上而下每层都依赖于下一层,所以我们从最下一层开始切入,比较好理解。

每层都运行特定的协议,越往上越靠近用户,越往下越靠近硬件

03、TCP / IP 物理层

物理层

物理层由来:上面提到,孤立的计算机之间要想一起玩,就必须接入internet,言外之意就是计算机之间必须完成组网
在这里插入图片描述
物理层功能:主要是基于电器特性发送高低电压(电信号),高电压对应数字1,低电压对应数字0。

04、TCP / IP 数据链路层

数据链路层由来:单纯的电信号0和1没有任何意义,必须规定电信号多少位一组,每组什么意思。

数据链路层的功能:定义了电信号的分组方式。

以太网协议:

早期的时候各个公司都有自己的分组方式,后来形成了统一的标准,即以太网协议ethernet,ethernet规定:

  • 一组电信号构成一个数据包,叫做‘帧’
  • 每一数据帧分成:报头head和数据data两部分

head包含:(固定18个字节)

发送者/源地址,6个字节,接收者/目标地址,6个字节,数据类型,6个字节,data包含:(最短46字节,最长1500字节)

数据包的具体内容:head长度+data长度 = 最短64字节,最长1518字节,超过最大限制就分片发送。

mac地址:

head中包含的源和目标地址由来:ethernet规定接入internet的设备都必须具备网卡,发送端和接收端的地址便是指网卡的地址,即mac地址

mac地址:每块网卡出厂时都被烧制上一个世界唯一的mac地址,长度为48位2进制,通常由12位16进制数表示(前六位是厂商编号,后六位是流水线号)
在这里插入图片描述
有了mac地址,同一网络内的两台主机就可以通信了(一台主机通过arp协议获取另外一台主机的mac地址)

ethernet采用最原始的方式,广播的方式进行通信,即计算机通信基本靠吼:
在这里插入图片描述

05、TCP / IP 网络层

地址解析协议(Address Resolution Protocol),其基本功能为透过目标设备的IP地址,查询目标设备的MAC地址,以保证通信的顺利进行。它是IPv4中网络层必不可少的协议,不过在IPv6中已不再适用,并被邻居发现协议(NDP)所替代。
在这里插入图片描述
网络层由来:有了ethernet、mac地址、广播的发送方式,世界上的计算机就可以彼此通信了,问题是世界范围的互联网是由一个个彼此隔离的小的局域网组成的,那么如果所有的通信都采用以太网的广播方式,那么一台机器发送的包全世界都会收到。
在这里插入图片描述
上图结论:必须找出一种方法来区分哪些计算机属于同一广播域,哪些不是,如果是就采用广播的方式发送,如果不是,就采用路由的方式(向不同广播域/子网分发数据包),mac地址是无法区分的,它只跟厂商有关。

网络层功能:引入一套新的地址用来区分不同的广播域/子网,这套地址即网络地址。

IP协议:

规定网络地址的协议叫ip协议,它定义的地址称之为ip地址,广泛采用的v4版本即ipv4,它规定网络地址由32位2进制表示范围0.0.0.0-255.255.255.255

一个ip地址通常写成四段十进制数,例:172.16.10.1

IP地址分成两部分

网络部分:标识子网
主机部分:标识主机

注意:单纯的ip地址段只是标识了ip地址的种类,从网络部分或主机部分都无法辨识一个ip所处的子网

例:172.16.10.1与172.16.10.2并不能确定二者处于同一子网

子网掩码

所谓”子网掩码”,就是表示子网络特征的一个参数。它在形式上等同于IP地址,也是一个32位二进制数字,它的网络部分全部为1,主机部分全部为0。

比如,IP地址172.16.10.1,如果已知网络部分是前24位,主机部分是后8位,那么子网络掩码就是11111111.11111111.11111111.00000000,写成十进制就是255.255.255.0。

知道”子网掩码”,我们就能判断,任意两个IP地址是否处在同一个子网络。方法是将两个IP地址与子网掩码分别进行AND运算(两个数位都为1,运算结果为1,否则为0),然后比较结果是否相同,如果是的话,就表明它们在同一个子网络中,否则就不是。

比如,已知IP地址172.16.10.1和172.16.10.2的子网掩码都是255.255.255.0,请问它们是否在同一个子网络?

两者与子网掩码分别进行AND运算:

172.16.10.110101100.00010000.00001010.000000001255255.255.255.0:11111111.11111111.11111111.00000000AND运算得网络地址结果:10101100.00010000.00001010.000000001->172.16.10.0
172.16.10.210101100.00010000.00001010.000000010255255.255.255.0:11111111.11111111.11111111.00000000AND运算得网络地址结果:10101100.00010000.00001010.000000001->172.16.10.0

结果都是172.16.10.0,因此它们在同一个子网络。

总结一下,IP协议的作用主要有两个,一个是为每一台计算机分配IP地址,另一个是确定哪些地址在同一个子网络。

ip数据包

ip数据包也分为head和data部分,无须为ip包定义单独的栏位,直接放入以太网包的data部分:

head:长度为20到60字节

data:最长为65,515字节。

而以太网数据包的数据部分,最长只有1500字节。因此,如果IP数据包超过了1500字节,它就需要分割成几个以太网数据包,分开发送了。
在这里插入图片描述
ARP协议

arp协议由来:计算机通信基本靠吼,即广播的方式,所有上层的包到最后都要封装上以太网头,然后通过以太网协议发送,在谈及以太网协议时候,我门了解到通信是基于mac的广播方式实现,计算机在发包时,获取自身的mac是容易的,如何获取目标主机的mac,就需要通过arp协议。

arp协议功能:广播的方式发送数据包,获取目标主机的mac地址。

协议工作方式

一:首先通过ip地址和子网掩码区分出自己所处的子网
在这里插入图片描述
二:分析172.16.10.10/24与172.16.10.11/24处于同一网络(如果不是同一网络,那么下表中目标ip为172.16.10.1,通过arp获取的是网关的mac)
在这里插入图片描述
三:这个包会以广播的方式在发送端所处的自网内传输,所有主机接收后拆开包,发现目标ip为自己的,就响应,返回自己的mac

06、TCP / IP 传输层

从字面意义上讲,有人可能会认为 TCP/IP 是指 TCP 和 IP 两种协议。实际生活当中有时也确实就是指这两种协议。然而在很多情况下,它只是利用 IP 进行通信时所必须用到的协议群的统称。具体来说,IP 或 ICMP、TCP 或 UDP、TELNET 或 FTP、以及 HTTP 等都属于 TCP/IP 协议。他们与 TCP 或 IP 的关系紧密,是互联网必不可少的组成部分。TCP/IP 一词泛指这些协议,因此,有时也称 TCP/IP 为网际协议群。

互联网进行通信时,需要相应的网络协议,TCP/IP 原本就是为使用互联网而开发制定的协议族。因此,互联网的协议就是 TCP/IP,TCP/IP 就是互联网的协议。

包、帧、数据包、段、消息

以上五个术语都用来表述数据的单位,大致区分如下:

  1. 包可以说是全能性术语;
  2. 帧用于表示数据链路层中包的单位;
  3. 数据包是 IP 和 UDP 等网络层以上的分层中包的单位;
  4. 段则表示 TCP 数据流中的信息;
  5. 消息是指应用协议中数据的单位。

每个分层中,都会对所发送的数据附加一个首部,在这个首部中包含了该层必要的信息,如发送的目标地址以及协议相关信息。通常,为协议提供的信息为包首部,所要发送的内容为数据。在下一层的角度看,从上一层收到的包全部都被认为是本层的数据。
在这里插入图片描述
网络中传输的数据包由两部分组成:一部分是协议所要用到的首部,另一部分是上一层传过来的数据。首部的结构由协议的具体规范详细定义。在数据包的首部,明确标明了协议应该如何读取数据。反过来说,看到首部,也就能够了解该协议必要的信息以及所要处理的数据。包首部就像协议的脸。

数据处理流程
在这里插入图片描述
① 应用程序处理

首先应用程序会进行编码处理,这些编码相当于 OSI 的表示层功能;

编码转化后,邮件不一定马上被发送出去,这种何时建立通信连接何时发送数据的管理功能,相当于 OSI 的会话层功能。

② TCP 模块的处理

TCP 根据应用的指示,负责建立连接、发送数据以及断开连接。TCP 提供将应用层发来的数据顺利发送至对端的可靠传输。为了实现这一功能,需要在应用层数据的前端附加一个 TCP 首部。

③ IP 模块的处理

IP 将 TCP 传过来的 TCP 首部和 TCP 数据合起来当做自己的数据,并在 TCP 首部的前端加上自己的 IP 首部。IP 包生成后,参考路由控制表决定接受此 IP 包的路由或主机。

④ 网络接口(以太网驱动)的处理

从 IP 传过来的 IP 包对于以太网来说就是数据。给这些数据附加上以太网首部并进行发送处理,生成的以太网数据包将通过物理层传输给接收端。

⑤ 网络接口(以太网驱动)的处理

主机收到以太网包后,首先从以太网包首部找到 MAC 地址判断是否为发送给自己的包,若不是则丢弃数据。

如果是发送给自己的包,则从以太网包首部中的类型确定数据类型,再传给相应的模块,如 IP、ARP 等。这里的例子则是 IP 。

⑥ IP 模块的处理

IP 模块接收到 数据后也做类似的处理。从包首部中判断此 IP 地址是否与自己的 IP 地址匹配,如果匹配则根据首部的协议类型将数据发送给对应的模块,如 TCP、UDP。这里的例子则是 TCP。

另外吗,对于有路由器的情况,接收端地址往往不是自己的地址,此时,需要借助路由控制表,在调查应该送往的主机或路由器之后再进行转发数据。

⑦ TCP 模块的处理

在 TCP 模块中,首先会计算一下校验和,判断数据是否被破坏。然后检查是否在按照序号接收数据。***检查端口号,确定具体的应用程序。数据被完整地接收以后,会传给由端口号识别的应用程序。

⑧ 应用程序的处理

接收端应用程序会直接接收发送端发送的数据。通过解析数据,展示相应的内容。

传输层中的 TCP 和 UDP

TCP/IP 中有两个具有代表性的传输层协议,分别是 TCP 和 UDP。

TCP 是面向连接的、可靠的流协议。流就是指不间断的数据结构,当应用程序采用 TCP 发送消息时,虽然可以保证发送的顺序,但还是犹如没有任何间隔的数据流发送给接收端。TCP 为提供可靠性传输,实行“顺序控制”或“重发控制”机制。此外还具备“流控制(流量控制)”、“拥塞控制”、提高网络利用率等众多功能。
在这里插入图片描述
UDP 是不具有可靠性的数据报协议。细微的处理它会交给上层的应用去完成。在 UDP 的情况下,虽然可以确保发送消息的大小,却不能保证消息一定会到达。因此,应用有时会根据自己的需要进行重发处理。

TCP 和 UDP 的优缺点无法简单地、绝对地去做比较:TCP 用于在传输层有必要实现可靠传输的情况;而在一方面,UDP 主要用于那些对高速传输和实时性有较高要求的通信或广播通信。TCP 和 UDP 应该根据应用的目的按需使用。

UDP协议:

不可靠传输,”报头”部分一共只有8个字节,总长度不超过65,535字节,正好放进一个IP数据包。

传输层TCP 端口的介绍

传输层的由来:网络层的ip帮我们区分子网,以太网层的mac帮我们找到主机,然后大家使用的都是应用程序,你的电脑上可能同时开启qq,暴风影音,等多个应用程序,

那么我们通过ip和mac找到了一台特定的主机,如何标识这台主机上的应用程序,答案就是端口,端口即应用程序与网卡关联的编号。

传输层功能:建立端口到端口的通信

补充:端口范围0-65535,0-1023为系统占用端口

07、TCP / IP 应用层

应用层由来:用户使用的都是应用程序,均工作于应用层,互联网是开发的,大家都可以开发自己的应用程序,数据多种多样,必须规定好数据的组织形式 。

应用层功能:规定应用程序的数据格式。

例:TCP协议可以为各种各样的程序传递数据,比如Email、WWW、FTP等等。那么,必须有不同协议规定电子邮件、网页、FTP数据的格式,这些应用程序协议就构成了”应用层”。

套接字 Socket

应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要 通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字 (Socket)的接口,区分不同应用程序进程间的网络通信和连接。

生成套接字,主要有3个参数:通信的目的IP地址、使用的传输 层协议(TCP或UDP)和使用的端口号。Socket原意是“插座”。通过将这3个参数结合起来,与一个“插座”Socket绑定,应用层就可以和传输 层通过套接字接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。

我们知道两个进程如果需要进行通讯最基本的一个前提能能够唯一的标示一个进程,在本地进程通讯中我们可以使用PID来唯一标示一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大,这时候我们需要另辟它径了,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。

能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,什么是socket呢?我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。
在这里插入图片描述
socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。
在这里插入图片描述

08、IP 地址详细介绍

什么是IP地址?

IP地址在网络中用于标识一个节点(或者网络设备的接口)

IP地址也用于IP分组在网络中的寻址

一个IPv4地址有32位

IPv4地址通常采用“点分十进制”表示

在这里插入图片描述

为什么是 A类的地址只是 1 - 126,没有 127,127 是用于做本地回环测试的,ping 127.0.0.1

子网掩码的概念及作用

IP地址是以网络号和主机号来标示网络上的主机的,我们把网络号相同的主机称之为本地网络,网络号不相同的主机称之为远程网络主机,本地网络中的主机可以直接相互通信;远程网络中的主机要相互通信必须通过本地网关(Gateway)来传递转发数据。

①、子网掩码(Subnet Mask)又叫网络掩码、地址掩码,必须结合IP地址一起对应使用。
②、只有通过子网掩码,才能表明一台主机所在的子网与其他子网的关系,使网络正常工作。
③、子网掩码和IP地址做“与”运算,分离出IP地址中的网络地址和主机地址,用于判断该IP地址是在本地网络上,还是在远程网络网上。
④、子网掩码还用于将网络进一步划分为若干子网,以避免主机过多而拥堵或过少而IP浪费。
在这里插入图片描述
在早期为了适应大型,中型,小型等不同的网络,ip地址在设计出来时被分成几类,分类地址的不同之处在于表示网络的位数和主机的位数。所有的ip地址被分成A,B,C,D,E这五大类,其中A,B,C类地址经常使用,而D和E类是特殊地址,不经常使用。
在这里插入图片描述

为什么要使用子网掩码?

前面说道,子网掩码可以分离出IP地址中的网络地址和主机地址,那为什么要分离呢?因为两台主机要通信,首先要判断是否处于同一网段,即网络地址是否相同。如果相同,那么可以把数据包直接发送到目标主机,否则就需要路由网关将数据包转发送到目的地。

在这里插入图片描述
公网、内网 的介绍

1、公网、内网

是Internet的两种接入方式。公网接入方式中上网的计算机得到的IP地址是Internet上的非保留地址,公网的计算机和Internet上的其他计算机可随意互相访问。

2、私有地址

属于非注册地址,专门为组织机构内部使用。主要有A、B、C三类,A类地址范围是10.0.0.0-10.255.255.255 ,B类地址范围是172.16.0.0-172.31.255.255,C类地址范围是192.168.0.0-192.168.255.255。

127.0.0.0 到127.255.255.255 为系统环回地址。

在这里插入图片描述
通常情况下,按照需要容纳的主机数选择私有地址段。家庭网络规模比较小,一个C类地址,192.168.1.x可以容纳254个终端,足够使用。
在这里插入图片描述

09、网络地址转换 NAT 原理

NAT英文全称是“Network Address Translation”,中文意思是“网络地址转换”,它是一个IETF(Internet Engineering Task Force, Internet工程任务组)标准,允许一个整体机构以一个公用IP(Internet Protocol)地址出现在Internet上。

顾名思义,它是一种把内部私有网络地址(IP地址)翻译成合法网络IP地址的技术。因此我们可以认为,NAT在一定程度上,能够有效的解决公网地址不足的问题。

NAT有三种类型:静态NAT(Static NAT)、动态地址NAT(Pooled NAT)、网络地址端口转换NAPT(Port-Level NAT)。

NAT的基本工作原理是,当私有网主机和公共网主机通信的IP包经过NAT网关时,将IP包中的源IP或目的IP在私有IP和NAT的公共IP之间进行转换。

其中,网络地址端口转换NAPT(Network Address Port Translation)则是把内部地址映射到外部网络的一个IP地址的不同端口上。它可以将中小型的网络隐藏在一个合法的IP地址后面。NAPT与 动态地址NAT不同,它将内部连接映射到外部网络中的一个单独的IP地址上,同时在该地址上加上一个由NAT设备选定的端口号。

(1)源NAT(Source NAT,SNAT):修改数据包的源地址。源NAT改变第一个数据包的来源地址,它永远会在数据包发送到网络之前完成,数据包伪装就是一具SNAT的例子。

(2)目的NAT(Destination NAT,DNAT):修改数据包的目的地址。Destination NAT刚好与SNAT相反,它是改变第一个数据懈的目的地地址,如平衡负载、端口转发和透明代理就是属于DNAT。
在这里插入图片描述
NAT主要可以实现以下几个功能:数据包伪装、平衡负载、端口转发和透明代理。

数据伪装: 可以将内网数据包中的地址信息更改成统一的对外地址信息,不让内网主机直接暴露在因特网上,保证内网主机的安全。同时,该功能也常用来实现共享上网。

端口转发: 当内网主机对外提供服务时,由于使用的是内部私有IP地址,外网无法直接访问。因此,需要在网关上进行端口转发,将特定服务的数据包转发给内网主机。

负载平衡: 目的地址转换NAT可以重定向一些服务器的连接到其他随机选定的服务器。(不是很明白)

失效终结: 目的地址转换NAT可以用来提供高可靠性的服务。如果一个系统有一台通过路由器访问的关键服务器,一旦路由器检测到该服务器当机,它可以使用目的地址转换NAT透明的把连接转移到一个备份服务器上。(如何转移的?)

透明代理: NAT可以把连接到因特网的HTTP连接重定向到一个指定的HTTP代理服务器以缓存数据和过滤请求。一些因特网服务提供商就使用这种技术来减少带宽的使用而不用让他们的客户配置他们的浏览器支持代理连接。(如何重定向的?)

如下图所示,NAT网关有2个网络端口,其中公共网络端口的IP地址是统一分配的公共 IP,为202.20.65.5;私有网络端口的IP地址是保留地址,为192.168.1.1。私有网中的主机192.168.1.2向公共网中的主机202.20.65.4发送了1个IP包(Dst=202.20.65.4,Src=192.168.1.2)。
在这里插入图片描述
当IP包经过NAT网关时,NAT Gateway会将IP包的源IP转换为NAT Gateway的公共IP并转发到公共网,此时IP包(Dst=202.20.65.4,Src=202.20.65.5)中已经不含任何私有网IP的信息。由于IP包的源IP已经被转换成NAT Gateway的公共IP,Web Server发出的响应IP包(Dst= 202.20.65.5,Src=202.20.65.4)将被发送到NAT Gateway。

这时,NAT Gateway会将IP包的目的IP转换成私有网中主机的IP,然后将IP包(Des=192.168.1.2,Src=202.20.65.4)转发到私有网。对于通信双方而言,这种地址的转换过程是完全透明的。转换示意图如下:
在这里插入图片描述
如果内网主机发出的请求包未经过NAT,那么当Web Server收到请求包,回复的响应包中的目的地址就是私网IP地址,在Internet上无法正确送达,导致连接失败。

连接跟踪

在上述过程中,NAT Gateway在收到响应包后,就需要判断将数据包转发给谁。此时如果子网内仅有少量客户机,可以用静态NAT手工指定;但如果内网有多台客户机,并且各自访问不同网站,这时候就需要连接跟踪(connection track)。如下图所示:

在这里插入图片描述
此时,NAT Gateway会在Connection Track中加入端口信息加以区分。如果两客户机访问同一服务器的源端口不同,那么在Track Table里加入端口信息即可区分,如果源端口正好相同,那么在时行SNAT和DNAT的同时对源端口也要做相应的转换,如下图所示。(这里的理解灰常重要)
在这里插入图片描述

10、网络通信实现原理

想实现网络通信,每台主机需具备四要素:

本机的IP地址 、子网掩码 、网关的IP地址 、DNS的IP地址

DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)是一个局域网的网络协议。

获取这四要素分两种方式:1.静态获取:即手动配置 2.动态获取:通过dhcp获取

在这里插入图片描述
(1)最前面的”以太网标头”,设置发出方(本机)的MAC地址和接收方(DHCP服务器)的MAC地址。前者就是本机网卡的MAC地址,后者这时不知道,就填入一个广播地址:FF-FF-FF-FF-FF-FF。

(2)后面的”IP标头”,设置发出方的IP地址和接收方的IP地址。这时,对于这两者,本机都不知道。于是,发出方的IP地址就设为0.0.0.0,接收方的IP地址设为255.255.255.255。

(3)最后的”UDP标头”,设置发出方的端口和接收方的端口。这一部分是DHCP协议规定好的,发出方是68端口,接收方是67端口。

这个数据包构造完成后,就可以发出了。以太网是广播发送,同一个子网络的每台计算机都收到了这个包。因为接收方的MAC地址是FF-FF-FF-FF-FF-FF,看不出是发给谁的,所以每台收到这个包的计算机,还必须分析这个包的IP地址,才能确定是不是发给自己的。当看到发出方IP地址是0.0.0.0,接收方是255.255.255.255,于是DHCP服务器知道”这个包是发给我的”,而其他计算机就可以丢弃这个包。

接下来,DHCP服务器读出这个包的数据内容,分配好IP地址,发送回去一个”DHCP响应”数据包。这个响应包的结构也是类似的,以太网标头的MAC地址是双方的网卡地址,IP标头的IP地址是DHCP服务器的IP地址(发出方)和255.255.255.255(接收方),UDP标头的端口是67(发出方)和68(接收方),分配给请求端的IP地址和本网络的具体参数则包含在Data部分。

新加入的计算机收到这个响应包,于是就知道了自己的IP地址、子网掩码、网关地址、DNS服务器等等参数。

11、DNS 域名解析原理

DNS的作用:在互联网中,其实没有类似于www.xxx.com这种域名方式,而替代的是以IP地址,如222.222.222.222,那我们在IE地址栏中应当输入222.222.222.222才能打开网站www.xxx.com,但我们细想一下,互联网上的网站成千上万,如果每个网站登陆都需要记住一大串数字,那是不是特别不方便,对于记忆力不强的人,根本无法记住这么烦琐的数字。这个时候DNS就出现了,它的作用就是将222.222.222.222解析为www.xxx.com,那么我们登陆的时候就直接输入域名就可以了。

为什么一定要设置DNS才能上网?有些朋友可能会发现,为什么我可能登陆QQ、MSN,但却打不开网页呢?其实大部分原因都是因为DNS服务器故障造成的,DNS服务器地址是唯一的,是运营商提供给终端用户用来解析IP地址及域名的关系,而如果不设定DNS服务器地址,那么就无法查询地址的去向,自然也就打不开网页,而QQ、MSN等即时聊天软件,采用的是UDP传输协议,即不可靠传输协议,无需提供DNS服务器地址,也同样可以登陆。

dns的两种查询方式

一 :递归查询

主机向本地域名服务器的查询一般都是采用递归查询。所谓递归查询就是:如果主机所询问的本地域名服务器不知道被查询的域名的IP地址。

那么本地域名服务器就以DNS客户的身份,向其它根域名服务器继续发出查询请求报文(即替主机继续查询),而不是让主机自己进行下一步查询。

因此,递归查询返回的查询结果或者是所要查询的IP地址,或者是报错,表示无法查询到所需的IP地址。
在这里插入图片描述
二:迭代查询

本地域名服务器向根域名服务器的查询的迭代查询。迭代查询的特点:当根域名服务器收到本地域名服务器发出的迭代查询请求报文时,要么给出所要查询的IP地址。

要么告诉本地服务器:“你下一步应当向哪一个域名服务器进行查询”。然后让本地服务器进行后续的查询。根域名服务器通常是把自己知道的顶级域名服务器的IP地址告诉本地域名服务器。

让本地域名服务器再向顶级域名服务器查询。顶级域名服务器在收到本地域名服务器的查询请求后,要么给出所要查询的IP地址,要么告诉本地服务器下一步应当向哪一个权限域名服务器进行查询。

最后,知道了所要解析的IP地址或报错,然后把这个结果返回给发起查询的主机。
在这里插入图片描述

下面举一个例子演示整个查询过程:

    假定域名为m.xyz.com的主机想知道另一个主机y.abc.com的IP地址。例如,主机m.xyz.com打算发送邮件给y.abc.com。这时就必须知道主机y.abc.com的IP地址。下面是图2的几个查询步骤:1、主机m.abc.com先向本地服务器dns.xyz.com进行递归查询。2、本地服务器采用迭代查询。它先向一个根域名服务器查询。3、根域名服务器告诉本地服务器,下一次应查询的顶级域名服务器dns.com的IP地址。4、本地域名服务器向顶级域名服务器dns.com进行查询。5、顶级域名服务器dns.com告诉本地域名服务器,下一步应查询的权限服务器dns.abc.com的IP地址。6、本地域名服务器向权限域名服务器dns.abc.com进行查询。7、权限域名服务器dns.abc.com告诉本地域名服务器,所查询的主机的IP地址。8、本地域名服务器最后把查询结果告诉m.xyz.com。

整个查询过程共用到了8个UDP报文

    为了提高DNS查询效率,并减轻服务器的负荷和减少因特网上的DNS查询报文数量,在域名服务器中广泛使用了高速缓存,用来存放最近查询过的域名以及从何处获得域名映射信息的记录。例如,在上面的查询过程中,如果在m.xyz.com的主机上不久前已经有用户查询过y.abc.com的IP地址,那么本地域名服务器就不必向根域名服务器重新查询y.abc.com的IP地址,而是直接把告诉缓存中存放的上次查询结果(即y.abc.com的IP地址)告诉用户。由于名字到地址的绑定并不经常改变,为保持告诉缓存中的内容正确,域名服务器应为每项内容设置计时器并处理超过合理时间的项(例如每个项目两天)。当域名服务器已从缓存中删去某项信息后又被请求查询该项信息,就必须重新到授权管理该项的域名服务器绑定信息。当权限服务器回答一个查询请求时,在响应中都指明绑定有效存在的时间值。增加此时间值可减少网络开销,而减少此时间值可提高域名解析的正确性。不仅在本地域名服务器中需要高速缓存,在主机中也需要。许多主机在启动时从本地服务器下载名字和地址的全部数据库,维护存放自己最近使用的域名的高速缓存,并且只在从缓存中找不到名字时才使用域名服务器。维护本地域名服务器数据库的主机应当定期地检查域名服务器以获取新的映射信息,而且主机必须从缓存中删除无效的项。由于域名改动并不频繁,大多数网点不需花精力就能维护数据库的一致性。

在这里插入图片描述
DNS解析流程举例
在这里插入图片描述
如上图所示,我们将详细阐述DNS解析流程。

1、首先客户端位置是一台电脑或手机,在打开浏览器以后,比如输入http://www.zdns.cn的域名,它首先是由浏览器发起一个DNS解析请求,

如果本地缓存服务器中找不到结果,则首先会向根服务器查询,根服务器里面记录的都是各个顶级域所在的服务器的位置,当向根请求http://www.zdns.cn的时候,
根服务器就会返回.cn服务器的位置信息。

2、递归服务器拿到.cn的权威服务器地址以后,就会寻问cn的权威服务器,知不知道http://www.zdns.cn的位置。这个时候cn权威服务器查找并返回http://zdns.cn服务器的地址。3、继续向http://zdns.cn的权威服务器去查询这个地址,由http://zdns.cn的服务器给出了地址:202.173.11.104、最终才能进行http的链接,顺利访问网站。5、这里补充说明,一旦递归服务器拿到解析记录以后,就会在本地进行缓存,如果下次客户端再请求本地的递归域名服务器相同域名的时候,就不会再这样一层一层查了,因为本地服务器里面已经有缓存了,这个时候就直接把http://www.zdns.cn的A记录返回给客户端就可以了。

记录一条域名信息映射关系,称之为资源记录(RR)。

DNS资源记录
在这里插入图片描述
当我们查询域名http://www.zdns.cn的时候,查询结果得到的资源记录结构体中有如下数据:

1、TTL,就是生存周期,是递归服务器会在缓存中保存该资源记录的时长。2、网络/协议类型,它的代表的标识是IN,IN就是internet,目前DNS系统主要支持的协议是IN。3、type,就是资源记录类型,一般的网站都是都是A记录(IPv4的主机地址)。4、rdata是资源记录数据,就是域名关联的信息数据。

DNS缓存

DNS缓存指DNS返回了正确的IP之后,系统就会将这个结果临时储存起来。并且它会为缓存设定一个失效时间 (例如N小时),在这N小时之内,当你再次访问这个网站时,系统就会直接从你电脑本地的DNS缓存中把结果交还给你,而不必再去询问DNS服务器,变相“加速”了网址的解析。

当然,在超过N小时之后,系统会自动再次去询问DNS服务器获得新的结果。所以,当你修改了 DNS 服务器,并且不希望电脑继续使用之前的DNS缓存时,就需要手动去清除本地的缓存了。

浏览器DNS查找顺序

浏览器DNS缓存->本地系统DNS缓存->本地计算机HOSTS文件->ISP DNS缓存->递归or迭代搜索

期间如果查询到了,也就直接访问ip地址了,这个就像三级缓存原理一样,例如,能够在hosts文件中找到就不会再去查其他的
在这里插入图片描述

12、网关与路由介绍

网关(Gateway)又称网间连接器、协议转换器。默认网关在网络层上以实现网络互连,是最复杂的网络互连设备,仅用于两个高层协议不同的网络互连。网关的结构也和路由器类似,不同的是互连层。网关既可以用于广域网互连,也可以用于局域网互连。

网关实质上是一个网络通向其他网络的IP地址。

那交换机和路由器有什么区别呢?

两者都是连接互联网的设备,它们之间主要区别就是,交换机发生在网络的第二层数据链路层,而路由器发生在第三层网络层。这个区别是两者各自工作方式的根本区别。路由器可以根据IP地址寻找下一个设备,可以处理TCPIP协议,而上一篇我们讲过交换机是根据MAC地址寻址的。
在这里插入图片描述
(1)外形上:

从外形上我们区分两者,交换机通常端口比较多看起来比较笨重,而路由器的端口就少得多体积也小得多,实际上右图并不是真正的路由器只是集成了路由器的功能,除此之外还有交换机的功能(LAN口就是作为交换机的端口来使用,WAN是用于连接外网的端口),而两个天线则是无线AP接入点(即是通常所说的无线局域网wifi)。

(2)工作层次不同:

最初的交换机工作在OSI开放式系统互联模型的数据链路层,也就是第二层,而路由器则工作在OSI模型的网络层,就是第三层。也就是由于这一点所以交换机的原理比较简单,一般都是采用硬件电路实现数据帧的转发,而路由器工作在网络层,肩负着网络互联的重任,要实现更加复杂的协议,具有更加智能的转发决策功能,一般都会在在路由器中跑操作系统,实现复杂的路由算法,更偏向于软件实现其功能。

(3)数据的转发对象不同:

交换机是根据MAC地址转发数据帧,而路由器则是根据IP地址来转发IP数据报/分组。数据帧是在IP数据包/分组的基础上封装了帧头(源MAC和目的MAC等)和帧尾(CRC校验码)。而对于MAC地址和IP地址大家也许就搞不明白了,为何需要两个地址,实际上IP地址决定最终数据包要到达某一台主机,而MAC地址则是决定下一跳将要交互给哪一台设备(一般是路由器或主机)。而且,IP地址是软件实现的,可以描述主机所在的网络,MAC地址是硬件实现的,每一个网卡在出厂的时候都会将全世界唯一的MAC地址固化在网卡的ROM中,所以MAC地址是不能被修改的,但是IP地址是可以被网络管理人员配置修改的。

(4)”分工“不同

交换机主要是用于组建局域网,而路由器则是负责让主机连接外网。多台主机可以通过网线连接到交换机,这时就组建好了局域网,就可以将数据发送给局域网中的其他主机,如我们使用的飞秋、极域电子教室等局域网软件就是通过交换机把数据转发给其他主机的,当然像极域电子教室这样的广播软件是利用广播技术让所有的主机都收到数据的。然而,通过交换机组建的局域网是不能访问外网的(即是Internet),这时需要路由器来为我们”打开外面精彩世界的大门“,局域网的所有主机使用的都是私网的IP,所以必须通过路由器转化为公网的IP之后才能访问外网。

(5)冲突域和广播域

交换机分割冲突域,但是不分割广播域,而路由器分割广播域。由交换机连接的网段仍属于同一个广播域,广播数据包会在交换机连接的所有网段上传播,在这种情况下会导致广播风暴和安全漏洞问题。而连接在路由器上的网段会被分配不通的广播域,路由器不会转发广播数据。需要说明的是单播的数据包在局域网中会被交换机唯一地送往目标主机,其他主机不会接收到数据,这是区别于原始的集线器的,数据的到达时间由交换机的转发速率决定,交换机会转发广播数据给局域网中的所有主机。

最后需要说明的是:路由器一般有防火墙的功能,能够对一些网络数据包选择性过滤。现在的一些路由器都具备交换机的功能(如上图右),一些交换机具备路由器的功能,被称为3层交换机,广泛使用。相比较而言,路由器的功能较交换机要强大,但是速度也较慢,价格昂贵,三层交换机既有交换机的线性转发报文的能力,又有路由器的良好的路由功能因此得到广泛的使用。

当然关于路由器和交换机的一些介绍远不止这些,上述所说是主要的一些区别,同时也是本人对路由器和交换机的浅显认识,如有其他一些较明显的区别特征望给出宝贵意见。

13、socket 套接字介绍

ocket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。

socket说成ip+port,ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序,ip地址是配置到网卡上的,而port是应用程序开启的,ip与port的绑定就标识了互联网中独一无二的一个应用程序。

在这里插入图片描述
先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
在这里插入图片描述
socket函数的这三个参数其实就是把抽象的socket具体化的条件,domain参数决定图中所示的第二层通信域,type决定了第三层的通信模式,protocol决定了第四层真正的通信协议。

SOCK_STREAM :	流套接字:提供序列化的、可靠的、双工的、基于连接的字节流式服务
SOCK_DGRAM :	数据报套接字:提供数据报式的服务(无连接、不可靠、有最大长度限制)
SOCK_RAW :  	原始套接字:提供原始套接字服务

服务器端的测试:

import socketserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 流协议 =》 TCP协议
# server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 报协议 =》 UDP协议server.bind(("127.0.0.1", 5000))server.listen(5)  # 半连接池的大小conn, cli_address = server.accept()
print(conn)
print(cli_address)  # 客户端的IP与端口号data = conn.recv(1024)  # 收消息 最大接收的数据量为1024Bytes,收到的是bytes类型
print("server receive data = ", data.decode("utf-8"))
conn.send(data.upper())conn.close()

客户端的测试:

import socketcli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)cli.connect(("127.0.0.1", 5000))cli.send("唤醒手腕喜欢吃屎!".encode("utf-8"))

运行结果展示:

<socket.socket fd=656, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 5000), raddr=('127.0.0.1', 56336)>
('127.0.0.1', 56336)
server receive data =  唤醒手腕喜欢吃屎!

内核为一个监听套接字维护两个队列:(注意一定是监听套接字,服务端等待连接的套接字)

SYN队列:

  1. 在三次握手中,收到了客户端的第一个SYN,此时连接放入SYN队列中(未完成连接队列);
    ACCEPT队列:
  2. 在三次握手中,收到了客户端的第三个ACK,此时连接从SYN队列中取出,放入到ACCEPT队列中。服务端调用accept,就是从ACCEPT队列中取出一个已完成连接。

bind 0.0.0.0的作用是什么呢?

0.0.0.0在服务器的环境中,指的就是服务器上所有的ipv4地址,如果机器上有2个ip 192.168.30.10 和 10.0.2.15,redis在配置中,如果配置监听在0.0.0.0这个地址上,那么,通过这2个ip地址都是能够到达这个redis服务的。同时呢,访问本地的127.0.0.1也是能够访问到redis服务的。

UDP 测试 C / S :

UDP协议的服务器端代码:

import socketserver = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 5000))while True:data, RetAddress = server.recvfrom(1024)print(data.decode("utf-8"))print(RetAddress)server.sendto(data.decode("utf-8").upper().encode("utf-8"), RetAddress)

UDP协议的客户端代码:

import socketclient = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)while True:msg = input("msg >>> ")client.sendto(msg.encode("utf-8"), ('127.0.0.1', 5000))print(client.recvfrom(1024)[0].decode("utf-8"))

疑难测试:

当服务器端与客户端建立连接后,不发送任何数据,直接close,但是在客户端进行recv接收数据,结果客户端没有出现阻塞的情况。

# Server.py
import socketserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 流协议 =》 TCP协议
server.bind(("127.0.0.1", 5000))
server.listen(5)  # 半连接池的大小
conn, cli_address = server.accept()
conn.close()#client.py
import socketcli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cli.connect(("127.0.0.1", 5000))
data = cli.recv(1024).decode("utf-8")print("ending!")

在客户端调用的是cli,当服务器端close(),关闭服务后,客户端是能够感知的,recv并没有造成阻塞。

当服务器端与客户端建立连接后,不发送任何数据,延时60秒,然后close结束服务器端的服务进程,客户端进行recv接收数据发生了阻塞,阻塞了60秒钟。

# Server.py
import socket
import timeserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 流协议 =》 TCP协议
server.bind(("127.0.0.1", 5002))
server.listen(5)  # 半连接池的大小
conn, cli_address = server.accept()
time.sleep(3)
conn.close()# client.py
import socketcli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cli.connect(("127.0.0.1", 5002))
print("client连接Server成功!")
data = cli.recv(36).decode("utf-8")print("ending!")"""
运行结果
client连接Server成功!
ending! # 60秒后出现Process finished with exit code 0
"""

在客户端调用的是cli,当服务器端没有close()前,服务器没有发送数据前提,recv接收数据会处于阻塞。直到服务器端停止服务进程,然后客户端cli感知连接端口,阻塞进程将会释放。

在底层来讲,socket 在服务器端,端口连接后,reve就不会发生阻塞了,会返回空,len的长度是0

14、TCP 黏包问题介绍

  1. 发送数据方多次send之间的间隔太短。tcp为了提高系统效率,使用了Nagle算法,假如多次短时间内使用send函数发送数据, Nagle会先将这几次send的数据存在内核的缓冲区,等缓冲区满了后或者超时(200ms)就会发送出去,这样就减少了发送的次数,提升了效率。

  2. 网路环境不好时,数据没有发送出去,就会全部堆积在缓冲区,等网络好后,就会一股脑的全部发送出去,这也会导致黏包。

  3. 接受数据方处理不当。tcp中接受方会将接受到的数据先放到缓冲区,然后应用程序再读取缓冲区的数据进行操作。假如缓冲区已经有个两个大小各为32个字节的数据,而此时直接读取64字节的数据的话,必然会造成黏包的。

只有TCP有黏包现象,UDP永远不会黏包

之前提到过,TCP是面向连接的通信方式,提供了顺序、可靠、不会重复的数据传输,而且不会加上边界。

这个就意味着,每一个要发送的信息,可能会被拆分成多份,每一份都会不多不少地正确地到达目的地,然后被重新拼装起来,传给正在等待地应用程序。

问题就出现了,传输地数据没有界限,当传输到另一台地机器的时候,从内存中取值的时候,取值的大小取决于recv地参数。

如果需要传输地数据比较大,那么recv肯定无法一次取完,在内存中有残留,这样就会出现黏包地现象。

还有就是为了提高效率,tcp协议在传输地过程中,会将数据包较小的和时间间距比较小的数据包一起发送,这样也会造成黏包。

socket收发消息的原理
在这里插入图片描述
发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。

而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。

例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束。

所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

TCP与UDP的区别

  1. TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
  2. UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
  3. tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头。

udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是 y > x 数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠。

tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

TCP黏包的解决方案

为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据。

struct模块

该模块可以把一个类型,如数字,转成固定长度的bytes
在这里插入图片描述
假设通过客户端上传文件apple.txt,服务器端设置header

import json,struct#为避免粘包,必须自定制报头
header={'file_size':1073741824000,'file_name':'apple.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T数据,文件路径和md5值#为了该报头能传送,需要序列化并且转为bytes
head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化并转成bytes,用于传输#为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节
head_len_bytes=struct.pack('i',len(head_bytes)) #这4个字节里只包含了一个数字,该数字是报头的长度#客户端开始发送
conn.send(head_len_bytes) #先发报头的长度,4个bytes
conn.send(head_bytes) #再发报头的字节格式
conn.sendall(文件内容) #然后发真实内容的字节格式

服务端开始接收:

head_len_bytes=s.recv(4) #先收报头4个bytes,得到报头长度的字节格式
x=struct.unpack('i',head_len_bytes)[0] #提取报头的长度head_bytes=s.recv(x) #按照报头长度x,收取报头的bytes格式
header=json.loads(json.dumps(header)) #提取报头#最后根据报头的内容提取真实的数据,比如
real_data_len=s.recv(header['file_size'])
s.recv(real_data_len)

在服务器端进行发送客户端文件(字节流)

import json
import struct
import socketserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 流协议 =》 TCP协议
server.bind(("127.0.0.1", 5000))
server.listen(5)  # 半连接池的大小conn = server.accept()
with open("mini_tang.jpg", 'rb') as img:image = img.read()file_size = len(image)
# 为避免粘包,必须自定制报头
header = {'file_size': file_size,'file_name': 'mini_tang.txt'}# 为了该报头能传送,需要序列化并且转为bytes
header_bytes = bytes(json.dumps(header), encoding='utf-8')  # 序列化并转成bytes,用于传输# 为了让客户端知道报头的长度,用struct将报头长度这个数字转成固定长度:4个字节header_len_bytes = struct.pack('i', len(header_bytes))
# 这4个字节里只包含了一个数字,该数字是报头的长度# 客户端开始发送 449 067 560
conn.send(header_len_bytes)  # 先发报头的长度,4个bytes
conn.send(header_bytes)  # 再发报头的字节格式
conn.sendall(image)  # 然后发真实内容的字节格式

代码实现 服务器端代码:

import json
import struct
import socketserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 流协议 =》 TCP协议
server.bind(("127.0.0.1", 5000))
server.listen(5)  # 半连接池的大小conn, address = server.accept()header_index = conn.recv(4)header_cont = json.loads(conn.recv(struct.unpack('i', header_index)[0]).decode('utf-8'))
file_size = header_cont['file_size']
file_name = header_cont['file_name']file_bytes = conn.recv(int(file_size))with open('wrist' + file_name, 'wb') as f:f.write(file_bytes)

我们可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了)

发送时:先发报头长度,再编码报头内容然后发送,最后发真实内容

接收时:先手报头长度,用struct取出来,根据取出的长度收取报头内容,然后解码,反序列化,从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容。

代码实现 客户端代码:

import json
import struct
import socketconn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 流协议 =》 TCP协议conn.connect(('127.0.0.1', 5000))
with open("mini_tang.jpg", 'rb') as img:image = img.read()file_size = len(image)
# 为避免粘包,必须自定制报头
header = {'file_size': file_size,'file_name': 'mini_tang.jpg'}# 为了该报头能传送,需要序列化并且转为bytes
header_bytes = bytes(json.dumps(header), encoding='utf-8')  # 序列化并转成bytes,用于传输# 为了让客户端知道报头的长度,用struct将报头长度这个数字转成固定长度:4个字节header_len_bytes = struct.pack('i', len(header_bytes))
# 这4个字节里只包含了一个数字,该数字是报头的长度# 客户端开始发送 449 067 560
conn.send(header_len_bytes)  # 先发报头的长度,4个bytes
conn.send(header_bytes)  # 再发报头的字节格式
conn.sendall(image)  # 然后发真实内容的字节格式

这篇关于Python 网络编程 OSI 协议原理、网络通信、DNS 域名解析、socket 套接字、TCP 粘包等教程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/845053

相关文章

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

python: 多模块(.py)中全局变量的导入

文章目录 global关键字可变类型和不可变类型数据的内存地址单模块(单个py文件)的全局变量示例总结 多模块(多个py文件)的全局变量from x import x导入全局变量示例 import x导入全局变量示例 总结 global关键字 global 的作用范围是模块(.py)级别: 当你在一个模块(文件)中使用 global 声明变量时,这个变量只在该模块的全局命名空

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

hdu4407(容斥原理)

题意:给一串数字1,2,......n,两个操作:1、修改第k个数字,2、查询区间[l,r]中与n互质的数之和。 解题思路:咱一看,像线段树,但是如果用线段树做,那么每个区间一定要记录所有的素因子,这样会超内存。然后我就做不来了。后来看了题解,原来是用容斥原理来做的。还记得这道题目吗?求区间[1,r]中与p互质的数的个数,如果不会的话就先去做那题吧。现在这题是求区间[l,r]中与n互质的数的和

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

ASIO网络调试助手之一:简介

多年前,写过几篇《Boost.Asio C++网络编程》的学习文章,一直没机会实践。最近项目中用到了Asio,于是抽空写了个网络调试助手。 开发环境: Win10 Qt5.12.6 + Asio(standalone) + spdlog 支持协议: UDP + TCP Client + TCP Server 独立的Asio(http://www.think-async.com)只包含了头文件,不依

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学