1.C/S B/S架构

C: client端

B: browse 浏览器

S: server端

C/S架构: 基于客户端与服务端之间的通信

​ QQ, 游戏,皮皮虾, 快手,抖音.

​ 优点: 个性化设置,响应速度快,

​ 缺点: 开发成本,维护成本高,占用空间,用户固定.

B/S架构: 基于浏览器与服务端之间的通信

​ 谷歌浏览器,360浏览器,火狐浏览器等等.

​ 优点: 开发维护成本低,占用空间相对低,用户不固定.

​ 缺点: 功能单一,没有个性化设置,响应速度相对慢一些.

2.网络通信原理

转回互联网通信:

​ 我现在想和美国的一个girl联系.你如何利用计算机联系???

  1. 两台计算机要有一堆物理连接介质连接.
  2. 找到对方计算机软件位置.
  3. 遵循一揽子互联网通信协议.

路由器

DHCP 协议

分发ip地址 网关

3.osi七层协议

功能 数据单元
应用层 网络进程到应用程序。针对特定应用规定各层协议、时序、表示等,进行封装 。在端系统中用软件来实现,如HTTP等 Data(数据) 主机层
表示层 数据表示形式,加密和解密,把机器相关的数据转换成独立于机器的数据。规定数据的格式化表示 ,数据格式的转换等
会话层 主机间通讯,管理应用程序之间的会话。规定通信时序 ;数据交换的定界、同步,创建检查点等
传输层 在网络的各个节点之间可靠地分发数据包。所有传输遗留问题;复用;流量;可靠o Segments(数据段)
网络层 在网络的各个节点之间进行地址分配、路由和(不一定可靠的)分发报文。路由( IP寻址);拥塞控制。 Datagram网络分组/数据报文 媒介层
数据链路层 一个可靠的点对点数据直链。检错与纠错(CRC码);多路访问;寻址 Bit/Frame(数据帧)
物理层 一个(不一定可靠的)点对点数据直链。定义机械特性;电气特性;功能特性;过程特性 Bit(比特)

简单串联五层协议以及作用

1.物理层

物理层指的就是网线,光纤,双绞线等等物理连接介质

物理层发送的是比特流: 01010101010101010101

数据应该有规律的分组,分组是数据链路层做的事情.

2.数据链路层

传输以“帧”为单位的数据包

定义了如何让格式化数据以进行传输,以及如何让控制对物理介质的访问,这一层通常还提供错误检测和纠正,以确保数据的可靠传输。

以太网协议: 对比数据进行分组.一组称之为一帧,数据报

一组数据01010101 叫做一帧,数据报.

​ head | data(晚上约么)

head是固定的长度:18个字节

​ 源地址: 6个字节

​ 目标地址: 6个字节

​ 数据类型: 6个字节

data: 最少是46个字节,最大1500字节.

一帧数据: 最少64个字节,最大1518个字节.

mac地址: 就是你的计算机上网卡上标注的地址.

12位16进制数组成 :前六位是厂商编号,后六位是流水线号.源mac地址 目标mac地址 数据类型 | data'1C-1B-0D-A4-E6-44'计算机的通信方式:同一个局域网内,通过广播的形式通信.计算机只能在局域网内进行广播: 范围大了 广播风暴,效率极低.

3.网络层

路由选择,点到点:IP寻址,通过IP连接网络上的计算机

为数据包选择路由

IP协议: 确定局域网(子网)的位置.

找到具体软件的位置,上一层的事情

4.传输层:

端口协议: 确定软件在计算机的位置

5.应用层:

提供应用接口,为用户直接提供各种网络服务。

广播(局域网内) + mac地址(计算机位置) + ip(局域网的位置) + 端口(软件在计算机的位置)

有了以上四个参数:你就可以确定世界上任何一个计算机的软件的位置.

6.对五层协议详细的补充说明

数据链路层补充:

同一个局域网通过广播的形式发送数据.

交换机的mac地址学习功能:

一个交换机的5个接口: 5个计算机.

1: FF-FF-FF-FF-FF-FF

2: FF-FF-FF-FF-FF-FF

3: FF-FF-FF-FF-FF-FF

4: FF-FF-FF-FF-FF-FF

5: FF-FF-FF-FF-FF-FF

接口1: 源mac 1C-1B-0D-A4-E6-44 目标1C-1C-0D-A4-E5-44 |数据 以广播的形式发出

2,3,4,5口都会接收到消息,5口是最终的目标地址,交换机就会将5口与mac地址对应上.

1: 1C-1B-0D-A4-E6-44

