美文网首页
网络编程基础(网络层+传输层)

网络编程基础(网络层+传输层)

作者: SMEB_ | 来源:发表于2019-07-12 12:20 被阅读0次

    由于不同机器上的程序要通信,才产生了网络

    基础知识

    基本架构

    1. 应用类:qq、微信、网盘...(安装应用)
    2. web类:百度、知乎、博客园...(浏览器访问)

    C/S架构

    • 服务端(server):一直运行,等待请求
    • 客户端(client):需要使用时,发送请求

    B/S架构

    基于浏览器(broser)。这是一个大的趋势(小程序、公众号),实际上就是统一入口。

    • B/S架构实际上也是一种C/S架构。

    物理相关知识

    局域网内通信

    当两台计算机要进行通信,用网线连接两台计算机。网线是接通计算机的网卡,网卡上有 全球唯一的 mac地址。

    (局域网内)当多台计算机要通信时,所有的计算机可以都用一条网线接到交换机上。

    • 通过ip地址,利用ARP协议找到对应的mac地址,进行连接传输。
    地址解析协议,即ARP(数据链路层协议),是根据IP地址获取物理地址的一个TCP/IP协议。
    主机发送信息时将包含目标IP地址的ARP请求广播到网络上的所有主机,并接收返回消息,以此确定目标的物理地址;收到返回消息后将该IP地址和物理地址存入本机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源。
    地址解析协议是建立在网络中各个主机互相信任的基础上的,网络上的主机可以自主发送ARP应答消息,其他主机收到应答报文时不会检测该报文的真实性就会将其记入本机ARP缓存;
    由此攻击者就可以向某一主机发送伪ARP应答报文,使其发送的信息无法到达预期的主机或到达错误的主机,这就构成了一个ARP欺骗。
    

    广域网通信

    通过路由器将多个局域网接通起来,而每个局域网都有一个统一的出口(网关)。

    通过ip地址 和 子网掩码 按位与 得出本网段(可用于判断是否同为同一个网段)。

    子网掩码:255.255.255.0
    ip地址:192.168.13.253
    按位与:192.168.13.0
    

    软件应用相关

    • 服务使用 TCP或UDP的端口侦听客户端请求
    • 客户端使用IP地址定位服务器 使用端口 定位服务
    • 可以在服务器网卡上设置只开放必要的端口,实现服务器的网络安全

    网络层

    网络层向上提供简单灵活的、无连接的、尽最大努力交付的数据报服务。

    网际协议IP

    网际协议IP是TCP/IP体系中两个最主要的协议之一,也是最重要的互联网标准协议之一。

    image
    • ARP协议在下,因为IP经常要使用它(PARP已淘汰)
    • ICMP、IGMP在上,它们要使用IP协议
    ip分类
    image image

    IP数据报的格式

    image

    主要分为两部分

    • 固定长度 (20字节)
    • 可选字节
    各字段
    1. 版本 4位 广泛使用ipv4、ipv6
    2. 首部长度 4位,而4位二进制数最大十进制是15,但固定长度是20字节。则将1乘4,所以此字段的最小值是5,最大值是60.若首部长度不是4的倍数时,利用最后的填充字段加以填充。
    3. 区分服务 一般不使用
    4. 总长度 指首部和数据之和,单位为字节。16位则最大长度位65535字节(很少这么长)
    5. 标识 16位。(并不是序号)因为ip是无连接服务,数据报不存在按序接收的问题。当数据报过长必须进行分片时,这个标识字段的值就被复制到所有的数据报片的标识字段中。相同的标识字段值使分片后的各数据报片最后能正确地重装成为原来的数据报。
    6. 标志 3位 目前只有两位有意义。MF(more fragment)=1表示还有分片,MF=0表示已经是最后一个。DF(don`t fragment)=0表示允许分片
    7. 片偏移 占13位,在分片后,某片在原分组中的相对位置。以8个字节为单位,即分片长度一定是8字节的整数倍。(规定分片长度不超过1420字节) image
    分片一:标识:777,MF=1,DF=0,片偏移=0
    分片二:标识:777,MF=1,DF=0,片偏移=175
    分片三:标识:777,MF=0,DF=0,片偏移350
    
    1. 生存时间 8位 TTL(time to live)。防止无法交付的数据报无限制的在互联网。每跳一个路由器减一,当为0时,路由器丢弃该数据报。
    2. 协议 8位。指出此数据报携带的数据是使用何种协议,以便知道该上交给哪个协议进行处理。

    协议名 | ICMP | IGMP | IP(ip数据报再封装ip数据报) | TCP | UDP | IPv6
    ---|---|---|---|---|---|---|---
    协议字段值 | 1 | 2 | 4 | 6 | 17 | 41

    1. 首部检验和 16位。只检验数据报首部。二进制反码求和。
    2. 源地址 32位
    3. 目的地址 32位
    分组转发
    1. 从数据报首部提取目的主机的IP地址,得出网络地址
    2. 若再同网段则直接交付(包括把目的地址转化位硬件地址)
    3. 若路由表中有目的地址的特定主机路由(特定指明的),则指明下一跳
    4. 若路由表有到达目的网络的路由,则指明下一跳
    5. 若有默认路由则发送给默认路由
    6. 报告转发分组出错

    注:路由表中并不是指明到达某网络的完整路径,而只是下一跳路径。

    运输层

    端口

    接通两台计算机后,需要确定与哪个应用程序进行通信。而每一个需要网络通信的程序都会在本机上开一个端口。(同一时间同一台计算机,一个端口只被一个程序占用。)

    单个计算机中进程是用进程标识符来标志的。但在互联网环境下,不同的计算机可能使用不同的操作系统,不同的操作系统又使用不同格式的进程标识符。
    为了使运行不同操作系统的计算机的应用进程能够互相通信,就必须用统一的方法对TCP/IP体系的应用进程进行标志。
    
    解决这个问题的方法就是在运输层使用协议端口号。
    
    端口号范围:0-65535
    • 0~1023是系统端口号。指派给了TCP/IP最重要的一些应用程序
    • 1024~49151是登记端口号。是为没有熟知端口号的应用程序使用的。这类端口号必须在IANA按照规定登记,以防止重复。
    • 49152~65535是客户端使用的端口号,又叫做短暂端口号。这类端口号留给客户进程选择暂时使用。当通信结束后,客户端已使用过的端口号就不复存在了。
    常见端口号
    端口 协议
    53 DNS域名系统协议
    80 超文本传输协议(HTTP)
    443 安全超文本传输协议(HTTPS)
    21 FTP文件传输协议
    22 安全外壳协议(SSH)
    53(UDP) DNS
    3306 mysql

    TCP协议(全双工通信、可靠、面向连接)

    全双工即通信允许数据在两个方向上、同时传输(半双工则不可同时)

    概述
    1. TCP是面向连接的运输层协议。在使用TCP协议之前,必须先建立TCP连接。数据传送完毕后必须释放已经建立的连接。
    2. 每条连接只能点对点
    3. 可靠交付
    4. 全双工(两端都设有发送缓存和接收缓存)
    5. 面向字节流(流入到进程或从进程流出的字节序列)

    TCP并不关心应用进程一次把多长的报文发送到TCP的缓存中,而是根据对方给出的窗口值和当前网络拥塞的程度来决定一个报文段应包含多少个字节。(UDP发送的报文长度是应用进程给出的)。如果应用进程传送到TCP缓存的数据块太长,TCP就可以把它划分短一些再传送。如果应用进程一次只发来一个字节,TCP也可以等待积累有足够多的字节后再构成报文段发送出去。

    连接

    TCP连接有两个端点,而这个端点叫套接字。每条TCP连接唯一地被通信两端的两个端点(套接字)所确定。

    套接字 socket = ( IP地址 : 端口号)
    
    可靠传输

    TCP下面的网络所提供的是不可靠的传输,因此,TCP必须采用适当的措施才能使得两个运输层之间的通信变得可靠。

    理想传输条件:

    1. 传输信道不产生差错
    2. 不管发送方以多快的速度发送数据,接收方总是来得及处理收到的数据。
    停止等待协议
    image

    最简单的用来保证可靠传输的协议,在每发送完一个分组时设定一个超时计时器,实现超时重传。

    但是信道利用率低!信道绝大多数时间都是等待空闲的。

    连续ARQ协议

    滑动窗口协议是TCP协议的精髓所在。

    image
    • 发送方连续发送分组,每收到一个确认,就把发送窗口向前滑动一个分组的位置。
    • 接收方一般采用累积确认的方式。不必收到每个分组都逐一确认,而是在收到几个分组后,对按序到达的最后一个分组发送确认。
    缺点是不能向发送方反映接收方已经正确收到的所有分组信息
    例如发送方发送了前5个分组,但只有第3个丢失了。这时接收方只能对前两个分组发送确认,发送方无法知道后面3个分组的情况,必须重新发送后面三个分组。
    这就叫做Go-back-N 回退N
    
    TCP报文段的首部格式

    TCP报文段氛围首部和数据两部分,而TCP的全部功能都体现在它的首部中各字段的作用。

    image

    报文段首部的前20个字节是固定的

    1. 源端口和目的端口(各2个字节)
    2. 序号 占4个字节,即2^32个序号(TCP是面向字节流的,传送中每一个字节都按顺序编号。该序号字段值表示本报文所发送的数据的第一个字节的序号)
    3. 确认号 期望收到对方下一个报文段的第一个数据字节的序号。即表示N-1为止的数据已收到。
    4. 数据偏移 实际上就是TCP报文段的首部长度。4位,但是单位是32位即4个字节。4位的最大十进制数是15,所以数据偏移的最大值是60,即是TCP首部的最大长度。(即选项长度不能超过40)
    5. 保留6位今后使用
    6. URG urgent紧急字段。相当于高优先级。(但是大量的开发者都将它置为1,导致用处不明显。)
    7. ACK 当ACK=1时确认好字段才有效。建立连接后ACK都必须置为1.
    8. RST reset表明TCP连接中出现严重差错,必须释放连接,然后再重新建立运输连接。
    9. SYN 建立连接时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接请求报文。若对方同意建立连接,则在响应报文中使用 SYN=1 ACK=1 .因此SYN置为1就表示这是一个连接请求或连接接受报文。
    10. FIN 用来释放连接。
    11. 窗口 占2字节。指的是发送本报文段的一方的接收窗口。用来告诉对方:从本报文段首部中的确认好算起,接收方目前允许对方发送的数据量。即作为接收方让发送方设置其发送窗口的依据。
    12. 检验和 包括首部和数据这两部分进行检验。同UDP需要伪首部(其中第4个字段17改位6)
    13. 紧急指针 当URG=1时,指明紧急数据中的字节数(紧急数据后就是普通数据)。当窗口为0时也可以发送紧急数据。
    TCP可靠传输的实现
    缓存机制

    TCP的滑动窗口是以字节为单位的。凡是已经发送过的数据,在未收到确认之前都必须暂时保留,以便在超时重传使用。

    image

    发送缓存用来展示存放:

    1. 发送应用程序传送给发送方TCP准备发送的数据
    2. TCP已发送但未收到确认的数据
    image

    接收缓存用来暂时存放:

    1. 按序到达、但尚未被接收应用程序读取的数据
    2. 未按序到达的数据
    超时重传时间的选择

    TCP采用了一种自适应算法,它记录一个报文段发出的时间,以及收到相应的确认的时间。这两个时间之差就是报文段的往返时间RTT。再进行加权平均,每测得一个新样本就更新一次RTTs。

    TCP流量控制

    所谓流量控制就是让发送方的发送速率不要太快,要让接收方来得及接收。

    • 利用滑动窗口机制可以很方便地在TCP连接上实现对发送方的流量控制
    传输效率

    在TCP实现中广泛使用Nagle算法:

    若发送应用进程把要发送的数据逐个字节地送到TCP的发送缓存
    则发送方就把第一个字节先发送出去,其他数据先缓存(避免了浪费大量的带宽而发送失败)
    待收到确认后,再把所有数据组装成一个报文段发送出去。
    且当达到的数据已达到发送窗口大小的一半或已达到报文段的最大长度时,就立即发送一个报文段。(有效的提高网络的吞吐量)
    

    糊涂窗口综合征

    当TCP接收方缓存已满,应用程序一次只读取一个字节的缓存
    则发送方一次只能发送一个字节
    导致网络效率低下
    
    可以让接收方等待一段时间,或者等待接收方缓存已有一半空闲的时间
    接收方就发送确认报文。
    
    拥塞控制
    拥塞控制与流量控制?

    拥塞控制就是防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机、路由器以及网络传输性能相关的所有因素。

    相反,流量控制往往是指点对点通信量的控制,是个端到端的问题(接收端控制发送端)

    某些拥塞控制算法是向发送端发送控制报文,并告诉发送端,网络已出现麻烦,必须放慢发送速率。这点又和流量控制是很相似的。

    image

    闭环控制的三种措施:

    1. 拥塞检测
    2. 拥塞通告
    3. 拥塞缓解
    TCP的拥塞控制
    • 慢开始
    • 拥塞避免
    • 快重传
    • 快恢复
    慢开始和拥塞避免

    刚开始发送数据时,由于并不清楚网络的负荷情况,所以如果立即把大量数据字节注入到网络,那么久有可能引起网络发生拥塞。较好的方法就是先探测一下,由小到大逐渐增大发生窗口

    每经过一个传输轮次,拥塞窗口cwnd就加倍。(发生方每接收到一个对新报文的确认就立即为其窗口加1,不需要等待整个轮次结束。)

    image

    当网络出现超时,发送方判断为网络拥塞,于是调整门限值(cwnd/2),同时窗口值cwnd设置为1,进入慢开始阶段。

    有时各别报文段丢失,而并非网络拥塞,但却启动慢重传会降低传输效率。于是采用快重传算法。接收方不要等待自己发送数据时才捎带确认,而是要立即发送确认,即使收到了失序的报文段也要立即发出对已收到报文段的重复确认。

    当已接收m1,m2,未接收m3但接收到m4时
    接收方除了会分别发送确认,还必须立即发送对m2的重复确认
    发送方一连收到3个重复确认就知道接收方没有收到报文m3
    立即进行重传。而不会出现超时
    快重传使整个网络的吞吐量提高约20%
    

    对于以上只是丢失了个别的报文段,于是不启动慢开始,而是执行快恢复。发送方门限值设置为cwnd/2, 拥塞窗口同设置为cwnd/2

    总结为:

    image
    三次握手
    image
    1. 客户端首先发送SYN请求,seq作为序列号标识,如果对方应答则返回 x+1。
    2. 因为是全双工通信,所以服务器接收到客户端的SYN后,不仅要回复确认ack,同时也要请求客户端的通信许可,即也回发一个SYN。
    3. 成功建立TCP连接。

    (TCP标准规定,ACK报文段可以携带数据,但如果不携带数据则不消耗序号)

    为什么A最后还要发送一次确认呢?

    假定出现这种情况:
    A发出的第一个请求报文在某处滞留,A没有收到确认于是再次发生连接请求。
    第二次数据传输完成后,关闭了连接。
    此时B收到第一个滞留的请求,于是回复。
    若没有A的第二次确认,则此连接一直占用,资源浪费
    
    四次挥手
    image

    结束的请求可以由客户端发起,也可以由服务端发起。

    例:

    1. 客户端发起结束连接的请求,则由客户端向服务端的通道先断开。(进入FIN-WAIT-1)
    2. 服务端同意客户端的断开。(进入FIN-WAIT-2)
    3. 服务器继续发送未完成的报文
    4. 服务器请求断开。
    5. 客户端同意断开。(等待两个来回时间,若无服务器请求,则正常关闭)

    (TCP标准规定,FIN报文即使不携带数据,也消耗掉一个序号)

    为什么握手三次,挥手需要四次?

    首先握手实际上也可以分为四次,只是在第二次握手时,把确认和请求一起发送了。

    建立连接时可以同时发送,但是断开连接时情况不同。当一方请求断开连接时,另一端可能还有数据没有传输完成,所以此时不会将 确认对方断开 和 请求自己断开 合并一起发送(两次挥手合并为一次)。

    生动讲解四次挥手

    • FIN_WAIT_1:
    其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。
    而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。
    而当对方回应ACK报文后,则进入到FIN_WAIT_2状态。
    当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。(主动方)
    
    • FIN_WAIT_2:
    实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你(ACK信息),稍后再关闭连接。(主动方)
    
    • CLOSE_WAIT:
    这种状态的含义其实是表示在等待关闭。
    当对方close一个SOCKET后发送FIN报文给自己,你会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。
    接下来,当数据全部传输结束后,那么就可以close这个SOCKET。
    发送FIN报文给对方,也即关闭连接。所以在CLOSE_WAIT状态下,对方需要完成的事情是等待你去关闭连接。
    
    • LAST_ACK:
    它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。(被动方)
    
    • TIME_WAIT:
    表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。
    如果FINWAIT1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(主动方)
    如果2个来回时间内,再次收到了对方发来的请求断开FIN,则说明前面发送的确认报文丢失,再次发送。
    

    UDP(无连接、不可靠、面向报文)

    不可靠的无连接传输,需要发送数据时,直接发往对方。

    优点: 快、不需要占用连接资源

    UDP特点
    • 无连接,减少了开销和发送数据之前的时延
    • 不可靠交付,尽最大努力交付,因此主机不需要维持复杂的连接状态。
    • 面向报文。UDP对应用层交下来的报文,既不合并也不拆分,而是保留这些报文的边界。(应用层交付给UDP多长的报文,UDP就照样发送,一次一个报文。)
    • 没有拥塞控制,即使出现网络拥塞也不会降低发送速率,这对实时应用很重要(ip电话,视频会议)。
    • 支持一对一、一对多、多对一、多对多交互通信。
    • 首部开销小,只有8个字节。TCP20个字节。
    UDP首部格式
    • 数据字段
    • 首部字段(8字节)
      1. 源端口
      2. 目的端口
      3. 长度(UDP用户数据报的长度,最小值为8即只有首部)
      4. 检验和
    image

    (伪首部是一个虚拟的数据结构,其中的信息是从数据报所在IP分组头的分组头中提取的。既不向下传送也不向上递交,而仅仅是为了计算检验和。)

    为什么伪首部要有目的IP地址
    
    学习过通信系统原理后,我们知道数据传输过程中会产生误码,0可能变为1,1可能变为0,并且每种校验码都有一定的查错能力,超过这个范围,就无法察觉错误了,而早期的通信环境大概比较糟糕,因此,在传输过程中出现误码,可能使IP报文的目的地址出现错误,接收主机的UDP计算校验和时,目的IP地址来自IP层,由于目的IP地址出现错误,导致发送主机计算检验和时使用的目的IP地址与接收主机计算检验和时使用的目的IP地址不同,UDP发现错误,丢弃报文
    
     
    
    为什么伪首部要有源IP地址
    
    为了让接收主机确认源IP地址没有出现错误。
    
    假设我们想要开发一款基于UDP的程序,A发送UDP报文给B,B要发送回应报文给A,假设传输过程出现误码,源IP地址出现错误,则A计算检验和时使用的源IP地址与B计算校验和时使用的源IP地址不同,B就可以发现错误,从而丢弃报文,定时重传等可靠性由应用程序自己保证
    

    https://blog.csdn.net/dhaiuda/article/details/80623150

    应用场景:
    DNS域名解析就是使用UDP(绝大多数)
    
    qq聊天功能实际上就是利用udp。因为如果聊天功能使用TCP传输则当和多个人聊天时,需要建立多条连接,持续占用。
    但是类似的聊天功能会在代码层面进行传输检查,所以我们可以知道某些信息发送失败了。
    
    多播、广播
    

    编程基础

    socket

    socket是应用层与传输层通信的中间软件抽象层,它是一组接口。把复杂的TCP/IP协议族隐藏在socket接口后面,用户只需要操作简单的接口即可,socket会去组织数据,以符合指定的协议。

    socket 套接字 (ip + port)

    • AF_UNIX : 【少用】(在unix中一切皆文件)基于文件的套接字。两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信。
    • AF_INET : 基于网络(ipv4)。还有AF_INET6被用于ipv6。

    socket建立tcp连接(基于数据流)

    image
    • server.py
    import socket
    sk = socket.socket()        # 实例化一个socket
    # 有些操作系统可能在端口使用完后不会马上回收,当重启服务时可能会导致端口占用。
    # sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSERADDR, 1)
    sk.bind(('127.0.0.1', 8980))# 绑定ip、端口. 注:以元组的形式
    sk.listen()                 # 监听
    
    conn, addr = sk.accept()    # 阻塞等待连接, 获得连接对象、对方的地址(接口内部完成三次握手)
    
    ret = conn.recv(1024)       # 阻塞等待接受数据流 1024字节
    conn.send(b'hi')            # 传送数据,只支持字节
    
    conn.close()                # 关闭连接(接口内部完成四次挥手)
    
    sk.close()                  # 关闭套接字
    
    • client.py
    import socket
    sk = socket.socket()        # 实例化一个socket
    sk.connect(('127.0.0.1', 8980))  # 绑定想连接的ip、端口
    
    # client端不需要获取连接对象
    sk.send(b'hello')           # 发送数据
    ret = sk.recv()             # 接受数据
    
    sk.close()
    

    黏包现象:由于面向连接,例当recv(1024)没接收完当前数据,可能会导致本次剩余的数据到下一次recv接收。(基于tcp的拆包分组机制)

    当server与client1连通后,执行另一个client2连接。由于tcp的面向连接的,当前连接会占用资源,此时client2会进入等待状态,直至server断开与client1的连接,则client2才能与server建立连接。

    解决黏包问题

    为什么会出现黏包现象?

    首先只有在TCP协议中才会出现黏包现象,是因为TCP协议是面向流的协议,在发送数据传输过程中还有缓存机制来避免数据丢失。
    因此,在连续发送小数据的时候、以及接收大小不符的时候都容易出现黏包现象。
    本质还是因为我们在接收数据的时候不知道发送的数据长短。
    

    怎么解决问题?

    在传输大量数据之前先告诉接收端要发送的数据大小
    如果要优化的话,可以使用struct模块来定制协议。
    

    黏包例子:

    # 第一次接收
    ret = conn.recv(2)
    # 第二次接收
    ret = conn.recv(10)
    

    当第一次接收未完成时,剩下的数据(存放在接收端的缓存区)可能会跟着第二次接收的内容一起被接收。导致数据错乱。

    同理可能还会出现: (tcp传输会进行切片,如果两数据较小,可能会组成一块,一起发送。)

    # client 连续send 若中间有其他操作 可能就不会出现
    conn.send(b'ab')
    conn.send(b'c')
    
    # server
    ret = conn.recv(10)
    ret.encode() # abc
    

    所以问题的本质是:不知道接收的数据大小导致了黏包。

    则解决方法是:先发送数据的大小(len()),接收端按长度接收。

    # 发生端 
    sk.send(str(len(s1)+len(s2).encode())
    sk.recv(1024)   # ok
    sk.send(s1)
    sk.send(s2)
    
    # 接收端
    num = conn.recv(1024).decode()
    conn.send(b'ok')
    res = conn.recv(int(num)).decode()
    

    中间多加一步ok是因为,接收端不知道num的长度,如果发送方直接发送数据,可能会和num的包一起发送出去,又造成黏包现象。

    • 优点:确定了每次要接收数据的大小
    • 缺点:多了一次交互

    以上多一次交互的原因是由于不知道数据长度的长度(num的长度)。使用struct优化。

    • struct:可以把一种类型转化为固定长度的byte(例如本处需要把数字转化)
    # 发送端
    import struct
    num = str(len(s1)+len(s2)
    num_byte = struct.pack('i', num)
    sk.send(num_byte)   # 直接连续3次send
    sk.send(s1)     
    sk.send(s2)
    
    # 接收端
    num = conn.recv(4)  # 已知固定长度为4
    num = struct.unpack('i', num)[0]    # 同样的方式解开,得到数据的长度
    res = conn.recv(int(num)).decode()
    
    image
    会连续send吗?

    连续send的情况还是比较常见的,例如传输大文件时,都是边读边传,边收边写。以下情况是双方都固定4096个字节,所以不会发生黏包现象。

    # rb 按字节读
    f = open(filename, 'rb')
    while True:
        # 缓存大小为4096字节
        msg = f.read(4096)
        if not msg:
            break
        cliSockfd.sendall(msg)
    f.close()
    time.sleep(1)
    cliSockfd.sendall('EOF'.encode())
    
    f = open(filename, 'wb')
    while True:
        msg = cliSockfd.recv(4096).decode()
        if msg == 'EOF':
            print('recv file success!')
            break
        f.write(msg.encode())
    f.close
    

    socket建立udp连接(基于数据包)

    image
    • server.py
    # udp不需要监听、建立连接
    import socket
    sk = socket.socket(type=socket.SOCK_DGRAM)        # datagram 数据报文
    sk.bind(('127.0.0.1', 8980))
    
    # 需要先被动等待
    msg, addr = sk.recvfrom(1024)
    # 发送时要带上源地址
    sk.sendto(b'bye', addr)
    
    • client.py
    import socket
    sk = socket.socket(type=socket.SOCK_DGRAM)
    ip_port = ('127.0.0.1', 8980)
    
    sk.sendto(b'hello', ip_port)
    msg,addr = sk.recvfrom(1024)
    
    sk.close()
    

    udp不会出现黏包现象,但是有可能丢包

    进阶编程

    简单客户端验证

    import hmac     # 类似hashilib
    h = hmac.new()
    密文 = h.digest()
    hmac.compare_digest(x1, x2)   # 对比 密文-另外一个密文
    

    双方已知的密钥的前提下,双方都通过密钥加密同一数据,通过对比是否相同,从而判断是否过验。

    import socket
    import hmac
    secret_key = 'egg'
    sk = socket.socket()
    sk.bind(('127.0.0.1', 8980))
    sk.listen()
    def check_conn(conn):
        msg = os.urandom(32)    # 生成随机32位字符
        conn.send(msg)          # 发送给客户端进行加密
        h = hmac.new(secret_key, msg)   # 服务端自己加密
        digest = h.digest()         # 获得密文
        client_digest = conn.recv(1024) # 获取客户端加密的密文
        return hmac.compare_digest(digest, client_digest)   # 对比两密文是否一致
    
    
    # 客户端
    import hmac
    import socket
    secret_key = 'egg'          # 同一密钥
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8980))
    msg = sk.recv(1024)
    h = hmac.new(secret_key, msg)
    digest = h.digest()
    sk.send
    
    sk.close()
    

    FTP和SMTP

    1. 我们已经有了FTP后,为何在邮件服务器之间传输邮件(邮件也是一种文件)时,还需要SMTP协议?以及为何需要HTTP协议

    SMTP 是一种提供可靠且有效电子邮件传输的协议。 SMTP 是建模在 FTP 文件传输服务上的一种邮件服务,主要用于传输系统之间的邮件信息并提供来信有关的通知。

    SMTP 独立于特定的传输子系统,且只需要可靠有序的数据流信道支持。 SMTP 重要特性之一是其能跨越网络传输邮件,即“ SMTP 邮件中继”。通常,一个网络可以由公用互联网上 TCP 可相互访问的主机、防火墙分隔的 TCP/IP 网络上 TCP 可相互访问的主机,及其它 LAN/WAN 中的主机利用非 TCP 传输层协议组成。使用 SMTP ,可实现相同网络上处理机之间的邮件传输,也可通过中继器或网关实现某处理机与其它网络之间的邮件传输。

    在这种方式下,邮件的发送可能经过从发送端到接收端路径上的大量中间中继器或网关主机。域名服务系统(DNS)的邮件交换服务器可以用来识别出传输邮件的下一跳 IP 地址。

    应用

    用Python实现PING:https://www.jianshu.com/p/14113212cd18

    引用

    link

    相关文章

      网友评论

          本文标题:网络编程基础(网络层+传输层)

          本文链接:https://www.haomeiwen.com/subject/tzjekctx.html