阅读目录
一、楔子
假如有两个python文件 a.py 和 b.py ,可以分别运行这两个py文件,当如果想要这两个py文件互相传递数据呢? 很简单,创建一个文件就行,a.py在文件中写入数据,b.py从文件中读取就行.
但是,问题是,如果这两个py文件不在同一台电脑呢?
二、软件开发机构
日常生活中我们使用过的涉及到两个程序之间通信的应用可以大致分为两种:
第一种是应用类:qq,微信,网盘等需要安装的应用;
第二种是web类:微博,博客园,百度等使用浏览器访问就可以使用的应用。
这两种应用又对应了两种不同的软件开发机构:
1.C/S架构
C/S:即Client/Server,客户端与服务器端架构.这种架构是从用户层面来划分的.
这里的客户端一般泛指客户端应用程序EXE,程序需要先安装后,才能运行在用户的电脑上,对用户的电脑操作系统环境依赖较大。
2.B/S架构
B/S:即Browser/Server,浏览器端与服务器端架构,这种架构也是从用户层面来划分的。
Browser浏览器,其实也是一种Client客户端,只是这个客户端不需要大家去安装应用程序,只需在浏览器上通过HTTP请求就能获得服务器端相关的资源(网页资源).
三.网络基础
OSI七层模型
一个计算机是由硬件,操作系统,应用软件三者组成,具备这三个条件,一台计算机就可以单机使用了,但是若想跟其他计算机联机通信,就需要上网了.
什么是互联网?
互联网的核心就是由一堆协议组成,协议就是标准,比如说世界范围内人的通信标准是英语,如果每个人都会英语,那么世界上的所有人都可以互相交流了,对计算机来说,协议就是标准,只有每台计算机都学会了协议,那么计算机之间就可以根据这个标准来通信了.
人们根据分工把互联网协议分成了一下层级:
每层常见的物理设备 和 协议
socket
socket层位于应用层下面,用于下面4层复杂的数据封装.
其实,站在我们自己的角度来看,socket就是一个模块,我们通过模块中已经实现的方法来建立两个进程之间的连接和通信.
也有人把socket说成ip+port, ip是用来标识互联网中一台主机的位置, port是用来标识这台机器上的某个应用程序,所以,只要知道了ip+port,我们就能确定应用程序的位置,从而使用socket模块来建立通信.
TCP/UDP协议
TCP协议
TCP协议(Transmission Control Protocol)面向连接的,可靠的流式传输协议.适用于对稳定性要求高的,大文件传输.例如:web浏览器,邮件等.
当应用程序通过tcp协议与另一个程序通信时,它会发送一个请求,这个请求必须要被送到一个确切的地方,等双方'握手'之后,tcp将在两个程序之间建立一个全双工的通信.
这个全双工的通信将占用两个程序之间的通信线路,直到它被一方或双方关闭.
TCP的三次握手与四次挥手:
UDP协议
UDP协议(User Datagram Protocol)无连接的,不可靠的高效率的传输协议.适用于对效率要求高的,短消息的传输.例如:视频.
当应用程序通过UDP与另一程序通信时,双方之间不用建立连接.
四.套接字socket初使用
基于tcp协议的socket
TCP是基于连接的,必须先启动服务端,再启动客户端连接服务端.
server端
import socket sk = socket.socket() sk.bind(('127.0.0.1',9002)) sk.listen() conn,addr = sk.accept() conn.send('喂'.encode('utf-8')) msg = conn.recv(1024) print(msg.decode('utf-8')) conn.close() sk.close()
client端
import socket sk = socket.socket() # 买了一个手机 sk.connect(('127.0.0.1',9002)) # 直接拨号 msg = sk.recv(1024) print(msg.decode('utf-8')) sk.send('你好'.encode('utf-8')) sk.close()
基于udp协议的socket
UDP是无连接的,启动服务之后可以直接接收消息,所以不需要提前建立连接.
server端
import socket sk = socket.socket(type = socket.SOCK_DGRAM) sk.bind(('127.0.0.1',9011)) msg,addr = sk.recvfrom(1024) sk.sendto(msg.decode().upper().encode(),addr) sk.close()
client端
import socket sk = socket.socket(type = socket.SOCK_DGRAM) sk.sendto(b'hello',('127.0.0.1',9011)) msg,addr = sk.recvfrom(1024) print(msg) sk.close()
五.黏包
黏包现象的成因:
发送端可以是1k 1k地发送数据,而接收端的应用程序可能是2k 2k地接收数据,或者是nk nk的接收数据,也就是说,应用程序看到的数据是一个整体,或者说是一个流(stream),一条消息到底有多少字节应用程序是看不到的,而tcp协议是流式传输协议,这就是tcp容易出现黏包现象的原因.
而udp是面向消息的协议,每个udp段都是一条消息,应用程序必须以消息为单位提取消息,而不能提取任意字节的数据,因此,udp没有黏包现象.
发生黏包现象的两种情况:
1.发送方的缓存机制
发送端会等缓冲区满才发送出去,数据会合在一起,造成黏包
2.接收方的缓存机制
接收端会等缓冲区满才提取数据,造成多个包接收,造成黏包
黏包的解决方案
产生黏包现象的根源在于,接收端不知道发送端将要发送的字节流的长度,所以解决黏包问题的关键是,如何让发送端在发送数据之前,让接收端知晓将要传送的字节流的总大小,然后让接收端按照长度循环接收完所有数据.
我们可以借助一个模块,这个模块帮助我们把我们要发送的数据长度转换成固定长度的字节,这样客户端在接收数据之前,只要先接收这个长度,然后按照这个长度收取数据,就能保证数据的完整性了.
struct模块
该模块,可以把一个数字类型,转换成固定长度的bytes.
import struct res = struct.pack('i',12334567) print(res,len(res))
# b'xe75xbcx00' 4
再来一个例子:
import struct
res = struct.pack('i',1234567890)
print(res,len(res))
# b'xd2x02x96I' 4
可以看到,不管是列子1中的8位数字,还是例子2中的10位数字,都转换成了固定4字节的bytes
注意: struct后的参数是有限制的.
import struct
res = struct.pack('i',12345678901)
print(res,len(res))
# struct.error: argument out of range
因此,我们就可以使用struct模块,将数据的长度固定为4字节,接收端先接收4字节,再将接收到的数据struct为数据的长度来接收数据.
import struct import socket sk = socket.socket() sk.bind(('127.0.0.1',9001)) sk.listen() conn,addr = sk.accept() msg = 'hellohellohellohellohellohello' res = struct.pack('i',len(msg)) conn.send(res) conn.send(msg.encode()) conn.send(b'world') sk.close()
import struct import time import socket sk = socket.socket() sk.connect(('127.0.0.1',9001)) time.sleep(0.1) n = sk.recv(4) res = struct.unpack('i',n)[0] msg1 = sk.recv(res).decode() msg2 = sk.recv(1024).decode() print(msg1) print(msg2) sk.close()