2: FF-FF-FF-FF-FF-FF

3: FF-FF-FF-FF-FF-FF

4: FF-FF-FF-FF-FF-FF

5: 1C-1C-0D-A4-E5-44

当五个口都对应上具体的mac地址,2口再次发消息,就不会广播了,就会以单播发送.

我们的前提是什么?

你必须知道对方的mac地址你才可以以广播的形式发消息.实际上,网络通信中,你只要知道对方的IP与自己的IP即可.

网络层:

IP协议:

ip地址:四段分十进制 192.168.0.12  取值范围 0~255.0~255.0~255.0~255子网掩码: C类子网掩码: 255.255.255.0ip地址 + 子网掩码 按位与运算 计算出是否在统一局域网(子网,网段).

计算172.16.10.1 与 172.16.10.128

​ 172.16.10.1:10101100.00010000.00001010.00000001

255.255.255.0: 11111111.11111111.11111111.00000000

从属于的局域网: 172.16.10.0

172.16.10.128:10101100.00010000.00001010.10000000

255.255.255.0: 11111111.11111111.11111111.00000000

从属于的局域网: 172.16.10.0

172.16.10.1 ~172.16.10.255

C类子网掩码 一个网段最多可以承载多个IP地址?

172.16.10.0 网段地址172.16.10.255 广播地址 172.16.10.1 被占用.通常被路由器默认使用253台计算机.

如果你要想给另一个计算机发数据, 你一定要知道对方的ip地址.

7.ARP协议

:通过对方的ip地址获取到对方的mac地址.

 源码mac  目标mac   源IP    目标IP    数据1C-1B-0D-A4-E6-44  FF:FF:FF:FF:FF:FF 172.16.10.13 172.16.10.156    数据第一次发消息: 发送到交换机 ---> 路由器  广播的形式发出去目标计算机收到消息:就要回消息:源码mac  目标mac   源IP    目标IP    数据1B-1B-0D-A4-E6-54  1C-1B-0D-A4-E6-44 172.16.10.156 172.16.10.13    数据

总结:

前提:知道目标mac:

    计算机A 发送一个消息给 计算机B ​       源码mac  目标mac   源IP    目标IP    数据单播的形式发送到交换机,交换机会检测自己的对照表有没有目标mac,如果有,单播传.如果没有,交由上一层: 路由器:路由器收到消息: 对消息进行分析: 要确定目标计算机与本计算机是否在同一网段,​   如果在同一网段,直接发送给对应的交换机,交换机在单播发给目标mac.​   如果不是在同一网段: ?

前提:不知道目标mac:

    计算机A 发送一个消息给 计算机B ​       源码mac  目标mac不知道   源IP    目标IP    数据​   单播的形式发送到交换机,交换机交由上一层路由器:路由器收到消息: 对消息进行分析: 要确定目标计算机与本计算机是否在同一网段,​   如果在同一网段通过 IP以及ARP协议获取到对方的mac地址,然后在通信.​   如果不是在同一网段: ?

传输层:

端口协议: UDP协议,TCP协议

65535端口1~1024操作系统专门使用的端口举例: 3306 数据库自己开发软件都是8080以后的端口号

UDP TCP 协议

TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、流式协议, 传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;文件传输程序。UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文(数据包),尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。

4.TCP协议的三次握手和四次挥手

syn洪水攻击:制造大量的假的无效的IP请求服务器.致使正常的IP访问不了服务器.

5.socket套接字

scoket是什么?

scoket处于应用层和传输层之间,提供了一些简单的接口,避免了与操作系统直接对接,省去了相当复杂繁琐的工作

看socket之前,先来回顾一下五层通讯流程:

但实际上从传输层开始以及以下,都是操作系统帮咱们完成的,下面的各种包头封装的过程,用咱们去一个一个做么?NO!

Socket又称为套接字,它是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。当我们使用不同的协议进行通信时就得使用不同的接口,还得处理不同协议的各种细节,这就增加了开发的难度,软件也不易于扩展(就像我们开发一套公司管理系统一样,报账、会议预定、请假等功能不需要单独写系统,而是一个系统上多个功能接口,不需要知道每个功能如何去实现的)。于是UNIX BSD就发明了socket这种东西,socket屏蔽了各个协议的通信细节,使得程序员无需关注协议本身,直接使用socket提供的接口来进行互联的不同主机间的进程的通信。这就好比操作系统给我们提供了使用底层硬件功能的系统调用,通过系统调用我们可以方便的使用磁盘(文件操作),使用内存,而无需自己去进行磁盘读写,内存管理。socket其实也是一样的东西,就是提供了tcp/ip协议的抽象,对外提供了一套接口,同过这个接口就可以统一、方便的使用tcp/ip协议的功能了。

  其实站在你的角度上看,socket就是一个模块。我们通过调用模块中已经实现的方法建立两个进程之间的连接和通信。也有人将socket说成ip+port,因为ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序。 所以我们只要确立了ip和port就能找到一个应用程序,并且使用socket模块来与之通信。

