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.0

ip地址 + 子网掩码 按位与运算 计算出是否在统一局域网(子网,网段).

计算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":
break
print(f"来自客户端的消息{from_client_date.decode('utf-8')}")
route=input("请输入")
conn.send(route.encode("utf-8"))
except Exception:
break conn.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("发送内容不能为空")
continue
phone.send(date.encode("utf-8"))
if date == "q":
break
from_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":
break
print(f"来自客户端的消息{from_client_date.decode('utf-8')}")
route=input("请输入")
conn.send(route.encode("utf-8"))
except Exception:
break conn.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":
break obj = 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("断开连接")
break conn.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("发送内容不能为空")
continue
phone.send(date.encode("utf-8"))
if date == "q":
break
from_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('客户端正常退出聊天了')
break obj = 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('客户端链接中断了')
break
conn.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('发送内容不能为空')
continue
phone.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('客户端正常退出聊天了')
break obj = 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('客户端链接中断了')
break
conn.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('发送内容不能为空')
continue
phone.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 socket phone =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 socket phone =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 socket phone =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协议的socket while 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 相当于一个conn
print(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、listen
server.serve_forever()
#5、使用我们上面这个类的对象来执行serve_forever()方法,他的作用就是说,我的服务一直开启着,就像京东一样,不能关闭网站,对吧,并且serve_forever()帮我们进行了accept
import socket

client = 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 os phone = 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 = 0
while 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 = 0
while data_size < file_info["file_size"]:
data = f.read(1024)
data_size += len(data)
phone.send(data) phone.close()

python之网络部分的更多相关文章

  1. 使用神经网络来识别手写数字【译】(三)- 用Python代码实现

    实现我们分类数字的网络 好,让我们使用随机梯度下降和 MNIST训练数据来写一个程序来学习怎样识别手写数字. 我们用Python (2.7) 来实现.只有 74 行代码!我们需要的第一个东西是 MNI ...

  2. Python GUI编程--Tkinter

    今天看到了GUI编程,书上推荐用wxPython,去官网上看了看,发现Windows的最高支持到2.7,我用的是3.4版本,咋办,用自带的库--Tkinter呗,它是Python的默认GUI库,几乎是 ...

  3. 基于Levenberg-Marquardt训练算法的BP网络Python实现

    经过一个多月的努力,终于完成了BP网络,参考的资料为: 1.Training feed-forward networks with the Marquardt algorithm 2.The Leve ...

  4. 《Python网络编程》学习笔记--从例子中收获的计算机网络相关知识

    从之前笔记的四个程序中(http://www.cnblogs.com/take-fetter/p/8278864.html),我们可以看出分别使用了谷歌地理编码API(对URL表示地理信息查询和如何获 ...

  5. python初学心得之一

    昨天开始接触并学习python,对python有了初步印象. 一.python主要应用方向 二.python语言类型 三.python2和3的主要区别 四.常见字符编码 五.Python语法初学  一 ...

  6. python基础知识点四

    网络编程(socket) 软件开发的架构: 两个程序之间通讯的应用大致通过从用户层面可以分为两种: 1是C/S,即客户端与服务端,为应用类的,比如微信,网盘等需要安装桌面应用的 2是B/S,即浏览器与 ...

  7. python 全栈开发,Day67(Django简介)

    昨日内容回顾 1. socket创建服务器 2. http协议: 请求协议 请求首行 请求方式 url?a=1&b=2 协议 请求头 key:value 请求体 a=1&b=2(只有p ...

  8. python 全栈开发,Day32(知识回顾,网络编程基础)

    一.知识回顾 正则模块 正则表达式 元字符 : . 匹配除了回车以外的所有字符 \w 数字字母下划线 \d 数字 \n \s \t 回车 空格 和 tab ^ 必须出现在一个正则表达式的最开始,匹配开 ...

  9. Python select 详解(转)

    I/O多路复用是在单线程模式下实现多线程的效果,实现一个多I/O并发的效果.看一个简单socket例子: import socket SOCKET_FAMILY = socket.AF_INET SO ...

随机推荐

  1. Nowcoder Monotonic Matrix ( Lindström–Gessel–Viennot lemma 定理 )

    题目链接 题意 : 在一个 n * m 的矩阵中放置 {0, 1, 2} 这三个数字.要求 每个元素 A(i, j) <= A(i+1, j) && A(i, j) <= ...

  2. pika 与 rabbitMQ 阻塞连接

    之前只是用celery, 这次用一下pika 参考rabbitMQ官网的python版,https://www.rabbitmq.com/tutorials/tutorial-one-python.h ...

  3. 51 Nod 1069 Nim游戏

    分析: a1 xor a2 xor a3 ... xor an !=0 则为必胜态 a1 xor a2 xor a3 ... xor an ==0 则为必败态 也就是说只要计算异或值,如果非零则A赢, ...

  4. 彩色图像--色彩空间 CMY(K)空间

    学习DIP第63天 转载请标明本文出处:***http://blog.csdn.net/tonyshengtan ***,出于尊重文章作者的劳动,转载请标明出处!文章代码已托管,欢迎共同开发:http ...

  5. sh_05_列表遍历

    sh_05_列表遍历 name_list = ["张三", "李四", "王五", "王小二"] # 使用迭代遍历列表 ...

  6. python类装饰器即__call__方法

    上一篇中我对学习过程中的装饰器进行了总结和整理,这一节简单整理下类装饰器 1.类中的__call__方法: 我们在定义好一个类后,实例化出一个对象,如果对这个对象以直接在后边加括号的方式进行调用,程序 ...

  7. python基础之流程控制

    流程控制之----if 流程控制,是指程序在运行时,个别的指令(或者是陈述.子程序)运行或者求值的顺序.人生道路上的岔口有很多,在每个路口都是一个选择,在每个路口加上一个标签,选择哪个就是满足哪个条件 ...

  8. pygame的常用模块

    加载图片: pygame.image.load("图片名称") eg:xiaojiejie = pygame.image.load("./data/a/o/l/t/i/p ...

  9. 项目配置 xml文件时 报错提示(The reference to entity "useSSL" must end with the ';' delimiter.)

    这次在配置xml文件时,出现错误提示( The reference to entity “useSSL” must end with the ‘;’ delimiter.) 报错行为 <prop ...

  10. Hidden的应用

    在写jsp中如果一个 请求的参数(例如:paramTypeCode)不能在另一个请求中使用,我们为了能让他在请求中使用可以利用隐藏域来表示,下面介绍他的用法: 1    <input type= ...