基于文件类型的套接字家族

套接字家族的名字:AF_UNIX

unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信

基于网络类型的套接字家族

套接字家族的名字:AF_INET

(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)

6. TCP和UDP对比

TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;文件传输程序。

UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文(数据包),尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。

7. TCP协议下的socket

个生活中的场景。你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。 生活中的场景就解释了这工作原理。

先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束

细说socket()模块函数用法

import socket
socket.socket(socket_family,socket_type,protocal=0)socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默认值为 0。获取tcp/ip套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)获取udp/ip套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)由于 socket 模块中有太多的属性。我们在这里破例使用了'from module import *'语句。使用 'from socket import *',我们就把 socket 模块里的所有属性都带到我们的命名空间里了,这样能 大幅减短我们的代码。
例如tcpSock = socket(AF_INET, SOCK_STREAM)
服务端套接字函数
s.bind()    绑定(主机,端口号)到套接字
s.listen()  开始TCP监听
s.accept()  被动接受TCP客户的连接,(阻塞式)等待连接的到来客户端套接字函数
s.connect()     主动初始化TCP服务器连接
s.connect_ex()  connect()函数的扩展版本,出错时返回出错码,而不是抛出异常公共用途的套接字函数
s.recv()            接收TCP数据
s.send()            发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall()         发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom()        接收UDP数据
s.sendto()          发送UDP数据
s.getpeername()     连接到当前套接字的远端的地址
s.getsockname()     当前套接字的地址
s.getsockopt()      返回指定套接字的参数
s.setsockopt()      设置指定套接字的参数
s.close()           关闭套接字面向锁的套接字方法
s.setblocking()     设置套接字的阻塞与非阻塞模式
s.settimeout()      设置阻塞套接字操作的超时时间
s.gettimeout()      得到阻塞套接字操作的超时时间面向文件的套接字的函数
s.fileno()          套接字的文件描述符
s.makefile()        创建一个与该套接字相关的文件

1.low版server

import  socket# 创建实例
# 默认AF_INET,SOCK_STREAM可以不填写
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#绑定IP地址和端口
phone.bind(("127.0.0.1",8078))  #必须元组的形式发送#绑定监听
phone.listen(5)#等待客户连接,阻塞中
conn,addr = phone.accept()  #接受收管道,和ip地址+端口号#最多接受1024个字节
client_date = conn.recv(1024) # python3.x以上的版本。网络数据的发送接受都是byte类型。
# 如果发送的数据是str类型则需要进行编解码
print(f"来自客户端的消息{client_date.decode('utf-8')}")# 给客户端返回数据
conn.send(client_date.upper())#主动关闭连接
conn.close()
phone.close()
import  socket# 创建实例
# 默认AF_INET,SOCK_STREAM可以不填写
phone = socket.socket(socket.AF_INET)  # 定义绑定的ip和port
phone.connect(("127.0.0.1",8078)) #必须元组的形式# 给服务器发送数据
date = input("请输入>>>")
phone.send(date.encode("utf-8"))#获取从服务端发过来的数据
server_date = phone.recv(1024)
print(f"来自服务端的{server_date.decode('utf-8')}")#关闭连接
phone.close()

2.通信循环版本

import  socket#买电话
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  #默认是基于TCP协议的socket.SOCK_STREAM可以不写
phone.bind(("localhost",8078)) #绑定IP地址和端口#开机监听
phone.listen(5)#等待连接
conn, addr = phone.accept()  # 阻塞 接听获取链接,和ip地址+端口号while 1:try:from_client_date = conn.recv(1024)  # 最多接受1024个字节 阻塞if from_client_date.decode('utf-8') == "q":breakprint(f"来自客户端的消息{from_client_date.decode('utf-8')}")route=input("请输入")conn.send(route.encode("utf-8"))except Exception:breakconn.close()
phone.close()
import  socket#买电话
phone = socket.socket(socket.AF_INET)  #m默认是基于TCP协议的socket#拨号打电话
phone.connect(("localhost",8078))
while 1:date = input("请输入>>>")if not date:print("发送内容不能为空")continuephone.send(date.encode("utf-8"))if date == "q":breakfrom_server_date = phone.recv(1024)print(f"来自服务端的{from_server_date.decode('utf-8')}")#关闭电话
phone.close()

3.通讯,连接循坏

import  socket#买电话
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  #默认是基于TCP协议的socket.SOCK_STREAM可以不写
phone.bind(("localhost",8078)) #绑定IP地址和端口#开机监听
phone.listen(5)#等待连接while 1:conn, addr = phone.accept()  # 阻塞 接听获取链接,和ip地址+端口号print(addr)while 1:try:from_client_date = conn.recv(1024)  # 最多接受1024个字节 阻塞if from_client_date.decode('utf-8') == "q":breakprint(f"来自客户端的消息{from_client_date.decode('utf-8')}")route=input("请输入")conn.send(route.encode("utf-8"))except Exception:breakconn.close()
phone.close()

4.模拟远程xshell操作服务器

import  socket
import  subprocess
#买电话
phone = socket.socket()  #默认是基于TCP协议的socket.SOCK_STREAM可以不写
phone.bind(("localhost",8078)) #绑定IP地址和端口需要#开机监听
phone.listen(2)while 1:# 等待连接conn, addr = phone.accept()  # 阻塞 接听获取链接,和ip地址+端口号print(addr)while 1:try:from_client_date = conn.recv(1024)  # 最多接受1024个字节 阻塞if from_client_date.decode('utf-8') == "q":breakobj = subprocess.Popen(f'{from_client_date.decode("utf-8")}',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,)correct = obj.stdout.read()  # 正确命令error = obj.stderr.read() # 错误命令conn.send(correct + error)except ConnectionResetError:print("断开连接")breakconn.close()
phone.close()
import  socket#买电话
phone = socket.socket(socket.AF_INET)  #m默认是基于TCP协议的socket#拨号打电话
phone.connect(("localhost",8078))
while 1:date = input("请输入>>>")if not date:print("发送内容不能为空")continuephone.send(date.encode("utf-8"))if date == "q":breakfrom_server_date = phone.recv(1024)print(f"来自服务端的{from_server_date.decode('gbk')}")#关闭电话
phone.close()

8.粘包

讲粘包之前先看看socket缓冲区的问题:

须知:只有TCP有粘包现象,UDP永远不会粘包!

1.操作系统的缓存区

优点:

  1. 暂时存储一些数据.
  2. 缓冲区存在如果你的网络波动,保证数据的收发稳定,匀速.

缺点: 造成了粘包现象之一.

2.什么情况下出现粘包

1. 接收方没有及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

连续短暂的send多次(数据量很小),你的数据会统一发送出去
2 .发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据也很小,会合到一起,产生粘包)

9.如何解决粘包现象

解决粘包现象的思路:

服务端发一次数据 10000字节,
客户端接收数据时,循环接收,每次(至多)接收1024个字节,直至将所有的字节全部接收完毕.将接收的数据拼接在一起,最后解码.
  1. 遇到的问题: recv的次数无法确定.

    你发送总具体数据之前,先给我发一个总数据的长度:5000个字节。然后在发送总数据。

    客户端: 先接收一个长度。 5000个字节。

    然后我再循环recv 控制循环的条件就是只要你接受的数据< 5000 一直接收。

  2. 遇到的问题: 总数据的长度转化成的字节数不固定

10.粘包的解决方案:

先介绍一下struct模块:

该模块可以把一个类型,如数字,转成固定长度的bytes

Format C Type Python 字节数
x pad byte no value 1
c char string of length 1 1
b signed char integer 1
B unsigned char integer 1
? _Bool bool 1
h short integer 2
H unsigned short integer 2
i int integer 4
I unsigned int integer 4
l long integer 4
L unsigned long integer 4
q long long integer 8
Q unsigned long long integer 8
f float float 4
d double float 8
s char[] string
p char[] string
P void * integer

struct用法

import struct
# 将一个数字转化成等长度的bytes类型。
ret = struct.pack('i', 183346)
print(ret, type(ret), len(ret))# 通过unpack反解回来
ret1 = struct.unpack('i',ret)[0]
print(ret1, type(ret1), len(ret1))# 但是通过struct 处理不能处理太大ret = struct.pack('l', 4323241232132324)
print(ret, type(ret), len(ret))  # 报错

1.方案一:low版本

题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总数按照固定字节发送给接收端后面跟上总数据,然后接收端先接收固定字节的总字节流,再来一个死循环接收完所有数据。

import socket
import subprocess
import struct
phone = socket.socket()phone.bind(('127.0.0.1',8848))phone.listen(2)
# listen: 2 允许有两个客户端加到半链接池,超过两个则会报错while 1:conn,addr = phone.accept()  # 等待客户端链接我,阻塞状态中# print(f'链接来了: {conn,addr}')while 1:try:from_client_data = conn.recv(1024)  # 接收命令if from_client_data.upper() == b'Q':print('客户端正常退出聊天了')breakobj = subprocess.Popen(from_client_data.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,)result = obj.stdout.read() + obj.stderr.read() #win10 得到了gbk字节total_size = len(result)print(f'总字节数:{total_size}')# 1. 制作固定长度的报头head_bytes = struct.pack('i',total_size) #将一个数字转化成等长度的bytes类型。# 2. 发送固定长度的报头conn.send(head_bytes) #发送的是4# 3. 发送总数据conn.send(result)except ConnectionResetError:print('客户端链接中断了')breakconn.close()
phone.close()
import socket
import struct
phone = socket.socket()phone.connect(('127.0.0.1',8848))
while 1:to_server_data = input('>>>输入q或者Q退出').strip().encode('utf-8')if not to_server_data:# 服务端如果接受到了空的内容,服务端就会一直阻塞中,所以无论哪一端发送内容时,都不能为空发送print('发送内容不能为空')continuephone.send(to_server_data)if to_server_data.upper() == b'Q':break# 1. 接收报头head_bytes = phone.recv(4)# 2. 反解报头total_size = struct.unpack('i',head_bytes)[0]total_data = b''while len(total_data) < total_size:total_data += phone.recv(1024)print(len(total_data))print(total_data.decode('gbk'))phone.close()

2.方案二,可自定制报头版

整个流程的大致解释:
我们可以把报头做成字典,字典里包含将要发送的真实数据的描述信息(大小啊之类的),然后json序列化,然后用struck将序列化后的数据长度打包成4个字节。
我们在网络上传输的所有数据 都叫做数据包,数据包里的所有数据都叫做报文,报文里面不止有你的数据,还有ip地址、mac地址、端口号等等,其实所有的报文都有报头,这个报头是协议规定的,看一下

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

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

服务端

# FTP 应用层自定义协议
'''
1. 高大上版: 自定制报头
dic = {'filename': XX, 'md5': 654654676576776, 'total_size': 26743}
2. 高大上版:可以解决文件过大的问题.'''import socket
import subprocess
import struct
import json
phone = socket.socket()phone.bind(('127.0.0.1',8848))phone.listen(2)
# listen: 2 允许有两个客户端加到半链接池,超过两个则会报错while 1:conn,addr = phone.accept()  # 等待客户端链接我,阻塞状态中# print(f'链接来了: {conn,addr}')while 1:try:from_client_data = conn.recv(1024)  # 接收命令if from_client_data.upper() == b'Q':print('客户端正常退出聊天了')breakobj = subprocess.Popen(from_client_data.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,)result = obj.stdout.read() + obj.stderr.read()total_size = len(result)# 1. 自定义报头head_dic = {'file_name': 'test1','md5': 6567657678678,'total_size': total_size,}# 2. json形式的报头head_dic_json = json.dumps(head_dic)  #字符转# 3. bytes形式报头head_dic_json_bytes = head_dic_json.encode('utf-8')# 4. 获取bytes形式的报头的总字节数len_head_dic_json_bytes = len(head_dic_json_bytes)# 5. 将不固定的int总字节数变成固定长度的4个字节four_head_bytes = struct.pack('i',len_head_dic_json_bytes)# 6. 发送固定的4个字节conn.send(four_head_bytes)# 7. 发送报头数据conn.send(head_dic_json_bytes)# 8. 发送总数据conn.send(result)except ConnectionResetError:print('客户端链接中断了')breakconn.close()
phone.close()

客户端

import socket
import struct
import json
phone = socket.socket()phone.connect(('127.0.0.1',8848))
while 1:to_server_data = input('>>>输入q或者Q退出').strip().encode('utf-8')if not to_server_data:# 服务端如果接受到了空的内容,服务端就会一直阻塞中,所以无论哪一端发送内容时,都不能为空发送print('发送内容不能为空')continuephone.send(to_server_data)if to_server_data.upper() == b'Q':break# 1. 接收固定长度的4个字节head_bytes = phone.recv(4)# 2. 获得bytes类型字典的总字节数len_head_dic_json_bytes = struct.unpack('i',head_bytes)[0]# 3. 接收bytes形式的dic数据head_dic_json_bytes = phone.recv(len_head_dic_json_bytes)# 4. 转化成json类型dic(字符串)head_dic_json = head_dic_json_bytes.decode('utf-8')# 5. 转化成字典形式的报头head_dic = json.loads(head_dic_json)'''head_dic = {'file_name': 'test1','md5': 6567657678678,'total_size': total_size,}'''total_data = b''while len(total_data) < head_dic['total_size']:total_data += phone.recv(1024)# print(len(total_data))print(total_data.decode('gbk'))phone.close()

11.详解recv的工作原理

源码解释:
Receive up to buffersize bytes from the socket.
接收来自socket缓冲区的字节数据,
For the optional flags argument, see the Unix manual.
对于这些设置的参数,可以查看Unix手册。
When no data is available, block untilat least one byte is available or until the remote end is closed.
当缓冲区没有数据可取时,recv会一直处于阻塞状态,直到缓冲区至少有一个字节数据可取,或者远程端关闭。
When the remote end is closed and all data is read, return the empty string.
关闭远程端并读取所有数据后,返回空字符串。

总结

服务端缓冲区数据没有取完,又执行了recv执行,recv会继续取值。
服务端缓冲区取完了,又执行了recv执行,此时客户端20秒内不关闭的前提下,recv处于阻塞状态
验证服务端缓冲区取完了,又执行了recv执行,此时客户端处于关闭状态,则recv会取到空字符串。

代码如下

'''
源码解释:
Receive up to buffersize bytes from the socket.
接收来自socket缓冲区的字节数据,
For the optional flags argument, see the Unix manual.
对于这些设置的参数,可以查看Unix手册。
When no data is available, block untilat least one byte is available or until the remote end is closed.
当缓冲区没有数据可取时,recv会一直处于阻塞状态,直到缓冲区至少有一个字节数据可取,或者远程端关闭。
When the remote end is closed and all data is read, return the empty string.
关闭远程端并读取所有数据后,返回空字符串。
'''
----------服务端------------:
# 1,验证服务端缓冲区数据没有取完,又执行了recv执行,recv会继续取值。import socketphone =socket.socket(socket.AF_INET,socket.SOCK_STREAM)phone.bind(('127.0.0.1',8080))phone.listen(5)conn, client_addr = phone.accept()
from_client_data1 = conn.recv(2)
print(from_client_data1)
from_client_data2 = conn.recv(2)
print(from_client_data2)
from_client_data3 = conn.recv(1)
print(from_client_data3)
conn.close()
phone.close()# 2,验证服务端缓冲区取完了,又执行了recv执行,此时客户端20秒内不关闭的前提下,recv处于阻塞状态。import socketphone =socket.socket(socket.AF_INET,socket.SOCK_STREAM)phone.bind(('127.0.0.1',8080))phone.listen(5)conn, client_addr = phone.accept()
from_client_data = conn.recv(1024)
print(from_client_data)
print(111)
conn.recv(1024) # 此时程序阻塞20秒左右,因为缓冲区的数据取完了,并且20秒内,客户端没有关闭。
print(222)conn.close()
phone.close()# 3 验证服务端缓冲区取完了,又执行了recv执行,此时客户端处于关闭状态,则recv会取到空字符串。import socketphone =socket.socket(socket.AF_INET,socket.SOCK_STREAM)phone.bind(('127.0.0.1',8080))phone.listen(5)conn, client_addr = phone.accept()
from_client_data1 = conn.recv(1024)
print(from_client_data1)
from_client_data2 = conn.recv(1024)
print(from_client_data2)
from_client_data3 = conn.recv(1024)
print(from_client_data3)
conn.close()
phone.close()
------------客户端------------
# 1,验证服务端缓冲区数据没有取完,又执行了recv执行,recv会继续取值。
import socket
import time
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))
phone.send('hello'.encode('utf-8'))
time.sleep(20)phone.close()# 2,验证服务端缓冲区取完了,又执行了recv执行,此时客户端20秒内不关闭的前提下,recv处于阻塞状态。
import socket
import time
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))
phone.send('hello'.encode('utf-8'))
time.sleep(20)phone.close()# 3,验证服务端缓冲区取完了,又执行了recv执行,此时客户端处于关闭状态,则recv会取到空字符串。
import socket
import time
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))
phone.send('hello'.encode('utf-8'))
phone.close()

12.UDP协议下的socket

UDP下的socket通讯流程

  先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),recvform接收消息,这个消息有两项,消息内容和对方客户端的地址,然后回复消息时也要带着你收到的这个客户端的地址,发送回去,最后关闭连接,一次交互结束

简单版本

import socket
server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# 基于网络的UDP协议的socket
server.bind(('127.0.0.1',9000))while 1:from_client_data = server.recvfrom(1024)  # 阻塞,等待客户来消息print(f'\033[1;35;0m来自客户端{from_client_data[1]}: {from_client_data[0].decode("utf-8")} \033[0m')# to_client_data = input('>>>').strip()# server.sendto(to_client_data.encode('utf-8'),from_client_data[1])# 1. 基于udp协议的socket无须建立管道,先开启服务端或者客户端都行.
# 2. 基于udp协议的socket接收一个消息,与发送一个消息都是无连接的.
# 3. 只要拿到我的ip地址和端口就都可以给我发消息,我按照顺序接收消息.
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# 基于网络的UDP协议的socketwhile 1:to_server_data = input('>>>:').strip()client.sendto(to_server_data.encode('utf-8'),('127.0.0.1',9000))# data,addr = client.recvfrom(1024)# print(f'来自服务端{addr}消息:{data.decode("utf-8")}')

13.socketserver实现并发

import socketserver #1、引入模块class MyServer(socketserver.BaseRequestHandler):#2、自己写一个类,类名自己随便定义,然后继承socketserver这个模块里面的BaseRequestHandler这个类def handle(self): #3、写一个handle方法,必须叫这个名字"""所有的业务逻辑基本都在这里:return:"""while 1:from_client_msg = self.request.recv(1024)#6、self.request 相当于一个connprint(from_client_msg.decode('utf-8'))  #7、收消息to_client_msg = input('某某技师说>>>').encode('utf-8')self.request.send(to_client_msg)  #8、发消息# pass# 拿到了我们对每个客户端的管道,那么我们自己在这个方法里面的就写我们接收消息发送消息的逻辑就可以了if __name__ == '__main__':server = socketserver.ThreadingTCPServer(('127.0.0.1',8002),MyServer)# 4、使用socketserver的ThreadingTCPServer这个类,将IP和端口的元祖传进去,还需要将上面咱们自己定义的类传进去,得到一个对象,相当于我们通过它进行了bind、listenserver.serve_forever()#5、使用我们上面这个类的对象来执行serve_forever()方法,他的作用就是说,我的服务一直开启着,就像京东一样,不能关闭网站,对吧,并且serve_forever()帮我们进行了accept
import socketclient = socket.socket()server_ip_port = ('127.0.0.1',8002)client.connect(server_ip_port)while 1:to_server_msg = input('给服务端的消息>>>').encode('utf-8')client.send(to_server_msg)from_server_msg = client.recv(1024).decode('utf-8')print('来自服务端的消息>>>',from_server_msg)

14.模拟FTP上传下载

low版本

import socket
import  struct
import  json
import osphone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind(("127.0.0.1",9000))
phone.listen(5)
file_position = r"d:\上传下载"conn,addr = phone.accept()#接收i
data = conn.recv(4)
#反解除要接收的总字节长度
head_dic_b_size = struct.unpack("i",data)[0]
#接收bytes数据
head_dic_b = conn.recv(head_dic_b_size)
#转回字符串格式
head_dic_json = head_dic_b.decode("utf-8")
#转回字典格式
head_dic = json.loads(head_dic_json)#合并地址
file_path  = os.path.join(file_position,head_dic["file_name"])with open(file_path,"wb")as f:data_size = 0while data_size < head_dic["file_size"]:clent_data = conn.recv(1024)f.write(clent_data)data_size += len(clent_data)conn.close()
phone.close()
import socket
import os
import json
import struct
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)phone.connect(("127.0.0.1",9000)) #建立连接#定制字典
file_info = {"file_path":r"C:\Users\追梦NAN\Desktop\all.txt","file_name":"all.txt","file_size":None
}
#确定文件大小
file_info["file_size"] =os.path.getsize(file_info["file_path"])#转化为json字符串
head_dic_json = json.dumps(file_info)
#再转换为bytes
head_dic_bytes = head_dic_json.encode("utf-8")
#将head_dic_bytes的大小转换为 i 4个字节
ret = struct.pack("i",len(head_dic_bytes))#发送头部的i
phone.send(ret)
#  发送head_dic_bytes
phone.send(head_dic_bytes)with open(file_info["file_path"],"rb") as f:data_size = 0while data_size < file_info["file_size"]:data = f.read(1024)data_size += len(data)phone.send(data)phone.close()

转载于:https://www.cnblogs.com/zdqc/p/11347312.html

python之网络部分相关推荐

  1. python网络部分

    python网络部分 1.基本概念 1.1 协议 网络协议的简称,是通信计算机双方必须共同遵守的同一组约定.只有遵守了计算机才能相互通信. HTTP / HTTPS协议: 协议的相关内容在此:http ...

  2. post获取重定向的链接 python_【转载】python面试基础知识(四) 网络部分

    最近,小编在整理python面试基础知识,看了很多博客.文章和咨询了一些大厂公司大牛.了解到,在python面试的时候,不仅要求你有项目经验,还要考试代码呢!今天,小编和大家分享一下python面试基 ...

  3. 刻意练习:Python基础 -- Task12. 模块

    背景 我们准备利用17天时间,将 "Python基础的刻意练习" 分为如下任务: Task01:变量.运算符与数据类型(1day) Task02:条件与循环(1day) Task0 ...

  4. 刻意练习:Python基础 -- Task11. 魔法方法

    背景 我们准备利用17天时间,将 "Python基础的刻意练习" 分为如下任务: Task01:变量.运算符与数据类型(1day) Task02:条件与循环(1day) Task0 ...

  5. 刻意练习:Python基础 -- Task09. else 与 with 语句

    背景 我们准备利用17天时间,将 "Python基础的刻意练习" 分为如下任务: Task01:变量.运算符与数据类型(1day) Task02:条件与循环(1day) Task0 ...

  6. 刻意练习:Python基础 -- Task08. 异常处理

    背景 我们准备利用17天时间,将 "Python基础的刻意练习" 分为如下任务: Task01:变量.运算符与数据类型(1day) Task02:条件与循环(1day) Task0 ...

  7. 刻意练习:Python基础 -- Task06. 字典与集合

    背景 我们准备利用17天时间,将 "Python基础的刻意练习" 分为如下任务: Task01:变量.运算符与数据类型(1day) Task02:条件与循环(1day) Task0 ...

  8. 技术图文:Python魔法方法之属性访问详解

    背景 今天在B站学习"零基础入门学习 Python"中的第45节"魔法方法:属性访问",这也是我们组织的 Python基础刻意练习活动 的学习任务,其中有这样的 ...

  9. 技术图文:Python描述符 (descriptor) 详解

    背景 今天在B站上学习"零基础入门学习Python"这门课程的第46讲"魔法方法:描述符",这也是我们组织的 Python基础刻意练习活动 的学习任务,其中有这 ...

最新文章

  1. python爬图片教程_python爬去妹子网整个图片资源教程(最详细版)
  2. 地图收敛心得170405
  3. Goods:注册页面保存User功能发送邮件以及激活功实现
  4. log4j.xml如何配置
  5. 她琴棋书画全能,还进入清华计算机系实验室,被赞智商太超群、能力过强悍...
  6. boost::mp11::mp_copy_if_q相关用法的测试程序
  7. char p[]与char *p的区别
  8. 论文笔记(Attentive Recurrent Social Recommendation)
  9. python变量域名_想尝试使用python进行域名分析,可是没有接触过python,想请教请教。...
  10. iOS底层探索之对象的本质和类的关联特性initIsa(下)
  11. SDI在自定义的工具栏上添加下拉控件
  12. 关于SN和PN的概念
  13. 机器人操作系统二 ROS2:设计、架构和野外使用 - 机器翻译
  14. ImageJ 中文教程(细胞计数)
  15. 档案管理系统,项目总结
  16. P5713 【深基3.例5】洛谷团队系统
  17. 纸牌游戏十点半c语言,纸牌游戏十点半之Excel版
  18. LDA模型中需要输入的数据格式
  19. 基于Cortex-A7架构的嵌入式linux ARM裸机开发<2>——LED灯闪烁(C版本)
  20. 空白设计手法在平面设计中有哪些作用

热门文章

  1. 移动端车牌识别:新能源车牌识别上线
  2. java.lang.NoSuchMethodError: No static method ..........
  3. 利用VBA在Excel中批量画图
  4. Lisseur Professionnel Ghd thé sont diurétiques et se traduira par nous
  5. win11玩游戏延迟_win11系统玩游戏卡顿性能提升小技巧
  6. java long型数据做余数运算_Java数据类型与运算符
  7. Web安全常见基本知识
  8. CSTC 2001 聪明的学生 BZOJ 2523 递归(类搜索,推理)
  9. Excel宏被禁用解决办法
  10. 微型计算机nuc 6i5syk,Intel Skylake NUC迷你机性能实测:15W的神器